// Client-side store holding the analyzer outputs (taxonomy, glossary terms,
// requirements, findings, last-run metadata). Sidebar reads from here for
// counts/timestamps; panes read for full content. Refetches on demand.

"use client";

import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import type { SectionPaneId } from "./openPanesStore";

export type ClientTermStatus = "accepted" | "suggested" | "deprecated";

export interface ClientTerm {
  id: string;
  parentId: string | null;
  label: string;
  definition: string | null;
  synonyms: string[];
  linkedBlockId: string | null;
  /// Review state: "accepted" surfaces normally, "suggested" / "deprecated"
  /// are pending the user's keep/discard decision.
  status: ClientTermStatus;
  pinned: boolean;
  /// True when the user authored / edited the definition by hand. Future
  /// Analyze runs skip this term's definition while the flag is on.
  definitionPinned: boolean;
}

export type ReviewStatus = "accepted" | "suggested" | "deprecated";

export interface ClientRequirement {
  id: string;
  tag: string;
  text: string;
  tracedToIds: string[];
  unsupported: boolean;
  /// Term this requirement is conceptually about, if any. Powers the back-link
  /// from a requirement row → TermDetail.
  linkedTermId: string | null;
  status: ReviewStatus;
  pinned: boolean;
}

export interface ClientFinding {
  id: string;
  kind: "assumption" | "risk" | "inconsistency";
  text: string;
  linkedElementIds: string[];
  confidence: number;
  severity: string | null;
  validationCode: string | null;
  /// "suggested" | "accepted" | "deprecated" | "dismissed" | "resolved" |
  /// (legacy) "open". Visible-only set is fetched from the API; dismissed and
  /// resolved are filtered server-side.
  status: string;
  pinned: boolean;
}

export interface ClientRun {
  status: "running" | "succeeded" | "failed";
  startedAt: string;
  finishedAt: string | null;
  errorMessage: string | null;
}

// All sections map 1:1 to server sections after T3 (taxonomy + glossary
// collapsed into "concepts"). Legacy values "taxonomy" / "glossary" still
// work because the server normalizes them.
export type AnalyzeSection = SectionPaneId | "all" | "taxonomy" | "glossary";

interface AnalysisStoreValue {
  terms: ClientTerm[];
  requirements: ClientRequirement[];
  findings: ClientFinding[];
  runs: Partial<Record<string, ClientRun>>;
  inFlight: Set<AnalyzeSection>;
  refresh(): Promise<void>;
  analyze(section: AnalyzeSection): Promise<void>;
  /** Lookup helper used by TermDetail. */
  getTerm(termId: string): ClientTerm | null;
  /** Apply a user decision to a pending term. Optimistically updates local
   *  state; on server error refreshes to recover. */
  decideTerm(termId: string, decision: "keep" | "discard"): Promise<void>;
  /** Set a term's definition by user action. Empty string clears it AND
   *  clears the pin (future Analyze fills it again from prose). */
  setTermDefinition(termId: string, definition: string | null): Promise<void>;
  decideRequirement(reqId: string, decision: "keep" | "discard"): Promise<void>;
  /** Findings support an extra "resolve" decision (semantically distinct from
   *  "dismissed" — same effect on visibility, but signals "I fixed it"). */
  decideFinding(findingId: string, decision: "keep" | "discard" | "resolve" | "restore"): Promise<void>;
}

const Ctx = createContext<AnalysisStoreValue | null>(null);

interface ProviderProps {
  projectId: string;
  children: React.ReactNode;
}

export function AnalysisStoreProvider({ projectId, children }: ProviderProps) {
  const [terms, setTerms] = useState<ClientTerm[]>([]);
  const [requirements, setRequirements] = useState<ClientRequirement[]>([]);
  const [findings, setFindings] = useState<ClientFinding[]>([]);
  const [runs, setRuns] = useState<Partial<Record<string, ClientRun>>>({});
  const [inFlight, setInFlight] = useState<Set<AnalyzeSection>>(new Set());

  const refresh = useCallback(async () => {
    try {
      const [t, r, f, runsRes] = await Promise.all([
        fetch(`/api/projects/${encodeURIComponent(projectId)}/taxonomy`).then(x => (x.ok ? x.json() : { terms: [] })),
        fetch(`/api/projects/${encodeURIComponent(projectId)}/requirements`).then(x => (x.ok ? x.json() : { requirements: [] })),
        fetch(`/api/projects/${encodeURIComponent(projectId)}/findings?include=all`).then(x => (x.ok ? x.json() : { findings: [] })),
        fetch(`/api/projects/${encodeURIComponent(projectId)}/runs`).then(x => (x.ok ? x.json() : { runs: {} })),
      ]);
      setTerms((t.terms as ClientTerm[]) ?? []);
      setRequirements((r.requirements as ClientRequirement[]) ?? []);
      setFindings((f.findings as ClientFinding[]) ?? []);
      setRuns((runsRes.runs as Partial<Record<string, ClientRun>>) ?? {});
    } catch (err) {
      console.error("[analysisStore] refresh failed:", err);
    }
  }, [projectId]);

  const analyze = useCallback(
    async (section: AnalyzeSection) => {
      setInFlight(prev => {
        const next = new Set(prev);
        next.add(section);
        return next;
      });
      try {
        const url =
          `/api/projects/${encodeURIComponent(projectId)}/analyze` +
          (section === "all" ? "" : `?section=${section}`);
        const res = await fetch(url, { method: "POST" });
        if (!res.ok) {
          const body = await res.text();
          console.error("[analyze] HTTP", res.status, body.slice(0, 200));
        }
        await refresh();
      } catch (err) {
        console.error("[analyze] failed:", err);
      } finally {
        setInFlight(prev => {
          const next = new Set(prev);
          next.delete(section);
          return next;
        });
      }
    },
    [projectId, refresh]
  );

  const getTerm = useCallback(
    (termId: string): ClientTerm | null => terms.find(t => t.id === termId) ?? null,
    [terms]
  );

  const decideRequirement = useCallback(
    async (reqId: string, decision: "keep" | "discard") => {
      setRequirements(prev => {
        if (decision === "discard") return prev.filter(r => r.id !== reqId);
        return prev.map(r =>
          r.id === reqId
            ? {
                ...r,
                status: "accepted" as ReviewStatus,
                pinned: r.status === "deprecated" ? true : r.pinned,
              }
            : r
        );
      });
      try {
        const res = await fetch(
          `/api/projects/${encodeURIComponent(projectId)}/requirements/${encodeURIComponent(reqId)}/decision`,
          {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ decision }),
          }
        );
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
      } catch (err) {
        console.error("[analysisStore] decideRequirement failed:", err);
        await refresh();
      }
    },
    [projectId, refresh]
  );

  const decideFinding = useCallback(
    async (findingId: string, decision: "keep" | "discard" | "resolve" | "restore") => {
      // Optimistic update by terminal status:
      //   keep / restore → "accepted" (visible in Kept list)
      //   discard        → "dismissed" (visible in Discarded drawer)
      //   resolve        → "resolved"  (visible in Discarded drawer)
      const nextStatus =
        decision === "keep" || decision === "restore"
          ? "accepted"
          : decision === "discard"
            ? "dismissed"
            : "resolved";
      setFindings(prev =>
        prev.map(f =>
          f.id === findingId
            ? {
                ...f,
                status: nextStatus,
                pinned:
                  decision === "keep" && f.status === "deprecated" ? true : f.pinned,
              }
            : f
        )
      );
      try {
        const res = await fetch(
          `/api/projects/${encodeURIComponent(projectId)}/findings/${encodeURIComponent(findingId)}/decision`,
          {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ decision }),
          }
        );
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
      } catch (err) {
        console.error("[analysisStore] decideFinding failed:", err);
        await refresh();
      }
    },
    [projectId, refresh]
  );

  const setTermDefinition = useCallback(
    async (termId: string, definition: string | null) => {
      const trimmed = (definition ?? "").trim();
      // Optimistic update: pin when non-empty, clear pin when empty.
      setTerms(prev =>
        prev.map(t =>
          t.id === termId
            ? {
                ...t,
                definition: trimmed.length > 0 ? trimmed : null,
                definitionPinned: trimmed.length > 0,
              }
            : t
        )
      );
      try {
        const res = await fetch(
          `/api/projects/${encodeURIComponent(projectId)}/terms/${encodeURIComponent(termId)}/definition`,
          {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ definition: trimmed.length > 0 ? trimmed : null }),
          }
        );
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
      } catch (err) {
        console.error("[analysisStore] setTermDefinition failed:", err);
        await refresh();
      }
    },
    [projectId, refresh]
  );

  const decideTerm = useCallback(
    async (termId: string, decision: "keep" | "discard") => {
      // Optimistic update: keep → accepted (+ pinned if previously deprecated);
      // discard → drop from local state.
      setTerms(prev => {
        if (decision === "discard") return prev.filter(t => t.id !== termId);
        return prev.map(t =>
          t.id === termId
            ? {
                ...t,
                status: "accepted" as ClientTermStatus,
                pinned: t.status === "deprecated" ? true : t.pinned,
              }
            : t
        );
      });
      try {
        const res = await fetch(
          `/api/projects/${encodeURIComponent(projectId)}/terms/${encodeURIComponent(termId)}/decision`,
          {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ decision }),
          }
        );
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
      } catch (err) {
        console.error("[analysisStore] decideTerm failed:", err);
        await refresh();
      }
    },
    [projectId, refresh]
  );

  useEffect(() => {
    void refresh();
  }, [refresh]);

  // First-load auto-analyze: if the project has a non-empty document but
  // nothing has been analyzed yet (no runs of any kind), kick off a full
  // Analyze in the background so the editor lands populated. Matches the
  // seed-finalize UX for the demo project and any imported docs.
  const bootstrappedRef = useRef(false);
  useEffect(() => {
    if (bootstrappedRef.current) return;
    if (Object.keys(runs).length > 0) return;
    if (terms.length > 0 || requirements.length > 0 || findings.length > 0) return;
    let cancelled = false;
    (async () => {
      try {
        const res = await fetch(`/api/projects/${encodeURIComponent(projectId)}/document`);
        if (!res.ok) return;
        const body = (await res.json()) as { doc?: unknown };
        const hasDoc = !!body.doc;
        if (!hasDoc || cancelled || bootstrappedRef.current) return;
        bootstrappedRef.current = true;
        void analyze("all");
      } catch {
        /* ignore */
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [projectId, runs, terms.length, requirements.length, findings.length, analyze]);

  // Poll while any AnalysisRun is still "running" (e.g. the seed-finalize
  // background pipeline). Stop polling once everything has settled.
  useEffect(() => {
    const anyRunning = Object.values(runs).some(r => r?.status === "running");
    if (!anyRunning) return;
    const handle = setInterval(() => void refresh(), 3000);
    return () => clearInterval(handle);
  }, [runs, refresh]);

  const value = useMemo<AnalysisStoreValue>(
    () => ({
      terms,
      requirements,
      findings,
      runs,
      inFlight,
      refresh,
      analyze,
      getTerm,
      decideTerm,
      decideRequirement,
      decideFinding,
      setTermDefinition,
    }),
    [
      terms,
      requirements,
      findings,
      runs,
      inFlight,
      refresh,
      analyze,
      getTerm,
      decideTerm,
      decideRequirement,
      decideFinding,
      setTermDefinition,
    ]
  );

  return <Ctx.Provider value={value}>{children}</Ctx.Provider>;
}

export function useAnalysis(): AnalysisStoreValue {
  const ctx = useContext(Ctx);
  if (!ctx) throw new Error("useAnalysis must be inside <AnalysisStoreProvider>");
  return ctx;
}
