<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
import { createElement } from "react";
import { createRoot } from "react-dom/client";
import { Tldraw, createShapeId, toRichText } from "tldraw";
import "tldraw/tldraw.css";

/* ------------------------------------------------------------------ */
/*  Constants                                                          */
/* ------------------------------------------------------------------ */

const LEVEL_LABELS = ["year", "month", "week", "day"] as const;
const MONTH_LABELS = [
  "January", "February", "March", "April",
  "May", "June", "July", "August",
  "September", "October", "November", "December",
];
const DAY_LABELS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];

const ZOOM_MS = 800;
const PRIME_TICKS_REQUIRED = 2;
const PRIME_RESET_MS = 600;

/* ------------------------------------------------------------------ */
/*  Reactive state                                                     */
/* ------------------------------------------------------------------ */

const hostRef = ref<HTMLDivElement | null>(null);
const status = ref("Loading tldraw engine...");

const activeLevel = ref(0);
const activeLabel = ref("Year 2026");

const debugInfo = computed(
  () => `${LEVEL_LABELS[activeLevel.value] ?? "year"}: ${activeLabel.value}`,
);

let reactRoot: { unmount: () => void } | null = null;
let teardown: (() => void) | null = null;

/* ------------------------------------------------------------------ */
/*  Shape builders — one function per zoom level                       */
/* ------------------------------------------------------------------ */

type ShapeDef = {
  id: string;
  type: "geo";
  x: number;
  y: number;
  isLocked: boolean;
  props: Record<string, unknown>;
  meta: Record<string, unknown>;
};

/** Level 0 — year view: 1 container + 12 month cards */
function buildYearShapes(): { shapes: ShapeDef[]; containerId: string; childIds: string[] } {
  const containerId = createShapeId("year-2026");
  const shapes: ShapeDef[] = [];
  const childIds: string[] = [];

  // Year container
  shapes.push({
    id: containerId,
    type: "geo",
    x: 0,
    y: 0,
    isLocked: true,
    props: {
      geo: "rectangle",
      w: 3200,
      h: 2200,
      richText: toRichText("2026"),
      color: "black",
      fill: "none",
      dash: "draw",
    },
    meta: { level: 0, label: "Year 2026", role: "container" },
  });

  const cols = 4;
  const gap = 34;
  const cardW = 730;
  const cardH = 650;
  const startX = 70;
  const startY = 90;

  for (let i = 0; i < 12; i++) {
    const col = i % cols;
    const row = Math.floor(i / cols);
    const x = startX + col * (cardW + gap);
    const y = startY + row * (cardH + gap);
    const id = createShapeId(`month-${i}`);
    childIds.push(id);

    shapes.push({
      id,
      type: "geo",
      x,
      y,
      isLocked: true,
      props: {
        geo: "rectangle",
        w: cardW,
        h: cardH,
        richText: toRichText(MONTH_LABELS[i]!),
        color: "black",
        fill: "semi",
        dash: "draw",
      },
      meta: { level: 1, label: MONTH_LABELS[i], role: "child", index: i },
    });
  }

  return { shapes, containerId, childIds };
}

/** Level 1 — month view: 1 month container + 6 week rows */
function buildMonthShapes(monthIndex: number): { shapes: ShapeDef[]; containerId: string; childIds: string[] } {
  const containerId = createShapeId(`month-${monthIndex}-expanded`);
  const shapes: ShapeDef[] = [];
  const childIds: string[] = [];

  shapes.push({
    id: containerId,
    type: "geo",
    x: 0,
    y: 0,
    isLocked: true,
    props: {
      geo: "rectangle",
      w: 2400,
      h: 1800,
      richText: toRichText(MONTH_LABELS[monthIndex]!),
      color: "black",
      fill: "none",
      dash: "draw",
    },
    meta: { level: 1, label: MONTH_LABELS[monthIndex], role: "container", index: monthIndex },
  });

  const weekGap = 20;
  const weekH = (1800 - 100 - weekGap * 5) / 6;
  const weekW = 2400 - 80;
  const weekX = 40;
  const weekStartY = 80;

  for (let w = 0; w < 6; w++) {
    const y = weekStartY + w * (weekH + weekGap);
    const id = createShapeId(`month-${monthIndex}-week-${w}`);
    childIds.push(id);

    shapes.push({
      id,
      type: "geo",
      x: weekX,
      y,
      isLocked: true,
      props: {
        geo: "rectangle",
        w: weekW,
        h: weekH,
        richText: toRichText(`Week ${w + 1}`),
        color: "black",
        fill: "semi",
        dash: "draw",
      },
      meta: { level: 2, label: `Week ${w + 1}`, role: "child", index: w },
    });
  }

  return { shapes, containerId, childIds };
}

/** Level 2 — week view: 1 week container + 7 day columns */
function buildWeekShapes(monthIndex: number, weekIndex: number): { shapes: ShapeDef[]; containerId: string; childIds: string[] } {
  const containerId = createShapeId(`week-${monthIndex}-${weekIndex}-expanded`);
  const shapes: ShapeDef[] = [];
  const childIds: string[] = [];

  shapes.push({
    id: containerId,
    type: "geo",
    x: 0,
    y: 0,
    isLocked: true,
    props: {
      geo: "rectangle",
      w: 2800,
      h: 1600,
      richText: toRichText(`${MONTH_LABELS[monthIndex]} · Week ${weekIndex + 1}`),
      color: "black",
      fill: "none",
      dash: "draw",
    },
    meta: { level: 2, label: `Week ${weekIndex + 1}`, role: "container", monthIndex, weekIndex },
  });

  const dayGap = 16;
  const dayW = (2800 - 80 - dayGap * 6) / 7;
  const dayH = 1600 - 120;
  const dayX = 40;
  const dayY = 80;

  for (let d = 0; d < 7; d++) {
    const x = dayX + d * (dayW + dayGap);
    const id = createShapeId(`week-${monthIndex}-${weekIndex}-day-${d}`);
    childIds.push(id);

    shapes.push({
      id,
      type: "geo",
      x,
      y: dayY,
      isLocked: true,
      props: {
        geo: "rectangle",
        w: dayW,
        h: dayH,
        richText: toRichText(DAY_LABELS[d]!),
        color: "black",
        fill: "semi",
        dash: "draw",
      },
      meta: { level: 3, label: DAY_LABELS[d], role: "child", index: d },
    });
  }

  return { shapes, containerId, childIds };
}

/** Level 3 — day view: 1 day container + time slot blocks */
function buildDayShapes(monthIndex: number, weekIndex: number, dayIndex: number): { shapes: ShapeDef[]; containerId: string; childIds: string[] } {
  const containerId = createShapeId(`day-${monthIndex}-${weekIndex}-${dayIndex}-expanded`);
  const shapes: ShapeDef[] = [];
  const childIds: string[] = [];

  const dayLabel = DAY_LABELS[dayIndex] ?? `Day ${dayIndex + 1}`;

  shapes.push({
    id: containerId,
    type: "geo",
    x: 0,
    y: 0,
    isLocked: true,
    props: {
      geo: "rectangle",
      w: 1200,
      h: 2400,
      richText: toRichText(`${MONTH_LABELS[monthIndex]} · Week ${weekIndex + 1} · ${dayLabel}`),
      color: "black",
      fill: "none",
      dash: "draw",
    },
    meta: { level: 3, label: dayLabel, role: "container", monthIndex, weekIndex, dayIndex },
  });

  // Time slots from 8:00 to 20:00 (12 hour-slots)
  const slotGap = 12;
  const slotH = (2400 - 100 - slotGap * 11) / 12;
  const slotW = 1200 - 80;
  const slotX = 40;
  const slotStartY = 80;

  for (let s = 0; s < 12; s++) {
    const y = slotStartY + s * (slotH + slotGap);
    const hour = 8 + s;
    const id = createShapeId(`day-${monthIndex}-${weekIndex}-${dayIndex}-slot-${s}`);
    childIds.push(id);

    shapes.push({
      id,
      type: "geo",
      x: slotX,
      y,
      isLocked: true,
      props: {
        geo: "rectangle",
        w: slotW,
        h: slotH,
        richText: toRichText(`${hour}:00`),
        color: "light-violet",
        fill: "semi",
        dash: "draw",
      },
      meta: { level: 4, label: `${hour}:00`, role: "slot", index: s },
    });
  }

  return { shapes, containerId, childIds };
}

/* ------------------------------------------------------------------ */
/*  Main zoom flow controller                                          */
/* ------------------------------------------------------------------ */

function setupZoomFlow(editor: any, host: HTMLDivElement) {
  let currentLevel = 0;
  let selectedMonth = 0;
  let selectedWeek = 0;
  let selectedDay = 0;

  let containerId = "";
  let childIds: string[] = [];

  let animating = false;
  let token = 0;

  // Wheel prime state
  let primeDirection: "in" | "out" | "" = "";
  let primeTicks = 0;
  let primeTimer: ReturnType<typeof setTimeout> | null = null;

  /** Wipe canvas and draw shapes for a given level */
  function renderLevel(level: number, immediate: boolean) {
    // Delete all existing shapes
    const allShapeIds = editor.getCurrentPageShapeIds();
    if (allShapeIds.size > 0) {
      editor.deleteShapes([...allShapeIds]);
    }

    let result: { shapes: ShapeDef[]; containerId: string; childIds: string[] };

    switch (level) {
      case 0:
        result = buildYearShapes();
        break;
      case 1:
        result = buildMonthShapes(selectedMonth);
        break;
      case 2:
        result = buildWeekShapes(selectedMonth, selectedWeek);
        break;
      case 3:
        result = buildDayShapes(selectedMonth, selectedWeek, selectedDay);
        break;
      default:
        return;
    }

    editor.createShapes(result.shapes);
    containerId = result.containerId;
    childIds = result.childIds;
    currentLevel = level;

    // Update reactive debug state
    activeLevel.value = level;
    const containerMeta = result.shapes.find((s) => s.id === containerId)?.meta;
    activeLabel.value = (containerMeta?.label as string) ?? LEVEL_LABELS[level] ?? "";

    // Zoom camera to fit container
    const bounds = editor.getShapePageBounds(containerId);
    if (bounds) {
      token += 1;
      const localToken = token;
      const duration = immediate ? 0 : ZOOM_MS;

      animating = !immediate;
      editor.stopCameraAnimation();
      editor.zoomToBounds(bounds, {
        inset: 40,
        animation: { duration },
        immediate,
        force: true,
      });

      if (!immediate) {
        setTimeout(() => {
          if (localToken === token) animating = false;
        }, duration + 50);
      }
    }
  }

  /** Resolve which child the mouse is hovering over */
  function resolveHoveredChild(): string | null {
    if (childIds.length === 0) return null;

    const pointer = editor.inputs.currentPagePoint;
    if (!pointer) return childIds[0] ?? null;

    const hit = editor.getShapeAtPoint(pointer, {
      margin: 0,
      hitInside: true,
      hitLocked: true,
      hitLabels: true,
      hitFrameInside: true,
    });

    if (hit && childIds.includes(hit.id)) return hit.id;

    // If hit is the container itself, try first child
    return childIds[0] ?? null;
  }

  function resetPrime() {
    primeDirection = "";
    primeTicks = 0;
    if (primeTimer) {
      clearTimeout(primeTimer);
      primeTimer = null;
    }
  }

  function zoomIn() {
    if (currentLevel >= 3) return;

    const targetId = resolveHoveredChild();
    if (!targetId) return;

    // Find the index of hovered child from its meta
    const shape = editor.getShape(targetId);
    const idx = (shape?.meta?.index as number) ?? 0;

    switch (currentLevel) {
      case 0:
        selectedMonth = idx;
        break;
      case 1:
        selectedWeek = idx;
        break;
      case 2:
        selectedDay = idx;
        break;
    }

    renderLevel(currentLevel + 1, false);
  }

  function zoomOut() {
    if (currentLevel <= 0) return;
    renderLevel(currentLevel - 1, false);
  }

  function onWheel(event: WheelEvent) {
    event.preventDefault();
    if (animating) return;

    const direction: "in" | "out" = event.deltaY < 0 ? "in" : "out";

    // Check if can go in this direction
    if (direction === "in" && currentLevel >= 3) return;
    if (direction === "out" && currentLevel <= 0) return;

    // Reset prime if direction changed
    if (primeDirection !== direction) {
      resetPrime();
      primeDirection = direction;
    }

    primeTicks += 1;

    // Reset prime timer
    if (primeTimer) clearTimeout(primeTimer);
    primeTimer = setTimeout(resetPrime, PRIME_RESET_MS);

    // Highlight target on pre-ticks
    if (primeTicks < PRIME_TICKS_REQUIRED) {
      // Visual hint: briefly scale hovered child
      if (direction === "in") {
        const targetId = resolveHoveredChild();
        if (targetId) {
          const shape = editor.getShape(targetId);
          if (shape) {
            // Flash the shape by toggling fill
            editor.updateShape({
              id: targetId,
              type: "geo",
              props: { fill: "solid" },
            });
            setTimeout(() => {
              try {
                editor.updateShape({
                  id: targetId,
                  type: "geo",
                  props: { fill: "semi" },
                });
              } catch (_) { /* shape might be gone */ }
            }, 150);
          }
        }
      }
      return;
    }

    // Enough ticks — execute zoom
    resetPrime();

    if (direction === "in") {
      zoomIn();
    } else {
      zoomOut();
    }
  }

  function onDoubleClick() {
    if (animating) return;
    resetPrime();
    if (currentLevel === 0) return;
    renderLevel(0, false);
  }

  // Initial render
  renderLevel(0, true);

  // Event listeners on host element (outside tldraw's event system)
  host.addEventListener("wheel", onWheel, { passive: false });
  host.addEventListener("dblclick", onDoubleClick);

  return () => {
    resetPrime();
    host.removeEventListener("wheel", onWheel);
    host.removeEventListener("dblclick", onDoubleClick);
  };
}

/* ------------------------------------------------------------------ */
/*  Lifecycle                                                          */
/* ------------------------------------------------------------------ */

onMounted(async () => {
  try {
    if (!hostRef.value) return;

    reactRoot = createRoot(hostRef.value);
    reactRoot.render(
      createElement(Tldraw, {
        hideUi: true,
        onMount: (editor: any) => {
          status.value = "Wheel up = zoom in · wheel down = zoom out · double click = reset";
          teardown = setupZoomFlow(editor, hostRef.value as HTMLDivElement);
        },
      }),
    );
  } catch (error) {
    status.value = "Failed to initialize local tldraw engine";
    console.error(error);
  }
});

onBeforeUnmount(() => {
  teardown?.();
  teardown = null;
  reactRoot?.unmount();
  reactRoot = null;
});
</script>

<template>
  <section class="tldraw-lab-root">
    <header class="tldraw-lab-toolbar">
      <p class="tldraw-lab-title">{{ status }}</p>
      <p class="tldraw-lab-subtitle">{{ debugInfo }}</p>
    </header>
    <div ref="hostRef" class="tldraw-lab-canvas" />
  </section>
</template>

<style scoped>
.tldraw-lab-root {
  height: calc(100dvh - 1rem);
  min-height: 640px;
  border-radius: 14px;
  border: 1px solid color-mix(in oklab, var(--color-base-content) 20%, transparent);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  background: color-mix(in oklab, var(--color-base-100) 95%, transparent);
}

.tldraw-lab-toolbar {
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 0 10px;
  border-bottom: 1px solid color-mix(in oklab, var(--color-base-content) 16%, transparent);
  background: color-mix(in oklab, var(--color-base-100) 94%, transparent);
}

.tldraw-lab-title {
  font-size: 0.78rem;
  color: color-mix(in oklab, var(--color-base-content) 72%, transparent);
}

.tldraw-lab-subtitle {
  font-size: 0.74rem;
  color: color-mix(in oklab, var(--color-base-content) 60%, transparent);
}

.tldraw-lab-canvas {
  width: 100%;
  height: calc(100% - 42px);
}
</style>
