import { createFileRoute, Link, useRouter } from "@tanstack/react-router";
import { FileSearch, Landmark, Search, Send } from "lucide-react";
import { useState } from "react";
import { toast } from "sonner";
import { AppShell, PageHeader } from "@/components/app-shell";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { DATA_SOURCES, RUN_SOURCE_LABEL, RUN_STATUS_LABEL } from "@/lib/constants";
import { getShellMeta } from "@/lib/server/api-core";
import { listCoverageSpikes, listDiscoveryRuns, runCoverageSpike, runDiscovery } from "@/lib/server/api-discovery";
import { listSolicitations } from "@/lib/server/api-pipeline";
import { formatDateTime, formatEin } from "@/lib/utils";

export const Route = createFileRoute("/discovery")({
  loader: async () => {
    const [meta, runs, spikes, solicitations] = await Promise.all([
      getShellMeta(),
      listDiscoveryRuns(),
      listCoverageSpikes(),
      listSolicitations(),
    ]);
    return { meta, runs, spikes, solicitations };
  },
  component: DiscoveryPage,
});

function irsForm(form: string | null | undefined) {
  const f = (form ?? "").toLowerCase().replace(/[^a-z0-9]/g, "");
  if (f === "990pf" || f === "2") return { code: "990-PF", meaning: "Private foundation tax return" };
  if (f === "990" || f === "0" || f === "1") return { code: "990", meaning: "Public charity tax return" };
  if (!form) return { code: "Unknown", meaning: "Filing type not in this sample" };
  return { code: form, meaning: "IRS return" };
}

function runStatusVariant(status: string) {
  if (status === "success" || status === "ok") return "success" as const;
  if (status === "partial" || status === "running") return "warning" as const;
  return "destructive" as const;
}

function DiscoveryPage() {
  const { meta, runs, spikes, solicitations } = Route.useLoaderData();
  const router = useRouter();
  const [running, setRunning] = useState(false);
  const [spiking, setSpiking] = useState(false);
  const latestSpike = spikes[0];
  const structured = latestSpike?.structured_count ?? 0;
  const sample = latestSpike?.sample_size ?? 0;
  const mailed = solicitations.filter((s) => Boolean(s.sent_at) && s.status !== "hold" && s.status !== "queued").length;
  const held = solicitations.filter((s) => s.status === "hold" || s.status === "queued" || !s.sent_at).length;

  async function run() {
    setRunning(true);
    try {
      const res = await runDiscovery();
      toast.message(res.status === "success" ? "Catalog search finished" : "Catalog search finished with errors", {
        description: `Checked ${res.ingested} public listings. ${res.created} ${res.created === 1 ? "was" : "were"} new to our catalog. Nothing was submitted to a funder.`,
      });
      await router.invalidate();
    } catch (err) {
      toast.error(err instanceof Error ? err.message : "Search failed");
    } finally {
      setRunning(false);
    }
  }

  async function spike() {
    setSpiking(true);
    try {
      const res = await runCoverageSpike();
      toast.message("Florida filing sample finished", {
        description: `${res.structured_count} of ${res.sample_size} foundations published a computer-readable list of grants they paid.`,
      });
      await router.invalidate();
    } catch (err) {
      toast.error(err instanceof Error ? err.message : "Sample failed");
    } finally {
      setSpiking(false);
    }
  }

  return (
    <AppShell hold={meta.hold} unread={meta.unread} noticeCount={meta.noticeCount} inboxUnread={meta.inboxUnread}>
      <PageHeader
        kicker="Finding grants · not receiving them"
        title="Catalog search"
        description="This page is a log of searches CFL runs against public databases so we can find grants to apply for. It is not an application inbox. Nothing here is submitted to a funder — a staff member still writes and sends every request."
        actions={
          <>
            <Button variant="outline" onClick={spike} disabled={spiking}>
              {spiking ? "Sampling 25 Florida foundations…" : "Sample 25 Florida filings"}
            </Button>
            <Button onClick={run} disabled={running}>
              {running ? "Searching…" : "Search public listings now"}
            </Button>
          </>
        }
      />

      <Card className="mb-6 border-primary/25 bg-primary/6">
        <CardContent className="flex flex-col gap-4 p-5 sm:flex-row sm:items-center sm:justify-between">
          <div className="min-w-0">
            <p className="text-[11px] font-medium tracking-[0.16em] text-primary uppercase">Nothing on this page was mailed</p>
            <p className="mt-2 text-sm leading-relaxed text-foreground">
              Catalog search only <span className="font-medium">finds</span> listings. It does not write a letter and it
              does not send one. To read what CFL actually prepared or mailed — the date, the letter, the original RFP,
              and any reply — open <span className="font-medium">What we sent</span>.
            </p>
            <p className="mt-2 text-xs text-muted-foreground">
              {mailed} request{mailed === 1 ? "" : "s"} logged as mailed
              {held ? ` · ${held} still on hold (not mailed)` : ""}
              {meta.hold ? " · sending hold is on — Super Admin does not mail letters" : ""}.
            </p>
          </div>
          <Button asChild className="shrink-0">
            <Link to="/outreach">
              Open what we sent
              <Send className="size-4" />
            </Link>
          </Button>
        </CardContent>
      </Card>

      <Card className="mb-6">
        <CardContent className="p-5">
          <p className="text-[11px] font-medium tracking-[0.16em] text-muted-foreground uppercase">
            How to read this page
          </p>
          <div className="mt-4 grid gap-4 sm:grid-cols-3">
            <Explainer
              icon={Search}
              step="1"
              title="We search public listings"
              body="Grants.gov lists federal RFPs. ProPublica republishes IRS filings of Florida foundations. Those are the two sources in the log below."
            />
            <Explainer
              icon={Landmark}
              step="2"
              title="We pull matches into our catalog"
              body={"“Listings checked” is how many records the source returned. “New in our catalog” is how many we did not already have. These are not applicants and not submissions."}
            />
            <Explainer
              icon={FileSearch}
              step="3"
              title="We check whether they publish grant sizes"
              body="Private foundations file IRS Form 990-PF. If they e-file a computer-readable copy that lists grants paid, we can see typical check sizes. If they don’t, we only know total giving."
            />
          </div>
        </CardContent>
      </Card>

      {latestSpike ? (
        <Card className="mb-6">
          <CardContent className="p-5">
            <p className="text-[11px] font-medium tracking-[0.16em] text-muted-foreground uppercase">
              Florida filing sample
            </p>
            <p className="mt-2 font-display text-3xl leading-tight">
              {structured} of {sample}
              <span className="mt-1 block font-sans text-base font-normal text-muted-foreground">
                foundations in this sample published a computer-readable list of the grants they paid.
              </span>
            </p>
            <p className="mt-3 max-w-3xl text-sm leading-relaxed text-muted-foreground">
              Each row is a Florida family foundation — the organization that filed an IRS return, not an applicant
              to CFL. “Computer-readable filing” means the IRS has an e-filed XML copy, not a scanned PDF.
              “Grant list included” means that filing names the nonprofits they paid last year. A zero here does
              not mean they make no grants; it means we cannot yet read the schedule of grants paid.
            </p>
            {latestSpike.notes ? (
              <p className="mt-2 max-w-3xl text-sm text-muted-foreground">{plainSpikeNote(latestSpike.notes, structured, sample)}</p>
            ) : null}

            <div className="mt-5 overflow-hidden rounded-lg">
              <div className="overflow-x-auto">
              <table className="w-full min-w-[640px] text-left text-sm">
                <thead>
                  <tr className="bg-primary text-primary-foreground">
                    <th className="px-3 py-2 font-semibold">
                      Foundation
                      <span className="mt-0.5 block text-xs font-normal text-primary-foreground/75">Filed the IRS return. Not applying to us.</span>
                    </th>
                    <th className="px-3 py-2 font-semibold">
                      IRS return
                      <span className="mt-0.5 block text-xs font-normal text-primary-foreground/75">990-PF = private foundation.</span>
                    </th>
                    <th className="px-3 py-2 font-semibold">
                      Computer-readable filing?
                      <span className="mt-0.5 block text-xs font-normal text-primary-foreground/75">E-filed XML, not a scanned PDF.</span>
                    </th>
                    <th className="px-3 py-2 font-semibold">
                      Grant list included?
                      <span className="mt-0.5 block text-xs font-normal text-primary-foreground/75">Names the nonprofits they paid.</span>
                    </th>
                  </tr>
                </thead>
                <tbody>
                  {latestSpike.rows_json.slice(0, 12).map((r) => {
                    const form = irsForm(r.form);
                    return (
                      <tr key={r.ein} className="border-t border-border">
                        <td className="py-3 pr-3">
                          <p className="font-medium">{r.name}</p>
                          <p className="text-xs text-muted-foreground">
                            EIN {formatEin(r.ein)} · {r.state}
                            {r.year ? ` · tax year ${r.year}` : ""}
                          </p>
                        </td>
                        <td className="py-3 pr-3">
                          <p>{form.code}</p>
                          <p className="text-xs text-muted-foreground">{form.meaning}</p>
                        </td>
                        <td className="py-3 pr-3">{r.has_xml ? "Yes — e-filed" : "No — paper or scanned"}</td>
                        <td className="py-3">{r.has_grants ? "Yes — lists grants paid" : "No — schedule not readable"}</td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
              </div>
            </div>
          </CardContent>
        </Card>
      ) : (
        <Card className="mb-6">
          <CardContent className="p-5">
            <p className="text-[11px] font-medium tracking-[0.16em] text-muted-foreground uppercase">
              Florida filing sample
            </p>
            <p className="mt-2 text-sm text-muted-foreground">
              Not sampled yet. Use “Sample 25 Florida filings” to check whether family foundations published a
              list of the grants they paid — that is how we estimate typical check size.
            </p>
          </CardContent>
        </Card>
      )}

      <Card className="mb-6">
        <CardContent className="p-5">
          <p className="mb-3 text-[11px] font-medium tracking-[0.16em] text-muted-foreground uppercase">
            Where these listings come from
          </p>
          <ul className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
            {DATA_SOURCES.filter((s) => s.layer.startsWith("L1") || s.layer === "L4" || s.layer === "Manual").map((s) => (
              <li key={s.name}>
                <p className="text-sm font-medium">{plainSourceName(s.name)}</p>
                <p className="text-xs text-muted-foreground">{plainSourceRole(s.role)}</p>
              </li>
            ))}
          </ul>
        </CardContent>
      </Card>

      <div>
        <div className="mb-3">
          <p className="text-[11px] font-medium tracking-[0.16em] text-muted-foreground uppercase">Search history</p>
          <p className="mt-1 max-w-3xl text-sm text-muted-foreground">
            Each row is one catalog refresh. “Listings checked” is what the public database returned.
            “New in our catalog” is how many of those we did not already have. These are not applications
            received, and they are not grants we submitted.
          </p>
        </div>
        <div className="overflow-hidden rounded-xl bg-card shadow-[var(--shadow-border)]">
          <div className="overflow-x-auto">
          <table className="w-full min-w-[720px] text-left text-sm">
            <thead>
              <tr className="bg-primary text-primary-foreground">
                <th className="px-4 py-3 font-semibold">
                  When it ran
                  <span className="mt-0.5 block text-xs font-normal text-primary-foreground/75">Start of this catalog search.</span>
                </th>
                <th className="px-4 py-3 font-semibold">
                  Result
                  <span className="mt-0.5 block text-xs font-normal text-primary-foreground/75">Whether the search finished cleanly.</span>
                </th>
                <th className="px-4 py-3 font-semibold">
                  Listings checked
                  <span className="mt-0.5 block text-xs font-normal text-primary-foreground/75">Records the source returned. Not applications.</span>
                </th>
                <th className="px-4 py-3 font-semibold">
                  New in our catalog
                  <span className="mt-0.5 block text-xs font-normal text-primary-foreground/75">Ones we did not already have.</span>
                </th>
                <th className="px-4 py-3 font-semibold">
                  What happened
                  <span className="mt-0.5 block text-xs font-normal text-primary-foreground/75">Short note from the search.</span>
                </th>
              </tr>
            </thead>
            <tbody>
              {runs.map((r) => (
                <tr key={r.id} className="border-b border-border last:border-0">
                  <td className="px-4 py-3">
                    <p>{formatDateTime(r.started_at)}</p>
                    <p className="text-xs text-muted-foreground">
                      {RUN_SOURCE_LABEL[r.source] ?? r.source}
                    </p>
                  </td>
                  <td className="px-4 py-3">
                    <Badge variant={runStatusVariant(r.status)}>
                      {RUN_STATUS_LABEL[r.status] ?? r.status}
                    </Badge>
                  </td>
                  <td className="px-4 py-3 tabular">{r.records_ingested}</td>
                  <td className="px-4 py-3 tabular">{r.records_new}</td>
                  <td className="px-4 py-3 text-muted-foreground">
                    {plainRunNote(r.notes, r.records_ingested, r.records_new)}
                    {r.errors_json.length > 0 ? (
                      <span className="mt-1 block text-destructive">
                        {r.errors_json.map((e) => e.message).join(" · ")}
                      </span>
                    ) : null}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
          </div>
        </div>
      </div>
    </AppShell>
  );
}

function Explainer({
  icon: Icon,
  step,
  title,
  body,
}: {
  icon: typeof Search;
  step: string;
  title: string;
  body: string;
}) {
  return (
    <div>
      <div className="mb-2 flex items-center gap-2">
        <span className="flex size-8 items-center justify-center rounded-lg bg-primary/12 text-primary">
          <Icon className="size-4" />
        </span>
        <span className="text-[11px] tracking-[0.16em] text-muted-foreground uppercase">Step {step}</span>
      </div>
      <p className="text-sm font-medium">{title}</p>
      <p className="mt-1 text-xs leading-relaxed text-muted-foreground">{body}</p>
    </div>
  );
}

function plainSourceName(name: string) {
  if (name.startsWith("Grants.gov")) return "Grants.gov";
  if (name.startsWith("ProPublica")) return "ProPublica Nonprofit Explorer";
  if (name.startsWith("Grantmakers")) return "Grantmakers.io";
  if (name.startsWith("Granted")) return "Granted AI (optional)";
  if (name.startsWith("DonorBox")) return "DonorBox gifts";
  if (name.startsWith("cPanel")) return "donations@ mailbox";
  return name;
}

function plainSourceRole(role: string) {
  if (role.includes("Federal")) return "Federal RFPs CFL may apply to";
  if (role.includes("Human research")) return "Staff research view over the same IRS filings";
  if (role.includes("990")) return "IRS filings of foundations — who they are, what they gave";
  if (role.includes("semantic")) return "Optional extra search across funders";
  if (role.includes("Inbound gift")) return "Individual gifts landing in Super Admin";
  if (role.includes("Award correspondence")) return "Award emails forwarded into Super Admin";
  return role;
}

function plainSpikeNote(notes: string, structured: number, sample: number) {
  if (/thin|budget IRS/i.test(notes)) {
    return `Coverage is thin: ${structured} of ${sample}. We still score these foundations from assets, total giving, and program area. A later IRS e-file download would fill in typical grant size.`;
  }
  if (/usable/i.test(notes)) {
    return `This sample is usable for a first small-grant view: ${structured} of ${sample} published a grant list we can read.`;
  }
  return notes;
}

function plainRunNote(notes: string | null, checked: number, created: number) {
  if (!notes) return `Checked ${checked} listings; ${created} new to the catalog.`;
  if (/ingested/i.test(notes) || /records/i.test(notes) || /healthy/i.test(notes) || /seed/i.test(notes)) {
    return `Checked ${checked} public listings. ${created} ${created === 1 ? "was" : "were"} new to our catalog. No applications were sent.`;
  }
  if (/partial/i.test(notes) || /survived/i.test(notes)) {
    return `One source had a problem; the other still loaded. Checked ${checked} listings; ${created} new to the catalog.`;
  }
  return notes;
}
