"use client";

import Link from "next/link";
import { useParams, useRouter, useSearchParams } from "next/navigation";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";

import { api } from "@/lib/api/client";
import type { components } from "@/lib/api/schema";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { PersonCombobox } from "@/components/person-combobox";

type Person = components["schemas"]["PersonRead"];
type Name = components["schemas"]["NameRead"];
type Me = components["schemas"]["UserRead"];
type Event = components["schemas"]["EventRead"];
type Relationship = components["schemas"]["RelationshipRead"];
type Qualifier = components["schemas"]["ParentChildQualifier"];
type RelCreate = components["schemas"]["RelationshipCreate"];
type Source = components["schemas"]["SourceRead"];
type Citation = components["schemas"]["CitationRead"];
type CitationCreate = components["schemas"]["CitationCreate"];

const fieldCls = "h-9 rounded-md border border-[var(--border)] bg-[var(--surface)] px-2 text-sm";
const QUALIFIERS: Qualifier[] = ["biological", "adoptive", "step", "foster", "donor", "guardian"];

// Typed name vocabulary. "birth" is the maiden/birth name; "married" etc. are
// alternates. The maiden name stays primary by convention (Ancestry/FamilySearch).
const NAME_TYPES: { value: string; label: string }[] = [
  { value: "birth", label: "Birth / maiden" },
  { value: "married", label: "Married" },
  { value: "alias", label: "Also known as" },
  { value: "nickname", label: "Nickname" },
  { value: "religious", label: "Religious" },
  { value: "immigration", label: "Anglicized" },
];
const nameTypeLabel = (t: string) =>
  NAME_TYPES.find((n) => n.value === t)?.label ?? t;
const formatName = (n: Name) =>
  [n.given, n.surname].filter(Boolean).join(" ") || "—";

// Curated genealogical event vocabulary (with an escape hatch).
const EVENT_TYPES = [
  "birth", "death", "marriage", "divorce", "engagement", "baptism", "burial",
  "residence", "census", "immigration", "emigration", "occupation", "education",
  "military service", "naturalization", "other",
];
// These belong to a couple, not a person — they attach to the partnership and
// show on both partners' pages, so they're only entered once.
const PARTNERSHIP_EVENTS = ["marriage", "divorce", "engagement"];
const MONTHS = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const GED_MON = ["", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
const DATE_QUALS: Record<string, string> = { exact: "", about: "ABT", before: "BEF", after: "AFT" };
const pad = (n: number, len: number) => String(n).padStart(len, "0");

function composeDate(qual: string, day: string, month: string, year: string) {
  const y = year.trim();
  if (!y || Number.isNaN(Number(y))) {
    return { date_value: null as string | null, date_start: null as string | null, date_precision: null as string | null };
  }
  const m = month ? Number(month) : null;
  const d = day.trim() ? Number(day) : null;
  const parts: string[] = [];
  if (d && m) parts.push(String(d));
  if (m) parts.push(GED_MON[m]);
  parts.push(y);
  const prefix = DATE_QUALS[qual];
  return {
    date_value: (prefix ? `${prefix} ` : "") + parts.join(" "),
    date_start: `${pad(Number(y), 4)}-${pad(m ?? 1, 2)}-${pad(d ?? 1, 2)}`,
    date_precision: qual,
  };
}

// Parse a stored date_value (e.g. "ABT 12 MAR 1900") back into form fields.
function parseDateValue(v: string | null | undefined) {
  let qual = "exact";
  let day = "";
  let month = "";
  let year = "";
  if (v) {
    let s = v.trim();
    const up = s.toUpperCase();
    for (const [q, pre] of Object.entries(DATE_QUALS)) {
      if (pre && up.startsWith(`${pre} `)) {
        qual = q;
        s = s.slice(pre.length + 1).trim();
        break;
      }
    }
    for (const t of s.toUpperCase().split(/\s+/).filter(Boolean)) {
      if (/^\d{3,4}$/.test(t) && !year) year = t;
      else if (/^\d{1,2}$/.test(t)) day = String(Number(t));
      else {
        const mi = GED_MON.indexOf(t);
        if (mi > 0) month = String(mi);
      }
    }
  }
  return { qual, day, month, year };
}

export default function PersonDetailPage() {
  const router = useRouter();
  const params = useParams<{ id: string; personId: string }>();
  const searchParams = useSearchParams();
  const treeId = params.id;
  const personId = params.personId;
  // Where we were opened from, so "back" returns there (centered on this
  // person) instead of always dumping onto the People view's home person.
  const from = searchParams.get("from") === "people" ? "people" : "tree";
  const backHref =
    from === "people"
      ? `/trees/${treeId}?focus=${personId}`
      : `/trees/${treeId}/tree?focus=${personId}`;
  const backLabel = from === "people" ? "← Back to People" : "← Back to Tree";
  // Carry the origin through person→person links so the chain keeps its anchor.
  const personHref = (id: string) => `/trees/${treeId}/persons/${id}?from=${from}`;

  const [person, setPerson] = useState<Person | null>(null);
  const [people, setPeople] = useState<Person[]>([]);
  const [names, setNames] = useState<Name[]>([]);
  const [me, setMe] = useState<Me | null>(null);
  const [tree, setTree] = useState<components["schemas"]["TreeRead"] | null>(null);
  const [events, setEvents] = useState<Event[]>([]);
  const [rels, setRels] = useState<Relationship[]>([]);
  const [sources, setSources] = useState<Source[]>([]);
  const [citations, setCitations] = useState<Citation[]>([]);
  const [media, setMedia] = useState<components["schemas"]["MediaRead"][]>([]);
  const [uploadingMedia, setUploadingMedia] = useState(false);
  const mediaFileRef = useRef<HTMLInputElement>(null);
  const [ready, setReady] = useState(false);

  const [evType, setEvType] = useState("birth");
  const [evTypeOther, setEvTypeOther] = useState("");
  const [evSpouse, setEvSpouse] = useState(""); // partner for a partnership event
  const [allEvents, setAllEvents] = useState<Event[]>([]); // tree-wide, for partnership events
  const [dateQual, setDateQual] = useState("exact");
  const [dateDay, setDateDay] = useState("");
  const [dateMonth, setDateMonth] = useState("");
  const [dateYear, setDateYear] = useState("");

  // Inline edit-event form.
  const [editId, setEditId] = useState<string | null>(null);
  const [edType, setEdType] = useState("birth");
  const [edTypeOther, setEdTypeOther] = useState("");
  const [edQual, setEdQual] = useState("exact");
  const [edDay, setEdDay] = useState("");
  const [edMonth, setEdMonth] = useState("");
  const [edYear, setEdYear] = useState("");

  // Inline edit-person form (name + vitals).
  const [editingPerson, setEditingPerson] = useState(false);
  const [pGiven, setPGiven] = useState("");
  const [pSurname, setPSurname] = useState("");
  const [pGender, setPGender] = useState("");
  const [pLiving, setPLiving] = useState("unknown");
  const [pPrivacy, setPPrivacy] = useState<"inherit" | "private" | "public">("inherit");

  const [relKind, setRelKind] = useState<"parent" | "child" | "partner" | "sibling">("parent");
  const [relOther, setRelOther] = useState("");
  const [relQual, setRelQual] = useState<Qualifier>("biological");
  const [relErr, setRelErr] = useState<string | null>(null);

  // Add-name form + inline edit.
  const [nameType, setNameType] = useState("married");
  const [nGiven, setNGiven] = useState("");
  const [nSurname, setNSurname] = useState("");
  const [editNameId, setEditNameId] = useState<string | null>(null);
  const [enType, setEnType] = useState("married");
  const [enGiven, setEnGiven] = useState("");
  const [enSurname, setEnSurname] = useState("");

  // Delete confirmation (with optional cascade to descendants).
  const [confirmingDelete, setConfirmingDelete] = useState(false);

  // Inline citation form: which fact is being cited ("p" = person, `e:<id>`).
  const [citeFor, setCiteFor] = useState<string | null>(null);
  const [citeSource, setCiteSource] = useState("");
  const [citePage, setCitePage] = useState("");

  const load = useCallback(async () => {
    const p = await api.GET("/api/v1/trees/{tree_id}/persons/{person_id}", {
      params: { path: { tree_id: treeId, person_id: personId } },
    });
    if (p.response.status === 401) {
      router.push("/login");
      return;
    }
    setPerson(p.data ?? null);
    const [all, nm, mine, tr, ev, rl, src, cit, evAll, med] = await Promise.all([
      api.GET("/api/v1/trees/{tree_id}/persons", { params: { path: { tree_id: treeId } } }),
      api.GET("/api/v1/trees/{tree_id}/persons/{person_id}/names", {
        params: { path: { tree_id: treeId, person_id: personId } },
      }),
      api.GET("/api/v1/users/me"),
      api.GET("/api/v1/trees/{tree_id}", { params: { path: { tree_id: treeId } } }),
      api.GET("/api/v1/trees/{tree_id}/persons/{person_id}/events", {
        params: { path: { tree_id: treeId, person_id: personId } },
      }),
      api.GET("/api/v1/trees/{tree_id}/persons/{person_id}/relationships", {
        params: { path: { tree_id: treeId, person_id: personId } },
      }),
      api.GET("/api/v1/trees/{tree_id}/sources", { params: { path: { tree_id: treeId } } }),
      api.GET("/api/v1/trees/{tree_id}/citations", { params: { path: { tree_id: treeId } } }),
      api.GET("/api/v1/trees/{tree_id}/events", { params: { path: { tree_id: treeId } } }),
      api.GET("/api/v1/trees/{tree_id}/media", { params: { path: { tree_id: treeId } } }),
    ]);
    setPeople(all.data ?? []);
    setNames(nm.data ?? []);
    setMe(mine.data ?? null);
    setTree(tr.data ?? null);
    setEvents(ev.data ?? []);
    setAllEvents(evAll.data ?? []);
    setMedia(med.data ?? []);
    setRels(rl.data ?? []);
    setSources(src.data ?? []);
    setCitations(cit.data ?? []);
    setReady(true);
  }, [router, treeId, personId]);

  useEffect(() => {
    load();
  }, [load]);

  const nameOf = useMemo(() => {
    const m = new Map(people.map((p) => [p.id, p.primary_name ?? "Unnamed"]));
    return (id: string) => m.get(id) ?? "Unknown";
  }, [people]);
  const sourceName = useMemo(() => {
    const m = new Map(sources.map((s) => [s.id, s.title]));
    return (id: string) => m.get(id) ?? "source";
  }, [sources]);

  const others = people.filter((p) => p.id !== personId);
  const parents = rels.filter((r) => r.type === "parent_child" && r.person_to_id === personId);
  const children = rels.filter((r) => r.type === "parent_child" && r.person_from_id === personId);
  const partners = rels.filter((r) => r.type === "partnership");
  const siblings = rels.filter((r) => r.type === "sibling");
  const eventCites = (id: string) => citations.filter((c) => c.event_id === id);
  const personCites = citations.filter((c) => c.person_id === personId);

  // Partnership events live on the relationship and show on both partners.
  const myPartnerRels = rels.filter(
    (r) => r.type === "partnership" && (r.person_from_id === personId || r.person_to_id === personId),
  );
  const myPartnerRelIds = new Set(myPartnerRels.map((r) => r.id));
  const relEvents = allEvents.filter(
    (e) => e.relationship_id && myPartnerRelIds.has(e.relationship_id),
  );
  const spouseOfRelEvent = (relId: string | null | undefined) => {
    const r = myPartnerRels.find((x) => x.id === relId);
    if (!r) return null;
    return r.person_from_id === personId ? r.person_to_id : r.person_from_id;
  };
  const isPartnershipType = (t: string) => PARTNERSHIP_EVENTS.includes(t);
  // Personal events + this person's partnership events, shown together.
  const shownEvents = [...events, ...relEvents];

  async function addEvent(e: React.FormEvent) {
    e.preventDefault();
    const event_type = evType === "other" ? evTypeOther.trim() : evType;
    if (!event_type) return;
    const { date_value, date_start, date_precision } = composeDate(
      dateQual,
      dateDay,
      dateMonth,
      dateYear,
    );

    let body: components["schemas"]["EventCreate"] = {
      event_type,
      person_id: personId,
      date_value,
      date_start,
      date_precision,
    };

    // A partnership event belongs to the couple: attach it to the partnership
    // relationship (creating it if needed) so it's entered once and shows on
    // both partners.
    if (isPartnershipType(event_type)) {
      if (!evSpouse) return;
      let relId = myPartnerRels.find(
        (r) => r.person_from_id === evSpouse || r.person_to_id === evSpouse,
      )?.id;
      if (!relId) {
        const { data, error: relErr } = await api.POST(
          "/api/v1/trees/{tree_id}/relationships",
          {
            params: { path: { tree_id: treeId } },
            body: { type: "partnership", person_from_id: personId, person_to_id: evSpouse },
          },
        );
        if (relErr || !data) return;
        relId = data.id;
      }
      body = {
        event_type,
        relationship_id: relId,
        person_id: null,
        date_value,
        date_start,
        date_precision,
      };
    }

    const { error } = await api.POST("/api/v1/trees/{tree_id}/events", {
      params: { path: { tree_id: treeId } },
      body,
    });
    if (!error) {
      setDateDay("");
      setDateMonth("");
      setDateYear("");
      setDateQual("exact");
      setEvTypeOther("");
      setEvSpouse("");
      load();
    }
  }
  async function removeEvent(id: string) {
    await api.DELETE("/api/v1/trees/{tree_id}/events/{event_id}", {
      params: { path: { tree_id: treeId, event_id: id } },
    });
    load();
  }

  function startEdit(ev: Event) {
    setEditId(ev.id);
    const known = EVENT_TYPES.includes(ev.event_type);
    setEdType(known ? ev.event_type : "other");
    setEdTypeOther(known ? "" : ev.event_type);
    const parsed = parseDateValue(ev.date_value);
    setEdQual(parsed.qual);
    setEdDay(parsed.day);
    setEdMonth(parsed.month);
    setEdYear(parsed.year);
  }

  async function saveEdit() {
    if (!editId) return;
    const event_type = edType === "other" ? edTypeOther.trim() : edType;
    if (!event_type) return;
    const { date_value, date_start, date_precision } = composeDate(edQual, edDay, edMonth, edYear);
    const { error } = await api.PATCH("/api/v1/trees/{tree_id}/events/{event_id}", {
      params: { path: { tree_id: treeId, event_id: editId } },
      body: { event_type, date_value, date_start, date_precision },
    });
    if (!error) {
      setEditId(null);
      load();
    }
  }

  async function linkRelative(otherId: string): Promise<boolean> {
    let body: RelCreate;
    if (relKind === "parent") {
      body = { type: "parent_child", person_from_id: otherId, person_to_id: personId, qualifier: relQual };
    } else if (relKind === "child") {
      body = { type: "parent_child", person_from_id: personId, person_to_id: otherId, qualifier: relQual };
    } else if (relKind === "partner") {
      body = { type: "partnership", person_from_id: personId, person_to_id: otherId };
    } else {
      body = { type: "sibling", person_from_id: personId, person_to_id: otherId };
    }
    const { error } = await api.POST("/api/v1/trees/{tree_id}/relationships", {
      params: { path: { tree_id: treeId } },
      body,
    });
    return !error;
  }

  async function addRel(e: React.FormEvent) {
    e.preventDefault();
    if (!relOther) return;
    setRelErr(null);
    if (await linkRelative(relOther)) {
      setRelOther("");
      load();
    } else {
      setRelErr("They're already linked that way.");
    }
  }

  // Create a brand-new person, link them in the chosen role, then jump to their
  // page so the user can fill in details immediately.
  async function createRelativeAndGo(name: string) {
    const toks = name.trim().split(/\s+/).filter(Boolean);
    const given = toks.length > 1 ? toks.slice(0, -1).join(" ") : toks[0] ?? name.trim();
    const surname = toks.length > 1 ? toks[toks.length - 1] : null;
    const { data } = await api.POST("/api/v1/trees/{tree_id}/persons", {
      params: { path: { tree_id: treeId } },
      body: { given, surname },
    });
    if (!data) return;
    await linkRelative(data.id);
    router.push(personHref(data.id));
  }
  async function removeRel(id: string) {
    await api.DELETE("/api/v1/trees/{tree_id}/relationships/{relationship_id}", {
      params: { path: { tree_id: treeId, relationship_id: id } },
    });
    load();
  }

  async function addCitation(target: Partial<CitationCreate>) {
    if (!citeSource) return;
    const body: CitationCreate = { source_id: citeSource, page: citePage || null, ...target };
    const { error } = await api.POST("/api/v1/trees/{tree_id}/citations", {
      params: { path: { tree_id: treeId } },
      body,
    });
    if (!error) {
      setCiteFor(null);
      setCiteSource("");
      setCitePage("");
      load();
    }
  }
  async function removeCitation(id: string) {
    await api.DELETE("/api/v1/trees/{tree_id}/citations/{citation_id}", {
      params: { path: { tree_id: treeId, citation_id: id } },
    });
    load();
  }

  async function removePerson(cascade: boolean) {
    await api.DELETE("/api/v1/trees/{tree_id}/persons/{person_id}", {
      params: { path: { tree_id: treeId, person_id: personId }, query: { cascade } },
    });
    router.push(`/trees/${treeId}`);
  }

  async function addName(e: React.FormEvent) {
    e.preventDefault();
    if (!nGiven.trim() && !nSurname.trim()) return;
    const { error } = await api.POST("/api/v1/trees/{tree_id}/persons/{person_id}/names", {
      params: { path: { tree_id: treeId, person_id: personId } },
      body: { name_type: nameType, given: nGiven || null, surname: nSurname || null },
    });
    if (!error) {
      setNGiven("");
      setNSurname("");
      setNameType("married");
      load();
    }
  }

  function startEditName(n: Name) {
    setEditNameId(n.id);
    setEnType(n.name_type);
    setEnGiven(n.given ?? "");
    setEnSurname(n.surname ?? "");
  }

  async function saveName() {
    if (!editNameId) return;
    const { error } = await api.PATCH(
      "/api/v1/trees/{tree_id}/persons/{person_id}/names/{name_id}",
      {
        params: { path: { tree_id: treeId, person_id: personId, name_id: editNameId } },
        body: { name_type: enType, given: enGiven || null, surname: enSurname || null },
      },
    );
    if (!error) {
      setEditNameId(null);
      load();
    }
  }

  async function makePrimaryName(id: string) {
    await api.PATCH("/api/v1/trees/{tree_id}/persons/{person_id}/names/{name_id}", {
      params: { path: { tree_id: treeId, person_id: personId, name_id: id } },
      body: { is_primary: true },
    });
    load();
  }

  async function removeName(id: string) {
    await api.DELETE("/api/v1/trees/{tree_id}/persons/{person_id}/names/{name_id}", {
      params: { path: { tree_id: treeId, person_id: personId, name_id: id } },
    });
    load();
  }

  async function setSelf(link: boolean) {
    await api.PATCH("/api/v1/users/me/self-person", {
      body: { self_person_id: link ? personId : null },
    });
    load();
  }

  async function setDefaultPerson(make: boolean) {
    await api.PATCH("/api/v1/trees/{tree_id}", {
      params: { path: { tree_id: treeId } },
      body: { home_person_id: make ? personId : null },
    });
    load();
  }

  async function uploadMediaForPerson(e: React.ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    if (mediaFileRef.current) mediaFileRef.current.value = "";
    if (!file) return;
    setUploadingMedia(true);
    const fd = new FormData();
    fd.append("file", file);
    fd.append("person_id", personId); // link on upload
    await fetch(`/api/v1/trees/${treeId}/media`, {
      method: "POST",
      body: fd,
      credentials: "include",
    });
    setUploadingMedia(false);
    load();
  }

  async function linkMedia(mediaId: string, link: boolean) {
    await api.PATCH("/api/v1/trees/{tree_id}/media/{media_id}", {
      params: { path: { tree_id: treeId, media_id: mediaId } },
      body: { person_id: link ? personId : null },
    });
    load();
  }

  function startEditPerson(current: Person) {
    const t = (current.primary_name ?? "").trim().split(/\s+/).filter(Boolean);
    setPGiven(t.length > 1 ? t.slice(0, -1).join(" ") : (t[0] ?? ""));
    setPSurname(t.length > 1 ? t[t.length - 1] : "");
    setPGender(current.gender ?? "");
    setPLiving(current.is_living === true ? "living" : current.is_living === false ? "deceased" : "unknown");
    setPPrivacy((current.privacy as "inherit" | "private" | "public") ?? "inherit");
    setEditingPerson(true);
  }

  async function savePerson() {
    const { error } = await api.PATCH("/api/v1/trees/{tree_id}/persons/{person_id}", {
      params: { path: { tree_id: treeId, person_id: personId } },
      body: {
        given: pGiven || null,
        surname: pSurname || null,
        gender: pGender || null,
        is_living: pLiving === "living" ? true : pLiving === "deceased" ? false : null,
        privacy: pPrivacy,
      },
    });
    if (!error) {
      setEditingPerson(false);
      load();
    }
  }

  if (!ready) return <p className="text-[var(--muted)]">Loading…</p>;
  if (!person) return <p className="text-[var(--muted)]">Not found.</p>;

  const isSelf = me?.self_person_id === personId;
  const isDefault = tree?.home_person_id === personId;

  // Inline "cite" control: a badge with count, a toggle, and the picker form.
  function citeControl(key: string, target: Partial<CitationCreate>, cites: Citation[]) {
    return (
      <span className="inline-flex items-center gap-2">
        {cites.length > 0 && (
          <span
            className="rounded bg-bronze/15 px-1.5 py-0.5 text-xs text-bronze"
            title={cites.map((c) => sourceName(c.source_id)).join(", ")}
          >
            ✓ {cites.length} sourced
          </span>
        )}
        {citeFor === key ? (
          <form
            onSubmit={(e) => {
              e.preventDefault();
              addCitation(target);
            }}
            className="inline-flex items-center gap-1"
          >
            <select
              className={fieldCls}
              value={citeSource}
              onChange={(e) => setCiteSource(e.target.value)}
            >
              <option value="">— source —</option>
              {sources.map((s) => (
                <option key={s.id} value={s.id}>
                  {s.title}
                </option>
              ))}
            </select>
            <input
              className={`${fieldCls} w-24`}
              placeholder="page"
              value={citePage}
              onChange={(e) => setCitePage(e.target.value)}
            />
            <Button type="submit" size="sm">
              cite
            </Button>
            <button
              type="button"
              onClick={() => setCiteFor(null)}
              className="text-xs text-[var(--muted)]"
            >
              cancel
            </button>
          </form>
        ) : sources.length === 0 ? (
          <Link href={`/trees/${treeId}/sources`} className="text-xs text-[var(--muted)] hover:underline">
            + add a source first
          </Link>
        ) : (
          <button
            type="button"
            onClick={() => {
              setCiteFor(key);
              setCiteSource("");
              setCitePage("");
            }}
            className="text-xs text-bronze hover:underline"
          >
            + cite
          </button>
        )}
      </span>
    );
  }

  const relGroup = (label: string, items: Relationship[], otherId: (r: Relationship) => string) =>
    items.length > 0 && (
      <div>
        <h3 className="text-sm font-semibold text-bronze">{label}</h3>
        <ul className="mt-1 space-y-1">
          {items.map((r) => (
            <li key={r.id} className="flex items-center justify-between text-sm">
              <Link href={personHref(otherId(r))} className="hover:underline">
                {nameOf(otherId(r))}
                {r.qualifier ? <span className="text-[var(--muted)]"> · {r.qualifier}</span> : null}
              </Link>
              <button
                onClick={() => removeRel(r.id)}
                className="text-[var(--muted)] hover:text-bronze"
                aria-label="Remove"
              >
                ×
              </button>
            </li>
          ))}
        </ul>
      </div>
    );

  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between gap-3">
        <Link href={backHref} className="text-sm text-[var(--muted)] hover:underline">
          {backLabel}
        </Link>
        <Link
          href={`/trees/${treeId}/tree?focus=${personId}`}
          className="text-sm text-bronze hover:underline"
        >
          View in tree →
        </Link>
      </div>

      {editingPerson ? (
        <form
          onSubmit={(e) => {
            e.preventDefault();
            savePerson();
          }}
          className="space-y-3 rounded-lg border border-[var(--border)] p-4"
        >
          <div className="flex flex-wrap gap-2">
            <Input className="w-40" placeholder="Given name" value={pGiven} onChange={(e) => setPGiven(e.target.value)} />
            <Input className="w-40" placeholder="Surname" value={pSurname} onChange={(e) => setPSurname(e.target.value)} />
            <select className={fieldCls} value={pGender} onChange={(e) => setPGender(e.target.value)}>
              <option value="">Gender: —</option>
              <option value="male">Male</option>
              <option value="female">Female</option>
            </select>
            <select className={fieldCls} value={pLiving} onChange={(e) => setPLiving(e.target.value)}>
              <option value="unknown">Status: unknown</option>
              <option value="living">Living</option>
              <option value="deceased">Deceased</option>
            </select>
            <select
              className={fieldCls}
              value={pPrivacy}
              onChange={(e) => setPPrivacy(e.target.value as "inherit" | "private" | "public")}
            >
              <option value="inherit">Privacy: default</option>
              <option value="private">Private</option>
              <option value="public">Public</option>
            </select>
          </div>
          <div className="flex gap-2">
            <Button type="submit" size="sm">
              Save
            </Button>
            <button type="button" onClick={() => setEditingPerson(false)} className="text-xs text-[var(--muted)]">
              cancel
            </button>
          </div>
        </form>
      ) : (
        <div className="flex flex-wrap items-center justify-between gap-2">
          <h1 className="flex flex-wrap items-center gap-3 text-3xl font-semibold">
            <span className="inline-flex items-center gap-2">
              {person.primary_name ?? "Unnamed person"}
              {person.gender === "male" && (
                <span title="Male" aria-label="Male" style={{ color: "rgb(120, 159, 172)" }}>
                  ♂
                </span>
              )}
              {person.gender === "female" && (
                <span title="Female" aria-label="Female" style={{ color: "rgb(196, 138, 146)" }}>
                  ♀
                </span>
              )}
            </span>
            {isSelf && (
              <span className="rounded-full bg-bronze/15 px-2.5 py-1 text-xs font-medium text-bronze">
                This is you
              </span>
            )}
            {isDefault && (
              <span className="rounded-full border border-bronze/40 px-2.5 py-1 text-xs font-medium text-bronze">
                Default person
              </span>
            )}
          </h1>
          <div className="flex flex-wrap items-center gap-3">
            {citeControl("p", { person_id: personId }, personCites)}
            {isSelf ? (
              <Button variant="ghost" size="sm" onClick={() => setSelf(false)}>
                Unlink me
              </Button>
            ) : (
              <Button variant="outline" size="sm" onClick={() => setSelf(true)}>
                This is me
              </Button>
            )}
            {!isDefault && (
              <Button variant="outline" size="sm" onClick={() => setDefaultPerson(true)}>
                Set as default
              </Button>
            )}
            <Button variant="outline" size="sm" onClick={() => startEditPerson(person)}>
              Edit
            </Button>
            <Button variant="ghost" size="sm" onClick={() => setConfirmingDelete(true)}>
              Delete
            </Button>
          </div>
        </div>
      )}

      {confirmingDelete && (
        <div className="space-y-3 rounded-lg border border-bronze/40 bg-bronze/[0.05] p-4">
          <p className="text-sm">
            Delete <strong>{person.primary_name ?? "this person"}</strong>? Their relationships
            will be removed too. This can be undone from Recovery.
          </p>
          <div className="flex flex-wrap gap-2">
            <Button variant="ghost" size="sm" onClick={() => removePerson(false)}>
              Delete only this person
            </Button>
            <Button variant="outline" size="sm" onClick={() => removePerson(true)}>
              Delete with all descendants
            </Button>
            <button
              type="button"
              onClick={() => setConfirmingDelete(false)}
              className="text-xs text-[var(--muted)]"
            >
              cancel
            </button>
          </div>
        </div>
      )}

      <Card>
        <CardHeader>
          <CardTitle className="text-base">Names</CardTitle>
        </CardHeader>
        <CardContent className="space-y-4">
          {names.length === 0 ? (
            <p className="text-sm text-[var(--muted)]">No names yet.</p>
          ) : (
            <ul className="space-y-2">
              {names.map((n) =>
                editNameId === n.id ? (
                  <li key={n.id}>
                    <form
                      onSubmit={(e) => {
                        e.preventDefault();
                        saveName();
                      }}
                      className="flex flex-wrap items-center gap-2"
                    >
                      <select
                        className={fieldCls}
                        value={enType}
                        onChange={(e) => setEnType(e.target.value)}
                      >
                        {NAME_TYPES.map((t) => (
                          <option key={t.value} value={t.value}>
                            {t.label}
                          </option>
                        ))}
                      </select>
                      <Input
                        className="h-9 w-36"
                        placeholder="Given"
                        value={enGiven}
                        onChange={(e) => setEnGiven(e.target.value)}
                      />
                      <Input
                        className="h-9 w-36"
                        placeholder="Surname"
                        value={enSurname}
                        onChange={(e) => setEnSurname(e.target.value)}
                      />
                      <Button type="submit" size="sm">
                        Save
                      </Button>
                      <button
                        type="button"
                        onClick={() => setEditNameId(null)}
                        className="text-xs text-[var(--muted)]"
                      >
                        cancel
                      </button>
                    </form>
                  </li>
                ) : (
                  <li
                    key={n.id}
                    className="flex flex-wrap items-center justify-between gap-2 text-sm"
                  >
                    <span className="flex items-center gap-2">
                      <span className="font-medium">{formatName(n)}</span>
                      <span className="rounded bg-[var(--border)]/50 px-1.5 py-0.5 text-xs text-[var(--muted)]">
                        {nameTypeLabel(n.name_type)}
                      </span>
                      {n.is_primary && (
                        <span className="rounded bg-bronze/15 px-1.5 py-0.5 text-xs text-bronze">
                          primary
                        </span>
                      )}
                    </span>
                    <span className="flex items-center gap-3">
                      {!n.is_primary && (
                        <button
                          onClick={() => makePrimaryName(n.id)}
                          className="text-xs text-bronze hover:underline"
                        >
                          make primary
                        </button>
                      )}
                      <button
                        onClick={() => startEditName(n)}
                        className="text-xs text-bronze hover:underline"
                      >
                        edit
                      </button>
                      <button
                        onClick={() => removeName(n.id)}
                        className="text-[var(--muted)] hover:text-bronze"
                        aria-label="Remove"
                      >
                        ×
                      </button>
                    </span>
                  </li>
                ),
              )}
            </ul>
          )}
          <form onSubmit={addName} className="flex flex-wrap items-end gap-2">
            <label className="flex flex-col gap-1">
              <span className="text-xs text-[var(--muted)]">Type</span>
              <select
                className={fieldCls}
                value={nameType}
                onChange={(e) => setNameType(e.target.value)}
              >
                {NAME_TYPES.map((t) => (
                  <option key={t.value} value={t.value}>
                    {t.label}
                  </option>
                ))}
              </select>
            </label>
            <label className="flex flex-col gap-1">
              <span className="text-xs text-[var(--muted)]">Given</span>
              <Input
                className="h-9 w-36"
                placeholder="Given"
                value={nGiven}
                onChange={(e) => setNGiven(e.target.value)}
              />
            </label>
            <label className="flex flex-col gap-1">
              <span className="text-xs text-[var(--muted)]">Surname</span>
              <Input
                className="h-9 w-36"
                placeholder="Surname"
                value={nSurname}
                onChange={(e) => setNSurname(e.target.value)}
              />
            </label>
            <Button type="submit">Add name</Button>
          </form>
        </CardContent>
      </Card>

      <Card>
        <CardHeader>
          <CardTitle className="text-base">Life events</CardTitle>
        </CardHeader>
        <CardContent className="space-y-4">
          {shownEvents.length === 0 ? (
            <p className="text-sm text-[var(--muted)]">No events yet.</p>
          ) : (
            <ul className="space-y-2">
              {shownEvents.map((ev) =>
                editId === ev.id ? (
                  <li key={ev.id}>
                    <form
                      onSubmit={(e) => {
                        e.preventDefault();
                        saveEdit();
                      }}
                      className="flex flex-wrap items-end gap-2"
                    >
                      <select
                        className={`${fieldCls} capitalize`}
                        value={edType}
                        onChange={(e) => setEdType(e.target.value)}
                      >
                        {EVENT_TYPES.map((t) => (
                          <option key={t} value={t} className="capitalize">
                            {t}
                          </option>
                        ))}
                      </select>
                      {edType === "other" && (
                        <Input
                          className="h-9 w-32"
                          placeholder="Custom"
                          value={edTypeOther}
                          onChange={(e) => setEdTypeOther(e.target.value)}
                        />
                      )}
                      <select className={fieldCls} value={edQual} onChange={(e) => setEdQual(e.target.value)}>
                        <option value="exact">on</option>
                        <option value="about">about</option>
                        <option value="before">before</option>
                        <option value="after">after</option>
                      </select>
                      <input
                        className={`${fieldCls} w-14`}
                        inputMode="numeric"
                        placeholder="Day"
                        value={edDay}
                        onChange={(e) => setEdDay(e.target.value)}
                      />
                      <select className={fieldCls} value={edMonth} onChange={(e) => setEdMonth(e.target.value)}>
                        <option value="">—</option>
                        {MONTHS.map((m, i) => (i > 0 ? <option key={i} value={i}>{m}</option> : null))}
                      </select>
                      <input
                        className={`${fieldCls} w-20`}
                        inputMode="numeric"
                        placeholder="Year"
                        value={edYear}
                        onChange={(e) => setEdYear(e.target.value)}
                      />
                      <Button type="submit" size="sm">
                        Save
                      </Button>
                      <button
                        type="button"
                        onClick={() => setEditId(null)}
                        className="text-xs text-[var(--muted)]"
                      >
                        cancel
                      </button>
                    </form>
                  </li>
                ) : (
                  <li key={ev.id} className="flex flex-wrap items-center justify-between gap-2 text-sm">
                    <span>
                      <span className="font-medium capitalize">{ev.event_type}</span>
                      {ev.relationship_id ? (
                        <span className="text-[var(--muted)]">
                          {" "}
                          · with {nameOf(spouseOfRelEvent(ev.relationship_id) ?? "")}
                        </span>
                      ) : null}
                      {ev.date_value ? (
                        <span className="text-[var(--muted)]"> — {ev.date_value}</span>
                      ) : null}
                      {ev.detail ? (
                        <span className="text-[var(--muted)]"> — {ev.detail}</span>
                      ) : null}
                    </span>
                    <span className="flex items-center gap-3">
                      {citeControl(`e:${ev.id}`, { event_id: ev.id }, eventCites(ev.id))}
                      <button
                        onClick={() => startEdit(ev)}
                        className="text-xs text-bronze hover:underline"
                      >
                        edit
                      </button>
                      <button
                        onClick={() => removeEvent(ev.id)}
                        className="text-[var(--muted)] hover:text-bronze"
                        aria-label="Remove"
                      >
                        ×
                      </button>
                    </span>
                  </li>
                ),
              )}
            </ul>
          )}
          <form onSubmit={addEvent} className="flex flex-wrap items-end gap-2">
            <label className="flex flex-col gap-1">
              <span className="text-xs text-[var(--muted)]">Event</span>
              <select
                className={`${fieldCls} capitalize`}
                value={evType}
                onChange={(e) => setEvType(e.target.value)}
              >
                {EVENT_TYPES.map((t) => (
                  <option key={t} value={t} className="capitalize">
                    {t}
                  </option>
                ))}
              </select>
            </label>
            {evType === "other" && (
              <label className="flex flex-col gap-1">
                <span className="text-xs text-[var(--muted)]">Type</span>
                <Input
                  className="h-9 w-36"
                  placeholder="Custom"
                  value={evTypeOther}
                  onChange={(e) => setEvTypeOther(e.target.value)}
                />
              </label>
            )}
            {isPartnershipType(evType) && (
              <label className="flex flex-col gap-1">
                <span className="text-xs text-[var(--muted)]">Spouse / partner</span>
                <PersonCombobox
                  people={others}
                  value={evSpouse}
                  onChange={setEvSpouse}
                  placeholder="Search for a spouse…"
                />
              </label>
            )}
            <label className="flex flex-col gap-1">
              <span className="text-xs text-[var(--muted)]">When</span>
              <select className={fieldCls} value={dateQual} onChange={(e) => setDateQual(e.target.value)}>
                <option value="exact">on</option>
                <option value="about">about</option>
                <option value="before">before</option>
                <option value="after">after</option>
              </select>
            </label>
            <label className="flex flex-col gap-1">
              <span className="text-xs text-[var(--muted)]">Day</span>
              <input
                className={`${fieldCls} w-14`}
                inputMode="numeric"
                placeholder="—"
                value={dateDay}
                onChange={(e) => setDateDay(e.target.value)}
              />
            </label>
            <label className="flex flex-col gap-1">
              <span className="text-xs text-[var(--muted)]">Month</span>
              <select className={fieldCls} value={dateMonth} onChange={(e) => setDateMonth(e.target.value)}>
                <option value="">—</option>
                {MONTHS.map((m, i) => (i > 0 ? <option key={i} value={i}>{m}</option> : null))}
              </select>
            </label>
            <label className="flex flex-col gap-1">
              <span className="text-xs text-[var(--muted)]">Year</span>
              <input
                className={`${fieldCls} w-20`}
                inputMode="numeric"
                placeholder="YYYY"
                value={dateYear}
                onChange={(e) => setDateYear(e.target.value)}
              />
            </label>
            <Button type="submit">Add event</Button>
          </form>
        </CardContent>
      </Card>

      <Card>
        <CardHeader>
          <CardTitle className="text-base">Relationships</CardTitle>
        </CardHeader>
        <CardContent className="space-y-4">
          {rels.length === 0 ? (
            <p className="text-sm text-[var(--muted)]">No relationships yet.</p>
          ) : (
            <div className="grid gap-4 sm:grid-cols-2">
              {relGroup("Parents", parents, (r) => r.person_from_id)}
              {relGroup("Children", children, (r) => r.person_to_id)}
              {relGroup("Partners", partners, (r) =>
                r.person_from_id === personId ? r.person_to_id : r.person_from_id,
              )}
              {relGroup("Siblings", siblings, (r) =>
                r.person_from_id === personId ? r.person_to_id : r.person_from_id,
              )}
            </div>
          )}

          {others.length === 0 ? (
            <p className="text-sm text-[var(--muted)]">Add more people to the tree to link them.</p>
          ) : (
            <form onSubmit={addRel} className="flex flex-wrap items-center gap-2">
              <span className="text-sm text-[var(--muted)]">Add</span>
              <select className={fieldCls} value={relKind} onChange={(e) => setRelKind(e.target.value as typeof relKind)}>
                <option value="parent">parent</option>
                <option value="child">child</option>
                <option value="partner">partner</option>
                <option value="sibling">sibling</option>
              </select>
              <PersonCombobox
                people={others}
                value={relOther}
                onChange={setRelOther}
                onCreate={createRelativeAndGo}
                placeholder="Search, or type a new name…"
              />
              {(relKind === "parent" || relKind === "child") && (
                <select className={fieldCls} value={relQual} onChange={(e) => setRelQual(e.target.value as Qualifier)}>
                  {QUALIFIERS.map((q) => (
                    <option key={q} value={q}>
                      {q}
                    </option>
                  ))}
                </select>
              )}
              <Button type="submit">Link</Button>
            </form>
          )}
          {relErr && <p className="text-sm text-red-600">{relErr}</p>}
        </CardContent>
      </Card>

      <Card>
        <CardHeader>
          <CardTitle className="text-base">Media</CardTitle>
        </CardHeader>
        <CardContent className="space-y-4">
          {(() => {
            const personMedia = media.filter((m) => m.person_id === personId);
            const unlinked = media.filter((m) => !m.person_id);
            return (
              <>
                {personMedia.length === 0 ? (
                  <p className="text-sm text-[var(--muted)]">No media linked to this person yet.</p>
                ) : (
                  <div className="grid grid-cols-2 gap-4 sm:grid-cols-3">
                    {personMedia.map((m) => (
                      <div key={m.id} className="overflow-hidden rounded-lg border border-[var(--border)]">
                        <a href={m.url ?? "#"} target="_blank" rel="noreferrer" className="block">
                          {m.content_type.startsWith("image/") ? (
                            // eslint-disable-next-line @next/next/no-img-element
                            <img
                              src={m.url ?? ""}
                              alt={m.title ?? m.original_filename}
                              className="aspect-square w-full object-cover"
                            />
                          ) : (
                            <div className="grid aspect-square w-full place-items-center bg-bronze/[0.06] font-serif text-2xl text-bronze">
                              {(m.original_filename.split(".").pop() ?? "file").toUpperCase()}
                            </div>
                          )}
                        </a>
                        <div className="flex items-center justify-between gap-2 p-2">
                          <span className="truncate text-xs" title={m.original_filename}>
                            {m.title ?? m.original_filename}
                          </span>
                          <button
                            onClick={() => linkMedia(m.id, false)}
                            className="shrink-0 text-xs text-[var(--muted)] hover:text-bronze"
                          >
                            unlink
                          </button>
                        </div>
                      </div>
                    ))}
                  </div>
                )}

                <div className="flex flex-wrap items-center gap-2">
                  <input
                    ref={mediaFileRef}
                    type="file"
                    onChange={uploadMediaForPerson}
                    className="hidden"
                  />
                  <Button
                    size="sm"
                    onClick={() => mediaFileRef.current?.click()}
                    disabled={uploadingMedia}
                  >
                    {uploadingMedia ? "Uploading…" : "Upload & link"}
                  </Button>
                  {unlinked.length > 0 && (
                    <select
                      className={fieldCls}
                      defaultValue=""
                      onChange={(e) => e.target.value && linkMedia(e.target.value, true)}
                    >
                      <option value="">Link existing media…</option>
                      {unlinked.map((m) => (
                        <option key={m.id} value={m.id}>
                          {m.title ?? m.original_filename}
                        </option>
                      ))}
                    </select>
                  )}
                </div>
              </>
            );
          })()}
        </CardContent>
      </Card>
    </div>
  );
}
