<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";

type TldrawApi = {
  Tldraw: unknown;
  createShapeId: (seed?: string) => string;
  toRichText: (text: string) => unknown;
};

type ShapeNode = {
  id: string;
  level: number;
  parentId: string | null;
  label: string;
  bounds: { x: number; y: number; w: number; h: number };
};

const LEVEL_LABELS = ["year", "month", "week", "day"];
const MONTH_LABELS = [
  "January", "February", "March", "April",
  "May", "June", "July", "August",
  "September", "October", "November", "December",
];
const ZOOM_MS = 1100;

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;

function buildScene(api: TldrawApi) {
  const nodes = new Map<string, ShapeNode>();
  const children = new Map<string, string[]>();

  const attachNode = (node: ShapeNode) => {
    nodes.set(node.id, node);
    if (!node.parentId) return;
    if (!children.has(node.parentId)) children.set(node.parentId, []);
    children.get(node.parentId)?.push(node.id);
  };

  const yearId = api.createShapeId("year-2026");
  attachNode({
    id: yearId,
    level: 0,
    parentId: null,
    label: "Year 2026",
    bounds: { x: 0, y: 0, w: 3200, h: 2200 },
  });

  const monthCols = 4;
  const monthRows = 3;
  const monthGap = 34;
  const monthW = 730;
  const monthH = 650;
  const monthStartX = 70;
  const monthStartY = 90;

  for (let month = 0; month < 12; month += 1) {
    const col = month % monthCols;
    const row = Math.floor(month / monthCols) % monthRows;
    const x = monthStartX + col * (monthW + monthGap);
    const y = monthStartY + row * (monthH + monthGap);

    const monthId = api.createShapeId(`month-${month + 1}`);
    const monthLabel = MONTH_LABELS[month] ?? `Month ${month + 1}`;

    attachNode({
      id: monthId,
      level: 1,
      parentId: yearId,
      label: monthLabel,
      bounds: { x, y, w: monthW, h: monthH },
    });

    const weekGap = 10;
    const weekRows = 6;
    const weekX = x + 14;
    const weekY = y + 54;
    const weekW = monthW - 28;
    const weekH = (monthH - 68 - weekGap * (weekRows - 1)) / weekRows;

    for (let week = 0; week < weekRows; week += 1) {
      const wy = weekY + week * (weekH + weekGap);
      const weekId = api.createShapeId(`month-${month + 1}-week-${week + 1}`);

      attachNode({
        id: weekId,
        level: 2,
        parentId: monthId,
        label: `Week ${week + 1}`,
        bounds: { x: weekX, y: wy, w: weekW, h: weekH },
      });

      const dayGap = 8;
      const dayCols = 7;
      const dayX = weekX + 10;
      const dayY = wy + 28;
      const dayW = (weekW - 20 - dayGap * (dayCols - 1)) / dayCols;
      const dayH = weekH - 36;

      for (let day = 0; day < dayCols; day += 1) {
        const dx = dayX + day * (dayW + dayGap);
        const dayId = api.createShapeId(`month-${month + 1}-week-${week + 1}-day-${day + 1}`);
        attachNode({
          id: dayId,
          level: 3,
          parentId: weekId,
          label: `Day ${day + 1}`,
          bounds: { x: dx, y: dayY, w: dayW, h: dayH },
        });
      }
    }
  }

  return { yearId, nodes, children };
}

function createShapesFromScene(api: TldrawApi, nodes: Map<string, ShapeNode>) {
  const list: Array<Record<string, unknown>> = [];

  for (const node of nodes.values()) {
    list.push({
      id: node.id,
      type: "geo",
      x: node.bounds.x,
      y: node.bounds.y,
      isLocked: true,
      props: {
        geo: "rectangle",
        w: node.bounds.w,
        h: node.bounds.h,
        richText: api.toRichText(node.label),
        color: "black",
        fill: "none",
        dash: "draw",
      },
      meta: {
        level: node.level,
        label: node.label,
      },
    });
  }

  return list;
}

function setupZoomFlow(editor: any, api: TldrawApi, host: HTMLDivElement) {
  const { yearId, nodes, children } = buildScene(api);
  const shapes = createShapesFromScene(api, nodes);
  editor.createShapes(shapes);

  let activeId = yearId;
  let animating = false;
  let token = 0;

  const moveTo = (shapeId: string, immediate = false) => {
    const bounds = editor.getShapePageBounds(shapeId);
    if (!bounds) return;

    token += 1;
    const localToken = token;
    const duration = immediate ? 0 : ZOOM_MS;

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

    activeId = shapeId;
    const node = nodes.get(shapeId);
    if (node) {
      activeLevel.value = node.level;
      activeLabel.value = node.label;
    }

    window.setTimeout(() => {
      if (localToken !== token) return;
      animating = false;
    }, duration + 40);
  };

  const firstChild = (shapeId: string) => {
    const next = children.get(shapeId);
    return next?.[0] ?? null;
  };

  const resolveChildFromHover = (shapeId: string) => {
    const nextIds = children.get(shapeId) ?? [];
    if (!nextIds.length) return null;

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

    if (!hit) return firstChild(shapeId);
    let cursor: string | null = hit.id ?? null;
    while (cursor && nodes.get(cursor)?.parentId !== shapeId) {
      cursor = nodes.get(cursor)?.parentId ?? null;
    }

    if (cursor && nextIds.includes(cursor)) return cursor;
    return firstChild(shapeId);
  };

  const zoomIn = () => {
    const node = nodes.get(activeId);
    if (!node || node.level >= 3) return;
    const target = resolveChildFromHover(activeId);
    if (!target) return;
    moveTo(target, false);
  };

  const zoomOut = () => {
    const parent = nodes.get(activeId)?.parentId;
    if (!parent) return;
    moveTo(parent, false);
  };

  const onWheel = (event: WheelEvent) => {
    event.preventDefault();
    if (animating) return;
    if (event.deltaY < 0) {
      zoomIn();
      return;
    }
    zoomOut();
  };

  const onDoubleClick = () => {
    if (animating) return;
    moveTo(yearId, false);
  };

  host.addEventListener("wheel", onWheel, { passive: false });
  host.addEventListener("dblclick", onDoubleClick);

  moveTo(yearId, true);

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

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

    const api = {
      Tldraw,
      createShapeId,
      toRichText,
    } satisfies TldrawApi;

    reactRoot = createRoot(hostRef.value);
    reactRoot.render(
      createElement(api.Tldraw, {
        hideUi: true,
        onMount: (editor: any) => {
          status.value = "Wheel up = zoom in, wheel down = zoom out, double click = reset";
          teardown = setupZoomFlow(editor, api, 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>
