"use client";

import { useEffect, useMemo, useRef, useState } from "react";

type Granularity = "year" | "month" | "day";

type TreeNode = {
  id: string;
  label: string;
  ts: string; // ISO string from API
  countTotal: number;
  countReady: number;
  children?: TreeNode[];
};

type ApiTreeRow = {
  bucket: string;
  group_ts: string;
  count_total: number;
  count_ready: number;
};

type ApiTreeResponse = {
  granularity: Granularity;
  start: string | null;
  end: string | null;
  nodes: ApiTreeRow[];
};

type Orientation = "vertical" | "horizontal";

type ExpandedState = Record<string, boolean>;

function monthKey(d: Date) {
  return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}`;
}

function dayKey(d: Date) {
  return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}-${String(d.getUTCDate()).padStart(2, "0")}`;
}

function yearKey(d: Date) {
  return `${d.getUTCFullYear()}`;
}

function buildHierarchy(dayRows: ApiTreeRow[]): TreeNode[] {
  const years = new Map<string, TreeNode>();
  const months = new Map<string, TreeNode>();

  for (const row of dayRows) {
    const date = new Date(row.group_ts);
    const year = yearKey(date);
    const month = monthKey(date);
    const day = dayKey(date);

    const yMapKey = `${row.bucket}:${year}`;
    const mMapKey = `${row.bucket}:${month}`;

    let yNode = years.get(yMapKey);
    if (!yNode) {
      yNode = {
        id: `y:${year}:${row.bucket}`,
        label: year,
        ts: new Date(Date.UTC(Number(year), 0, 1)).toISOString(),
        countTotal: 0,
        countReady: 0,
        children: [],
      };
      years.set(yMapKey, yNode);
    }

    let mNode = months.get(mMapKey);
    if (!mNode) {
      const [yy, mm] = month.split("-");
      mNode = {
        id: `m:${month}:${row.bucket}`,
        label: `${yy}-${mm}`,
        ts: new Date(Date.UTC(Number(yy), Number(mm) - 1, 1)).toISOString(),
        countTotal: 0,
        countReady: 0,
        children: [],
      };
      months.set(mMapKey, mNode);
      yNode.children!.push(mNode);
    }

    const dNode: TreeNode = {
      id: `d:${day}:${row.bucket}`,
      label: day,
      ts: row.group_ts,
      countTotal: row.count_total,
      countReady: row.count_ready,
    };

    mNode.children!.push(dNode);

    // rollups
    mNode.countTotal += row.count_total;
    mNode.countReady += row.count_ready;
    yNode.countTotal += row.count_total;
    yNode.countReady += row.count_ready;
  }

  // Sort ascending within each parent
  const yearNodes = Array.from(years.values()).sort((a, b) => a.label.localeCompare(b.label));
  for (const y of yearNodes) {
    y.children = (y.children ?? []).sort((a, b) => a.label.localeCompare(b.label));
    for (const m of y.children) {
      m.children = (m.children ?? []).sort((a, b) => a.label.localeCompare(b.label));
    }
  }

  return yearNodes;
}

function gatherVisible(
  roots: TreeNode[],
  expanded: ExpandedState,
): Array<{ node: TreeNode; depth: number; parentId: string | null }> {
  const out: Array<{ node: TreeNode; depth: number; parentId: string | null }> = [];

  function walk(nodes: TreeNode[], depth: number, parentId: string | null) {
    for (const n of nodes) {
      out.push({ node: n, depth, parentId });
      const isExpanded = expanded[n.id] ?? depth < 1; // default expand years
      if (n.children?.length && isExpanded) {
        walk(n.children, depth + 1, n.id);
      }
    }
  }

  walk(roots, 0, null);
  return out;
}

export function TimelineTree(props: { onSelectDay?: (dayIso: string) => void }) {
  const [orientation, setOrientation] = useState<Orientation>("vertical");
  const [expanded, setExpanded] = useState<ExpandedState>({});
  const [rows, setRows] = useState<ApiTreeRow[] | null>(null);
  const [error, setError] = useState<string | null>(null);

  // simple pan/zoom via viewBox
  const svgRef = useRef<SVGSVGElement | null>(null);
  const [viewBox, setViewBox] = useState({ x: 0, y: 0, w: 1200, h: 800 });
  const dragState = useRef<{
    active: boolean;
    startX: number;
    startY: number;
    startViewBox: typeof viewBox;
  } | null>(null);

  useEffect(() => {
    let cancelled = false;
    async function load() {
      try {
        setError(null);
        const res = await fetch("/api/tree?granularity=day&limit=500&includeFailed=1", {
          cache: "no-store",
        });
        if (!res.ok) throw new Error(`tree_fetch_failed:${res.status}`);
        const json = (await res.json()) as ApiTreeResponse;
        if (!cancelled) setRows(json.nodes);
      } catch (e) {
        if (!cancelled) setError(e instanceof Error ? e.message : String(e));
      }
    }
    void load();
    return () => {
      cancelled = true;
    };
  }, []);

  const roots = useMemo(() => (rows ? buildHierarchy(rows) : []), [rows]);
  const visible = useMemo(() => gatherVisible(roots, expanded), [roots, expanded]);

  const layout = useMemo(() => {
    const nodeGap = 56;
    const depthGap = 180;

    const positions = new Map<string, { x: number; y: number }>();

    for (let i = 0; i < visible.length; i++) {
      const { node, depth } = visible[i];
      const primary = i * nodeGap;
      const secondary = depth * depthGap;

      const x = orientation === "vertical" ? secondary : primary;
      const y = orientation === "vertical" ? primary : secondary;

      positions.set(node.id, { x, y });
    }

    // compute bounds
    let maxX = 0;
    let maxY = 0;
    for (const p of positions.values()) {
      maxX = Math.max(maxX, p.x);
      maxY = Math.max(maxY, p.y);
    }

    const padding = 120;
    const w = maxX + padding * 2;
    const h = maxY + padding * 2;

    return { positions, w, h, padding };
  }, [visible, orientation]);

  useEffect(() => {
    // reset viewBox when layout changes
    setViewBox((vb) => ({ ...vb, w: Math.max(layout.w, 1200), h: Math.max(layout.h, 800) }));
  }, [layout.w, layout.h]);

  function toggleNode(id: string) {
    setExpanded((prev) => ({ ...prev, [id]: !(prev[id] ?? false) }));
  }

  function onPointerDown(e: React.PointerEvent<SVGSVGElement>) {
    (e.currentTarget as SVGSVGElement).setPointerCapture(e.pointerId);
    dragState.current = {
      active: true,
      startX: e.clientX,
      startY: e.clientY,
      startViewBox: viewBox,
    };
  }

  function onPointerMove(e: React.PointerEvent<SVGSVGElement>) {
    if (!dragState.current?.active) return;
    const dx = e.clientX - dragState.current.startX;
    const dy = e.clientY - dragState.current.startY;

    // Convert pixels to viewBox units roughly; this is intentionally simple.
    const scaleX = viewBox.w / 900;
    const scaleY = viewBox.h / 600;

    setViewBox({
      ...viewBox,
      x: dragState.current.startViewBox.x - dx * scaleX,
      y: dragState.current.startViewBox.y - dy * scaleY,
    });
  }

  function onPointerUp(e: React.PointerEvent<SVGSVGElement>) {
    dragState.current = null;
    try {
      (e.currentTarget as SVGSVGElement).releasePointerCapture(e.pointerId);
    } catch {
      // ignore
    }
  }

  function onWheel(e: React.WheelEvent<SVGSVGElement>) {
    e.preventDefault();
    const factor = e.deltaY < 0 ? 0.9 : 1.1;

    const mouseX = e.clientX;
    const mouseY = e.clientY;
    const rect = e.currentTarget.getBoundingClientRect();

    const px = (mouseX - rect.left) / rect.width;
    const py = (mouseY - rect.top) / rect.height;

    const newW = Math.max(300, Math.min(5000, viewBox.w * factor));
    const newH = Math.max(300, Math.min(5000, viewBox.h * factor));

    const newX = viewBox.x + (viewBox.w - newW) * px;
    const newY = viewBox.y + (viewBox.h - newH) * py;

    setViewBox({ x: newX, y: newY, w: newW, h: newH });
  }

  return (
    <div style={{ display: "grid", gap: 12 }}>
      <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
        <strong>Timeline</strong>
        <button
          type="button"
          onClick={() => setOrientation((o) => (o === "vertical" ? "horizontal" : "vertical"))}
        >
          Orientation: {orientation}
        </button>
        <button type="button" onClick={() => setViewBox({ x: 0, y: 0, w: 1200, h: 800 })}>
          Reset view
        </button>
        {rows ? <span style={{ color: "#666" }}>{rows.length} day nodes</span> : null}
      </div>

      {error ? <div style={{ color: "#b00" }}>Error: {error}</div> : null}
      {!rows && !error ? <div style={{ color: "#666" }}>Loading tree…</div> : null}

      <div
        style={{
          border: "1px solid #ddd",
          borderRadius: 8,
          overflow: "hidden",
          height: 600,
        }}
      >
        <svg
          ref={svgRef}
          viewBox={`${viewBox.x} ${viewBox.y} ${viewBox.w} ${viewBox.h}`}
          width="100%"
          height="100%"
          onPointerDown={onPointerDown}
          onPointerMove={onPointerMove}
          onPointerUp={onPointerUp}
          onWheel={onWheel}
          style={{ touchAction: "none", background: "#fafafa" }}
        >
          <g transform={`translate(${layout.padding} ${layout.padding})`}>
            {/* edges */}
            {visible.map(({ node, parentId }) => {
              if (!parentId) return null;
              const p = layout.positions.get(parentId);
              const c = layout.positions.get(node.id);
              if (!p || !c) return null;
              return (
                <line
                  key={`${parentId}->${node.id}`}
                  x1={p.x}
                  y1={p.y}
                  x2={c.x}
                  y2={c.y}
                  stroke="#bbb"
                  strokeWidth={2}
                />
              );
            })}

            {/* nodes */}
            {visible.map(({ node }) => {
              const pos = layout.positions.get(node.id);
              if (!pos) return null;

              const hasChildren = Boolean(node.children?.length);
              const isExpanded = expanded[node.id] ?? node.id.startsWith("y:");

              const isDay = node.id.startsWith("d:");
              const clickCursor = hasChildren || isDay ? "pointer" : "default";

              return (
                <g
                  key={node.id}
                  transform={`translate(${pos.x} ${pos.y})`}
                  style={{ cursor: clickCursor }}
                  onClick={() => {
                    if (hasChildren) toggleNode(node.id);
                    if (isDay) props.onSelectDay?.(node.ts);
                  }}
                >
                  <circle r={12} fill={hasChildren ? "#1f6feb" : "#666"} />
                  <text x={20} y={5} fontSize={14} fill="#111">
                    {node.label} ({node.countReady}/{node.countTotal})
                    {hasChildren ? (isExpanded ? " ▼" : " ▶") : ""}
                  </text>
                </g>
              );
            })}
          </g>
        </svg>
      </div>

      <div style={{ color: "#666", fontSize: 12 }}>
        Drag to pan, mouse wheel to zoom. Click years/months to expand.
      </div>
    </div>
  );
}
