"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";

/*
 * /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;
  group: string;
};

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" },
};

// "חד-פעמי" / "one-shot" markers inside the Scheduled column of an active row.
const ONCE_RE = /חד-?פעמי|one-?shot|בוצע/;

// Archived/deleted rows get their own collapsible groups; active rows are
// grouped by the `### <sub-topic>` headers maintained in SCRIPTS.md (#11).
const ARCHIVE_GROUP = "ארכיון";
const DELETED_GROUP = "נמחקו";

/**
 * Parse SCRIPTS.md markdown tables into typed rows. The file has three
 * sections; the active section is further split into `### <sub-topic>` blocks.
 * We read the first two columns (name + role), derive status from the section +
 * scheduling note, and carry the sub-topic header as the row's display group.
 */
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 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;

    let status: ScriptStatus = section;
    if (section === "active") {
      const scheduled = cells[3] ?? "";
      status = ONCE_RE.test(scheduled) ? "once" : "active";
    }
    // role: active = Purpose (col 2), archive = Original Purpose (col 1),
    // deleted = Reason (col 1).
    const role =
      section === "active" ? cells[2] ?? cells[1] ?? "" : cells[1] ?? "";

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

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

/** Group rows by their display group, preserving first-seen order. */
function groupScripts(rows: ScriptRow[]): { title: string; rows: ScriptRow[] }[] {
  const order: string[] = [];
  const byGroup = new Map<string, ScriptRow[]>();
  for (const r of rows) {
    if (!byGroup.has(r.group)) {
      byGroup.set(r.group, []);
      order.push(r.group);
    }
    byGroup.get(r.group)!.push(r);
  }
  return order.map((title) => ({ title, rows: byGroup.get(title)! }));
}

// 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";
          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,
}: {
  title: string;
  rows: ScriptRow[];
  giteaBase: string | null;
  defaultOpen: boolean;
  runnable: Set<string>;
  runningName: string | null;
  onRun: (name: string) => void;
}) {
  const [open, setOpen] = useState(defaultOpen);
  return (
    <Card className="bg-surface border-rule shadow-sm overflow-hidden p-0">
      <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 groups = useMemo(() => groupScripts(rows), [rows]);

  const lastModified =
    data?.last_modified != null
      ? new Date(data.last_modified * 1000).toLocaleDateString("he-IL", {
          year: "numeric",
          month: "long",
          day: "numeric",
        })
      : 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">
            {groups.map((g) => (
              <ScriptGroup
                key={g.title}
                title={g.title}
                rows={g.rows}
                giteaBase={giteaBase}
                defaultOpen={g.title !== ARCHIVE_GROUP && g.title !== DELETED_GROUP}
                runnable={runnable}
                runningName={runningName}
                onRun={handleRun}
              />
            ))}
            {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>
  );
}
