"use client";

import { useMemo, useState } from "react";
import Link from "next/link";
import { useQuery } from "@tanstack/react-query";
import { ChevronDown, ChevronLeft, Play, Loader2 } from "lucide-react";

import { AppShell } from "@/components/app-shell";
import { Card } from "@/components/ui/card";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table";
import {
  fetchScriptsCatalog,
  useRunScript,
  type ScriptRunResult,
} from "@/lib/api/scripts";
import { formatDateLong } from "@/lib/format-date";

/*
 * /scripts — catalog of everything under scripts/, rendered as the
 * approved IA-redesign table (name mono · role · status chip · run/source
 * ghost button).
 *
 * The single source of truth is still `scripts/SCRIPTS.md` (CLAUDE.md mandates
 * updating it on every script change), served verbatim by
 * GET /api/scripts/catalog. We parse its markdown tables into structured rows
 * for display — editing remains git/Gitea only, so the per-row "מקור" button
 * deep-links to the file in Gitea rather than inventing a run-from-UI mutation.
 */

type ScriptStatus = "active" | "once" | "archive" | "deleted";

type ScriptRow = {
  name: string;
  role: string;
  status: ScriptStatus;
  section: ScriptStatus; // which `## ` section the row lives in (= its status)
  group: string; // the `### ` sub-topic within the section
};

const STATUS_LABEL: Record<ScriptStatus, string> = {
  active: "פעיל",
  once: "חד-פעמי",
  archive: "ארכיון",
  deleted: "נמחק",
};

const STATUS_TONE: Record<ScriptStatus, { wrap: string; dot: string }> = {
  active: { wrap: "bg-success-bg text-success", dot: "bg-success" },
  once: { wrap: "bg-info-bg text-info", dot: "bg-info" },
  archive: { wrap: "bg-rule-soft text-ink-muted", dot: "bg-ink-muted" },
  deleted: { wrap: "bg-danger-bg text-danger", dot: "bg-danger" },
};

// Archived/deleted rows get their own collapsible groups; active + one-time rows
// are grouped by the `### <sub-topic>` headers maintained in SCRIPTS.md, under
// their respective `## ` section ("סקריפטים פעילים" / "חד פעמי", #11 + one-time split).
const ARCHIVE_GROUP = "ארכיון";
const DELETED_GROUP = "נמחקו";

// Top-level sections in render order; "active"/"once" carry a section heading.
const SECTION_ORDER: ScriptStatus[] = ["active", "once", "archive", "deleted"];
const SECTION_META: Record<ScriptStatus, { label: string; note: string } | null> = {
  active: { label: "פעילים", note: "סקריפטים חוזרים / קבועי-תפעול, מקובצים לפי תת-נושא." },
  once: {
    label: "חד פעמי",
    note: "סקריפטים שהורצו פעם-אחת (מיגרציות / backfills / POCs), נשמרים לרפרנס.",
  },
  archive: null,
  deleted: null,
};

/**
 * Parse SCRIPTS.md markdown tables into typed rows. The file has four `## `
 * sections (פעילים / חד פעמי / .archive / נמחקו); the first two are split into
 * `### <sub-topic>` blocks. The row's `section` is its status (and which heading
 * it renders under); `group` is the sub-topic.
 */
function parseScripts(md: string): ScriptRow[] {
  const lines = md.split("\n");
  const rows: ScriptRow[] = [];
  let section: ScriptStatus = "active";
  let category = "";

  for (const raw of lines) {
    const line = raw.trim();
    if (line.startsWith("## ")) {
      if (line.includes(".archive") || line.includes("הושלמו")) section = "archive";
      else if (line.includes("נמחק")) section = "deleted";
      else if (line.includes("חד פעמי")) section = "once";
      else section = "active";
      category = "";
      continue;
    }
    if (line.startsWith("### ")) {
      category = line.slice(4).trim();
      continue;
    }
    if (!line.startsWith("|")) continue;
    // skip header + separator rows
    const cells = line
      .split("|")
      .slice(1, -1)
      .map((c) => c.trim());
    if (cells.length < 2) continue;
    if (/^-+$/.test(cells[0].replace(/[-:]/g, "-"))) continue; // separator
    if (cells[0] === "Script") continue; // header
    if (!cells[0]) continue;

    const name = cells[0].replace(/`/g, "");
    if (!name) continue;

    // role: active/once = Purpose (col 2), archive = Original Purpose (col 1),
    // deleted = Reason (col 1).
    const role =
      section === "active" || section === "once"
        ? cells[2] ?? cells[1] ?? ""
        : cells[1] ?? "";

    const group =
      section === "archive" ? ARCHIVE_GROUP
      : section === "deleted" ? DELETED_GROUP
      : category || "כללי";

    rows.push({ name, role: stripMd(role), status: section, section, group });
  }
  return rows;
}

type SectionBlock = {
  key: ScriptStatus;
  groups: { title: string; rows: ScriptRow[] }[];
};

/** Group rows by section, then by sub-topic within each section, preserving
 *  first-seen order; sections themselves come back in SECTION_ORDER. */
function sectionize(rows: ScriptRow[]): SectionBlock[] {
  const bySection = new Map<ScriptStatus, Map<string, ScriptRow[]>>();
  for (const r of rows) {
    let cats = bySection.get(r.section);
    if (!cats) {
      cats = new Map();
      bySection.set(r.section, cats);
    }
    let g = cats.get(r.group);
    if (!g) {
      g = [];
      cats.set(r.group, g);
    }
    g.push(r);
  }
  return SECTION_ORDER.filter((k) => bySection.has(k)).map((key) => ({
    key,
    groups: [...bySection.get(key)!.entries()].map(([title, rs]) => ({
      title,
      rows: rs,
    })),
  }));
}

// Strip bold/inline-code markdown so the role reads as plain text in a cell.
function stripMd(s: string): string {
  return s.replace(/\*\*/g, "").replace(/`/g, "");
}

function StatusChip({ status }: { status: ScriptStatus }) {
  const tone = STATUS_TONE[status];
  return (
    <span
      className={`inline-flex items-center gap-1.5 rounded-full px-2.5 py-[3px] text-[0.75rem] font-semibold ${tone.wrap}`}
    >
      <span className={`h-1.5 w-1.5 rounded-full ${tone.dot}`} aria-hidden />
      {STATUS_LABEL[status]}
    </span>
  );
}

function ScriptTable({
  rows, giteaBase, runnable, runningName, onRun,
}: {
  rows: ScriptRow[];
  giteaBase: string | null;
  runnable: Set<string>;
  runningName: string | null;
  onRun: (name: string) => void;
}) {
  return (
    <Table>
      <TableHeader>
        <TableRow className="bg-parchment hover:bg-parchment border-rule">
          <TableHead className="text-start text-[0.75rem] font-semibold text-ink-muted px-5 py-3">
            שם הסקריפט
          </TableHead>
          <TableHead className="text-start text-[0.75rem] font-semibold text-ink-muted px-5 py-3">
            תפקיד
          </TableHead>
          <TableHead className="text-start text-[0.75rem] font-semibold text-ink-muted px-5 py-3">
            סטטוס
          </TableHead>
          <TableHead className="text-end text-[0.75rem] font-semibold text-ink-muted px-5 py-3">
            פעולה
          </TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {rows.map((s) => {
          const disabled = s.status === "archive" || s.status === "deleted";
          // giteaBase is the SCRIPTS.md *file* URL (…/scripts/SCRIPTS.md); the
          // per-script "מקור" link needs the scripts/ *directory*, so strip the
          // trailing filename before appending the script name (else the link
          // becomes …/scripts/SCRIPTS.md/<name> and 404s on Gitea).
          const href = giteaBase
            ? `${giteaBase.replace(/\/[^/]+$/, "")}/${s.name}`
            : null;
          return (
            <TableRow
              key={s.name}
              className="border-rule-soft hover:bg-gold-wash align-middle"
            >
              <TableCell className="px-5 py-3.5">
                <code
                  className="font-mono text-[0.81rem] font-semibold text-navy"
                  dir="ltr"
                >
                  {s.name}
                </code>
              </TableCell>
              <TableCell className="px-5 py-3.5 text-ink-soft text-[0.84rem] leading-snug max-w-xl whitespace-normal">
                <span className="line-clamp-2">{s.role}</span>
              </TableCell>
              <TableCell className="px-5 py-3.5">
                <StatusChip status={s.status} />
              </TableCell>
              <TableCell className="px-5 py-3.5 text-end">
                <span className="inline-flex items-center gap-2 justify-end">
                  {/* "הרץ" — only on read-only/audit scripts in the host allowlist (#4) */}
                  {!disabled && runnable.has(s.name) ? (
                    <button
                      type="button"
                      disabled={runningName !== null}
                      onClick={() => onRun(s.name)}
                      className="inline-flex items-center gap-1.5 rounded-lg border border-success bg-success px-3.5 py-1.5 text-[0.81rem] font-semibold text-white hover:bg-success/90 disabled:opacity-50 transition-colors"
                    >
                      {runningName === s.name ? (
                        <Loader2 className="size-3.5 animate-spin" aria-hidden />
                      ) : (
                        <Play className="size-3.5" aria-hidden />
                      )}
                      הרץ
                    </button>
                  ) : null}
                  {disabled || !href ? (
                    <button
                      type="button"
                      disabled
                      className="rounded-lg border border-rule-soft px-4 py-1.5 text-[0.81rem] font-semibold text-ink-muted cursor-default"
                    >
                      מקור
                    </button>
                  ) : (
                    <a
                      href={href}
                      target="_blank"
                      rel="noreferrer"
                      className="inline-block rounded-lg border border-rule px-4 py-1.5 text-[0.81rem] font-semibold text-gold-deep hover:bg-gold-wash hover:border-gold transition-colors"
                    >
                      מקור
                    </a>
                  )}
                </span>
              </TableCell>
            </TableRow>
          );
        })}
      </TableBody>
    </Table>
  );
}

/** Collapsible sub-topic block (#11, mockup 16): parchment header with a
 *  chevron + title + count, and the group's table beneath. */
function ScriptGroup({
  title, rows, giteaBase, defaultOpen, runnable, runningName, onRun, indented,
}: {
  title: string;
  rows: ScriptRow[];
  giteaBase: string | null;
  defaultOpen: boolean;
  runnable: Set<string>;
  runningName: string | null;
  onRun: (name: string) => void;
  indented?: boolean;
}) {
  const [open, setOpen] = useState(defaultOpen);
  return (
    <Card
      className={`bg-surface border-rule shadow-sm overflow-hidden p-0 ${
        indented ? "ms-5" : ""
      }`}
    >
      <button
        type="button"
        onClick={() => setOpen((o) => !o)}
        aria-expanded={open}
        className={`flex w-full items-center gap-2.5 bg-parchment px-5 py-3 text-start transition-colors hover:bg-gold-wash/50 ${
          open ? "border-b border-rule" : ""
        }`}
      >
        {open ? (
          <ChevronDown className="size-4 text-ink-muted" aria-hidden />
        ) : (
          <ChevronLeft className="size-4 text-ink-muted" aria-hidden />
        )}
        <h2 className="m-0 text-[0.95rem] font-semibold text-navy">{title}</h2>
        <span className="ms-auto rounded-full bg-rule-soft px-2.5 py-0.5 text-[0.78rem] text-ink-muted tabular-nums">
          {rows.length}
        </span>
      </button>
      {open ? (
        <ScriptTable
          rows={rows}
          giteaBase={giteaBase}
          runnable={runnable}
          runningName={runningName}
          onRun={onRun}
        />
      ) : null}
    </Card>
  );
}

export default function ScriptsPage() {
  const { data, isLoading, isError, error } = useQuery({
    queryKey: ["scripts-catalog"],
    queryFn: ({ signal }) => fetchScriptsCatalog(signal),
  });

  const rows = useMemo(
    () => (data?.content ? parseScripts(data.content) : []),
    [data],
  );
  const sections = useMemo(() => sectionize(rows), [rows]);

  const lastModified =
    data?.last_modified != null
      ? formatDateLong(data.last_modified * 1000)
      : null;

  const giteaBase = data?.gitea_url ?? null;

  // #4 — read-only scripts the host allowlist permits running; the "הרץ" button
  // surfaces only for these. A run opens an output dialog with exit code + stdout.
  const runnable = useMemo(
    () => new Set(data?.runnable_scripts ?? []),
    [data],
  );
  const run = useRunScript();
  const [runState, setRunState] = useState<{
    name: string;
    running: boolean;
    result?: ScriptRunResult;
    error?: string;
  } | null>(null);
  const runningName = runState?.running ? runState.name : null;

  async function handleRun(name: string) {
    if (!window.confirm(`להריץ את ${name}? (סקריפט קריאה-בלבד)`)) return;
    setRunState({ name, running: true });
    try {
      const result = await run.mutateAsync(name);
      setRunState({ name, running: false, result });
    } catch (e) {
      setRunState({
        name,
        running: false,
        error: e instanceof Error ? e.message : "שגיאה בהרצה",
      });
    }
  }

  return (
    <AppShell>
      <section className="space-y-6">
        <header>
          <nav className="text-[0.78rem] text-ink-muted mb-1">
            <Link href="/" className="hover:text-gold-deep">בית</Link>
            <span aria-hidden> · </span>
            <span className="text-navy">סקריפטים</span>
          </nav>
          <h1 className="text-navy mb-0">סקריפטים</h1>
          <p className="text-sm text-ink-muted mt-1 max-w-2xl">
            סקריפטי-תחזוקה ותפעול. מקור-האמת הוא{" "}
            <code className="rounded bg-rule-soft px-1 py-0.5 font-mono text-[0.78rem]">
              scripts/SCRIPTS.md
            </code>{" "}
            — עריכה דרך git, לא מכאן.
          </p>
        </header>

        <div className="h-[2px] bg-gradient-to-l from-transparent via-gold to-transparent" />

        {isLoading ? (
          <Card className="bg-surface border-rule px-6 py-5 text-sm text-ink-muted">
            טוען קטלוג…
          </Card>
        ) : isError ? (
          <Card className="bg-danger-bg border-danger/40 px-6 py-5 text-sm text-danger">
            שגיאה בטעינת הקטלוג: {(error as Error)?.message ?? "לא ידוע"}
          </Card>
        ) : (
          <div className="space-y-3.5">
            {sections.map((sec) => {
              const meta = SECTION_META[sec.key];
              return (
                <div key={sec.key} className="space-y-3.5">
                  {meta ? (
                    <div className="flex items-baseline gap-3 flex-wrap border-t-2 border-rule pt-4 mt-2 first:border-t-0 first:pt-0 first:mt-0">
                      <h2 className="text-navy text-lg font-bold mb-0">{meta.label}</h2>
                      <span className="text-[0.78rem] text-ink-muted">{meta.note}</span>
                    </div>
                  ) : null}
                  {sec.groups.map((g) => (
                    <ScriptGroup
                      key={`${sec.key}:${g.title}`}
                      title={g.title}
                      rows={g.rows}
                      giteaBase={giteaBase}
                      defaultOpen={sec.key === "active"}
                      indented={sec.key === "once"}
                      runnable={runnable}
                      runningName={runningName}
                      onRun={handleRun}
                    />
                  ))}
                </div>
              );
            })}
            {lastModified ? (
              <p className="px-1 pt-1 text-xs text-ink-muted">
                עודכן לאחרונה: {lastModified}
              </p>
            ) : null}
          </div>
        )}
      </section>

      {/* run-output dialog (#4) — opens when a run completes (exit code + output) */}
      <Dialog
        open={!!runState && !runState.running}
        onOpenChange={(o) => !o && setRunState(null)}
      >
        <DialogContent className="max-w-xl">
          <DialogHeader>
            <DialogTitle className="flex items-center gap-2 flex-wrap">
              <span className="text-ink-soft text-sm font-normal">הרצה:</span>
              <code dir="ltr" className="font-mono text-sm text-navy">
                {runState?.name}
              </code>
              {runState?.result ? (
                <span
                  className={`ms-auto rounded-full px-2.5 py-0.5 text-xs font-semibold ${
                    runState.result.ok
                      ? "bg-success-bg text-success"
                      : "bg-danger-bg text-danger"
                  }`}
                >
                  {runState.result.ok ? "✓" : "✗"} exit {runState.result.exit_code}
                </span>
              ) : null}
            </DialogTitle>
          </DialogHeader>
          {runState?.error ? (
            <p className="text-danger text-sm">{runState.error}</p>
          ) : runState?.result ? (
            <pre
              dir="ltr"
              className="text-[0.72rem] font-mono whitespace-pre-wrap max-h-72 overflow-auto rounded-md border border-rule-soft bg-parchment p-3 leading-relaxed"
            >
              {(runState.result.stdout || "") +
                (runState.result.stderr ? `\n${runState.result.stderr}` : "") ||
                "—"}
            </pre>
          ) : null}
        </DialogContent>
      </Dialog>
    </AppShell>
  );
}
