"use client";

// Vendored family-chart styles (the package blocks the CSS subpath export).
import "../app/trees/[id]/tree/chart.css";

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

import type { components } from "@/lib/api/schema";
import { DepthControl } from "@/components/depth-control";

type Person = components["schemas"]["PersonRead"];
type Relationship = components["schemas"]["RelationshipRead"];
type Event = components["schemas"]["EventRead"];

function splitName(name: string | null | undefined): [string, string] {
  const t = (name ?? "").trim().split(/\s+/).filter(Boolean);
  if (t.length <= 1) return [name ?? "", ""];
  return [t.slice(0, -1).join(" "), t[t.length - 1]];
}

/**
 * Read-only family-chart hourglass for the public surface. Same renderer the
 * member tree view uses (incl. the cycle-sanitisation that keeps a bad graph
 * from blowing the stack), fed by already-redacted public data. Clicking a card
 * recenters; `onOpen` links out to the person's public page.
 */
export function PublicTreeChart({
  people,
  rels,
  events,
  focusId,
  onFocus,
  onOpen,
}: {
  people: Person[];
  rels: Relationship[];
  events: Event[];
  focusId: string | null;
  onFocus: (id: string) => void;
  onOpen?: (id: string) => void;
}) {
  const containerRef = useRef<HTMLDivElement>(null);
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const chartRef = useRef<any>(null);

  // Generations to show around the focus, each settable (or "all"). ALL_DEPTH is
  // a number bigger than any real lineage; the chart only renders real people.
  const [ancDepth, setAncDepth] = useState(3);
  const [progDepth, setProgDepth] = useState(2);
  const [ancAll, setAncAll] = useState(false);
  const [progAll, setProgAll] = useState(false);
  const ALL_DEPTH = 100;
  const effAnc = ancAll ? ALL_DEPTH : ancDepth;
  const effProg = progAll ? ALL_DEPTH : progDepth;

  const parentsOf = useCallback(
    (id: string) =>
      rels.filter((x) => x.type === "parent_child" && x.person_to_id === id).map((x) => x.person_from_id),
    [rels],
  );
  const partnersOf = useCallback(
    (id: string) =>
      rels
        .filter((x) => x.type === "partnership" && (x.person_from_id === id || x.person_to_id === id))
        .map((x) => (x.person_from_id === id ? x.person_to_id : x.person_from_id)),
    [rels],
  );
  const years = useMemo(() => {
    const m = new Map<string, string>();
    for (const ev of events) {
      if (ev.person_id && ev.event_type === "birth" && !m.has(ev.person_id)) {
        const y = ev.date_start ? ev.date_start.slice(0, 4) : ev.date_value ?? "";
        if (y) m.set(ev.person_id, y);
      }
    }
    return m;
  }, [events]);
  const byId = useMemo(() => new Map(people.map((p) => [p.id, p])), [people]);
  const nameOf = useCallback((id: string) => byId.get(id)?.primary_name ?? "person", [byId]);

  // Build the chart when the data changes (not on focus — recenter handles that).
  useEffect(() => {
    if (people.length === 0 || !containerRef.current) return;
    let cancelled = false;
    (async () => {
      const alive = new Set(people.map((pp) => pp.id));
      const ok = (ids: string[], self: string) =>
        [...new Set(ids)].filter((id) => alive.has(id) && id !== self);

      const parentsMap = new Map<string, string[]>();
      const childrenMap = new Map<string, string[]>();
      const isAncestorOf = (ancestor: string, of: string): boolean => {
        const stack = [...(parentsMap.get(of) ?? [])];
        const seen = new Set<string>();
        while (stack.length) {
          const n = stack.pop()!;
          if (n === ancestor) return true;
          if (seen.has(n)) continue;
          seen.add(n);
          for (const p of parentsMap.get(n) ?? []) stack.push(p);
        }
        return false;
      };
      for (const pp of people) {
        const accepted: string[] = [];
        for (const par of ok(parentsOf(pp.id), pp.id)) {
          if (isAncestorOf(pp.id, par)) continue;
          accepted.push(par);
          parentsMap.set(pp.id, accepted);
          childrenMap.set(par, [...(childrenMap.get(par) ?? []), pp.id]);
        }
        parentsMap.set(pp.id, accepted);
      }

      const data = people.map((pp) => {
        const [fn, ln] = splitName(pp.primary_name);
        return {
          id: pp.id,
          data: {
            "first name": fn || "Unnamed",
            "last name": ln,
            birthday: years.get(pp.id) ?? "",
            gender: pp.gender === "female" ? "F" : "M",
          },
          rels: {
            spouses: ok(partnersOf(pp.id), pp.id),
            parents: parentsMap.get(pp.id) ?? [],
            children: childrenMap.get(pp.id) ?? [],
          },
        };
      });

      const f3 = await import("family-chart");
      if (cancelled || !containerRef.current) return;
      try {
        containerRef.current.innerHTML = "";
        const chart = f3.createChart(containerRef.current, data);
        chart.setCardHtml().setCardDisplay([["first name", "last name"], ["birthday"]]);
        chart.setOrientationHorizontal();
        chart.setAncestryDepth?.(effAnc);
        chart.setProgenyDepth?.(effProg);
        chart.setAfterUpdate?.(() => {
          const md = chart.getMainDatum?.();
          const id = md?.id ?? md?.data?.id;
          if (id) onFocus(id);
        });
        chartRef.current = chart;
        if (focusId) chart.updateMainId(focusId);
        chart.updateTree({ initial: true });
      } catch (err) {
        console.error("public tree render failed", err);
        if (containerRef.current) containerRef.current.innerHTML = "";
      }
    })();
    return () => {
      cancelled = true;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [people, rels, events]);

  // Recenter when the focus changes without rebuilding the chart.
  useEffect(() => {
    if (focusId && chartRef.current) {
      chartRef.current.updateMainId?.(focusId);
      chartRef.current.updateTree?.();
    }
  }, [focusId]);

  // Apply depth changes to the live chart without a full rebuild.
  useEffect(() => {
    if (!chartRef.current) return;
    chartRef.current.setAncestryDepth?.(effAnc);
    chartRef.current.setProgenyDepth?.(effProg);
    chartRef.current.updateTree?.();
  }, [effAnc, effProg]);

  return (
    <div className="space-y-2">
      <div className="flex flex-wrap items-center gap-x-6 gap-y-2 rounded-lg border border-[var(--border)] bg-[var(--surface)] px-4 py-2.5">
        <span className="text-sm font-medium">Generations</span>
        <DepthControl
          label="Ancestors"
          icon="↑"
          value={ancDepth}
          all={ancAll}
          onValue={(v) => {
            setAncAll(false);
            setAncDepth(v);
          }}
          onAll={setAncAll}
        />
        <DepthControl
          label="Descendants"
          icon="↓"
          value={progDepth}
          all={progAll}
          onValue={(v) => {
            setProgAll(false);
            setProgDepth(v);
          }}
          onAll={setProgAll}
        />
      </div>
      <div
        ref={containerRef}
        className="f3 rounded-xl border border-[var(--border)]"
        style={{ width: "100%", height: "74vh", background: "var(--surface)" }}
      />
      <div className="flex flex-wrap items-center justify-between gap-2">
        <p className="text-sm text-[var(--muted)]">
          Drag to pan · scroll to zoom · click a person to recenter.
        </p>
        {focusId && onOpen && (
          <button onClick={() => onOpen(focusId)} className="text-sm text-bronze hover:underline">
            Open {nameOf(focusId)} →
          </button>
        )}
      </div>
    </div>
  );
}
