import { useState } from "react"
import {
  Inbox, Check, X, GitBranch, FileDiff, Loader2, AlertTriangle, Sparkles,
  ChevronDown, ChevronRight, Clock, Hammer, RefreshCw, Lightbulb, Send, HelpCircle,
} from "lucide-react"
import { api, type AuftragItem, type IdeenItem, type SkillKandidat } from "@/lib/api"
import { useAuftragsbuch, useIdeen, useIdeenLog, useQueryClient, qk } from "@/lib/queries"
import { useDialog } from "@/lib/useDialog"
import { cn } from "@/lib/utils"

// Auftragsbuch = die Vorschlags-Inbox der Box. Werkstatt/Orchestrator liefern propose-only-
// Branches, der Traum liefert Skill-Kandidaten — hier fällt die Entscheidung per Klick.
// Annehmen (Branch) = Merge→Push→Deploy→Health mit Auto-Rollback; alles detached auf der Box.

const KIND_LABEL: Record<string, string> = {
  wartung: "Werkstatt",
  orchestrator: "Orchestrator",
  doku: "Doku",
  feature: "Feature",
}

const STATE_UI: Record<string, { label: string; cls: string; spin?: boolean }> = {
  laeuft: { label: "wird eingespielt …", cls: "text-sky-300 border-sky-500/40 bg-sky-500/10", spin: true },
  rollback: { label: "Rollback läuft …", cls: "text-amber-300 border-amber-500/40 bg-amber-500/10", spin: true },
  eingespielt: { label: "eingespielt ✓", cls: "text-emerald-300 border-emerald-500/40 bg-emerald-500/10" },
  fehlgeschlagen: { label: "fehlgeschlagen", cls: "text-red-300 border-red-500/40 bg-red-500/10" },
  zurueckgerollt: { label: "zurückgerollt", cls: "text-amber-300 border-amber-500/40 bg-amber-500/10" },
  kritisch: { label: "KRITISCH — Box prüfen", cls: "text-red-200 border-red-500/60 bg-red-500/20" },
  abgelehnt: { label: "abgelehnt", cls: "text-muted-foreground border-border/60 bg-background/30" },
}

function fmtWhen(ts: number | null | undefined): string {
  if (!ts) return "—"
  return new Date(ts * 1000).toLocaleString("de-DE", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" })
}

export function AuftragsbuchView() {
  const { data, isLoading, refetch, isFetching } = useAuftragsbuch()
  const qc = useQueryClient()
  const { showAlert, showConfirm, dialogElement } = useDialog()
  const [busy, setBusy] = useState<Record<string, boolean>>({})

  const reload = () => qc.invalidateQueries({ queryKey: qk.auftragsbuch })

  async function act(key: string, path: string, body: object, done?: string) {
    setBusy((b) => ({ ...b, [key]: true }))
    try {
      await api(path, { method: "POST", body: JSON.stringify(body) })
      if (done) showAlert("Erledigt", done)
      reload()
    } catch (e: any) {
      showAlert("Das hat nicht geklappt", String(e?.message || e))
    } finally {
      setBusy((b) => ({ ...b, [key]: false }))
    }
  }

  const items = data?.items ?? []
  const kandidaten = data?.skill_kandidaten ?? []
  const empty = items.length === 0 && kandidaten.length === 0

  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between gap-3">
        <div>
          <h1 className="bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent">
            Auftragsbuch
          </h1>
          <p className="text-sm text-muted-foreground">
            Was die Box gebaut oder sich ausgedacht hat — du entscheidest. Annehmen spielt es mit Fangnetz ein (Health-Check, automatischer Rollback).
          </p>
        </div>
        <button onClick={() => refetch()}
          className="flex h-9 items-center gap-1.5 rounded-lg border border-border/60 bg-card/45 px-3 text-xs font-semibold text-muted-foreground hover:text-foreground hover:border-primary/40 transition-all cursor-pointer">
          <RefreshCw className={cn("h-3.5 w-3.5", isFetching && "animate-spin")} /> Aktualisieren
        </button>
      </div>

      {data && !data.available && (
        <div className="rounded-2xl border border-amber-500/30 bg-amber-500/5 p-4 text-xs text-amber-300">
          Das Auftragsbuch lebt auf der Box (liest die Gitea-Branches dort). Im lokalen Dev-Modus ist hier nichts zu sehen.
        </div>
      )}

      {isLoading && (
        <div className="flex items-center gap-2 rounded-2xl border border-border/60 bg-card/30 p-6 text-xs text-muted-foreground">
          <Loader2 className="h-4 w-4 animate-spin" /> Vorschläge werden geladen …
        </div>
      )}

      {data?.available && empty && (
        <div className="flex items-center gap-3 rounded-2xl border border-emerald-500/25 bg-emerald-500/5 p-5">
          <Check className="h-5 w-5 shrink-0 text-emerald-400" />
          <div>
            <p className="text-sm font-semibold text-foreground">Nichts zu entscheiden</p>
            <p className="text-xs text-muted-foreground">Keine offenen Vorschläge. Werkstatt, Orchestrator und Traum melden sich hier, sobald es etwas gibt.</p>
          </div>
        </div>
      )}

      <IdeenSection />

      {items.length > 0 && (
        <section className="space-y-3">
          <SectionLabel icon={GitBranch}>Fertige Patches ({items.length})</SectionLabel>
          {items.map((it) => (
            <ProposalCard key={`${it.repo ?? "mc2"}:${it.branch}`} item={it} busy={busy} onAct={act} showConfirm={showConfirm} />
          ))}
        </section>
      )}

      {kandidaten.length > 0 && (
        <section className="space-y-3">
          <SectionLabel icon={Sparkles}>Skill-Kandidaten aus dem Traum ({kandidaten.length})</SectionLabel>
          {kandidaten.map((k) => (
            <KandidatCard key={k.file} k={k} busy={busy} onAct={act} showConfirm={showConfirm} />
          ))}
        </section>
      )}

      {dialogElement}
    </div>
  )
}

function SectionLabel({ icon: Icon, children }: { icon: any; children: React.ReactNode }) {
  return (
    <p className="flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60">
      <Icon className="h-3.5 w-3.5" /> {children}
    </p>
  )
}

// ── Ideen-Queue (natives Hermes-Kanban) ──────────────────────────────────────
// Eine Queue, drei Türen (Zentrale/Telegram/Lucy). Idee rein → die Box arbeitet sie
// selbst aus (Specifier) und baut (Werkstatt-Profil) → Code kommt als Karte zurück.

const IDEEN_STATE: Record<string, { label: string; cls: string; spin?: boolean }> = {
  triage: { label: "wird ausgearbeitet …", cls: "text-violet-300 border-violet-500/40 bg-violet-500/10", spin: true },
  todo: { label: "wartet", cls: "text-muted-foreground border-border/60 bg-background/30" },
  scheduled: { label: "geplant", cls: "text-muted-foreground border-border/60 bg-background/30" },
  ready: { label: "wartet", cls: "text-muted-foreground border-border/60 bg-background/30" },
  running: { label: "in Arbeit …", cls: "text-sky-300 border-sky-500/40 bg-sky-500/10", spin: true },
  review: { label: "im Review", cls: "text-amber-300 border-amber-500/40 bg-amber-500/10" },
  blocked: { label: "hängt — braucht dich", cls: "text-red-300 border-red-500/40 bg-red-500/10" },
  done: { label: "fertig ✓", cls: "text-emerald-300 border-emerald-500/40 bg-emerald-500/10" },
}

// Wie die Frage einer hängenden Karte eingeleitet wird — je nach Block-Grund des Workers.
const FRAGE_HINT: Record<string, string> = {
  needs_input: "Die Box hat eine Frage:",
  capability: "Die Box kam hier nicht weiter:",
  transient: "Die Aufgabe hakt:",
}

function IdeenSection() {
  const { data, isLoading } = useIdeen()
  const qc = useQueryClient()
  const { showAlert, dialogElement } = useDialog()
  const [text, setText] = useState("")
  const [sending, setSending] = useState(false)
  const [busy, setBusy] = useState<Record<string, boolean>>({})
  const [openLog, setOpenLog] = useState<string | null>(null)
  const [antworten, setAntworten] = useState<Record<string, string>>({})

  const items = data?.items ?? []
  const reload = () => qc.invalidateQueries({ queryKey: qk.ideen })

  // Polling-Log nur aktivieren, wenn Idee triage/running und geöffnet
  const logEnabled = openLog !== null && (data?.items.find((i) => i.id === openLog)?.status === "triage" || data?.items.find((i) => i.id === openLog)?.status === "running")
  const logData = useIdeenLog(openLog ?? "", logEnabled)

  async function submit() {
    const t = text.trim()
    if (t.length < 3 || sending) return
    setSending(true)
    try {
      // Lange Eingaben: Kurzfassung als Titel, alles als Notiz — der Specifier der Box
      // macht daraus ohnehin einen sauberen Auftrag.
      const titel = t.length > 160 ? t.slice(0, 157) + "…" : t
      await api("/api/ideen", { method: "POST", body: JSON.stringify({ titel, notiz: t.length > 160 ? t : "" }) })
      setText("")
      reload()
    } catch (e: any) {
      showAlert("Das hat nicht geklappt", String(e?.message || e))
    } finally {
      setSending(false)
    }
  }

  async function archive(id: string) {
    setBusy((b) => ({ ...b, [id]: true }))
    try {
      await api("/api/ideen/archivieren", { method: "POST", body: JSON.stringify({ id }) })
      reload()
    } catch (e: any) {
      showAlert("Das hat nicht geklappt", String(e?.message || e))
    } finally {
      setBusy((b) => ({ ...b, [id]: false }))
    }
  }

  // Hängende Aufgabe beantworten: die Antwort geht als Kommentar an die Karte, der Worker
  // macht damit weiter (Ausweg aus der Sackgasse „fragt, aber niemand kann antworten").
  async function answer(id: string) {
    const t = (antworten[id] || "").trim()
    if (t.length < 1 || busy["ans:" + id]) return
    setBusy((b) => ({ ...b, ["ans:" + id]: true }))
    try {
      await api("/api/ideen/antwort", { method: "POST", body: JSON.stringify({ id, antwort: t }) })
      setAntworten((a) => ({ ...a, [id]: "" }))
      reload()
    } catch (e: any) {
      showAlert("Das hat nicht geklappt", String(e?.message || e))
    } finally {
      setBusy((b) => ({ ...b, ["ans:" + id]: false }))
    }
  }

  // Windows-Dev / Queue nicht verfügbar → Sektion still weglassen (wie der Rest der Seite).
  if (data && !data.available) return null

  return (
    <section className="space-y-3">
      <SectionLabel icon={Lightbulb}>
        Ideen-Queue{data ? ` — ${data.offen} offen` : ""}
      </SectionLabel>
      <div className="mc-card p-4 space-y-3">
        <div className="flex gap-2">
          <input
            value={text}
            onChange={(e) => setText(e.target.value)}
            onKeyDown={(e) => { if (e.key === "Enter") submit() }}
            placeholder="Neue Idee — einfach hinschreiben, die Box macht den Rest"
            className="h-9 min-w-0 flex-1 rounded-lg border border-border/60 bg-background/50 px-3 text-sm text-foreground placeholder:text-muted-foreground/50 focus:border-primary/50 focus:outline-none"
          />
          <button
            onClick={submit}
            disabled={sending || text.trim().length < 3}
            className="flex h-9 shrink-0 items-center gap-1.5 rounded-lg border border-primary/40 bg-primary/10 px-3 text-[11px] font-bold uppercase tracking-wide text-primary transition-all hover:bg-primary/20 cursor-pointer disabled:opacity-50">
            {sending ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Send className="h-3.5 w-3.5" />} In die Queue
          </button>
        </div>
        <p className="text-[11px] text-muted-foreground">
          Eine Queue, alle Türen: hier, per Telegram oder im Gespräch mit Lucy. Die Box arbeitet Ideen selbst
          aus und baut — Code-Ergebnisse erscheinen als Karte unter „Fertige Patches“, nichts davon geht ohne
          deinen Klick live. Nur reine Doku-Bagatellen zieht die Box nachts selbst ein (mit Fangnetz und Meldung).
        </p>

        {isLoading && (
          <p className="flex items-center gap-2 text-xs text-muted-foreground"><Loader2 className="h-3.5 w-3.5 animate-spin" /> Queue wird geladen …</p>
        )}
        {data?.fehler && (
          <p className="text-xs text-amber-300">Queue gerade nicht lesbar — neuer Versuch läuft.</p>
        )}

        {items.length > 0 && (
          <ul className="space-y-1.5">
            {items.map((it: IdeenItem) => {
              const ui = IDEEN_STATE[it.status] ?? { label: it.status, cls: "text-muted-foreground border-border/60 bg-background/30" }
              const isOpen = openLog === it.id
              const canOpen = it.status === "triage" || it.status === "running"
              const blocked = it.status === "blocked"
              const answering = !!busy["ans:" + it.id]
              return (
                <li key={it.id} className={cn("flex flex-col gap-2 rounded-xl border bg-background/30 px-3 py-2", blocked ? "border-red-500/30" : "border-border/40")}>
                  <div className="flex items-center gap-2">
                    <span className={cn("flex shrink-0 items-center gap-1.5 rounded-lg border px-2 py-0.5 text-[10px] font-bold", ui.cls)}>
                      {ui.spin && <Loader2 className="h-3 w-3 animate-spin" />}
                      {ui.label}
                    </span>
                    <span className="min-w-0 flex-1 truncate text-xs text-foreground" title={it.body || it.titel}>{it.titel}</span>
                    <span className="flex shrink-0 items-center gap-1 text-[10px] text-muted-foreground/70"><Clock className="h-3 w-3" />{fmtWhen(it.erstellt)}</span>
                    {(it.status === "done" || blocked) && (
                      <button
                        disabled={!!busy[it.id]}
                        onClick={() => archive(it.id)}
                        title={blocked ? "Aufgeben und aus der Liste räumen (wandert ins Kanban-Archiv)" : "Aus der Liste räumen (wandert ins Kanban-Archiv)"}
                        className="shrink-0 rounded-md p-1 text-muted-foreground/60 transition-colors hover:text-foreground cursor-pointer disabled:opacity-50">
                        {busy[it.id] ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <X className="h-3.5 w-3.5" />}
                      </button>
                    )}
                    {canOpen && (
                      <button
                        onClick={() => setOpenLog(isOpen ? null : it.id)}
                        title="Worker-Log anzeigen"
                        className="shrink-0 rounded-md p-1 text-muted-foreground/60 transition-colors hover:text-foreground cursor-pointer">
                        {isOpen ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
                      </button>
                    )}
                  </div>

                  {blocked && (
                    <div className="space-y-2 border-t border-red-500/20 pt-2">
                      <p className="flex items-start gap-1.5 text-[11px] leading-relaxed text-red-200/90">
                        <HelpCircle className="mt-0.5 h-3.5 w-3.5 shrink-0 text-red-300" />
                        <span>
                          <b>{FRAGE_HINT[it.frage_kind || ""] ?? "Die Aufgabe hängt und wartet auf dich:"}</b>{" "}
                          {it.frage
                            ? <span className="text-foreground/90">{it.frage}</span>
                            : <span className="text-muted-foreground italic">kein genauer Grund hinterlegt — antworte einfach mit einer Entscheidung, dann läuft sie weiter.</span>}
                        </span>
                      </p>
                      <div className="flex gap-2">
                        <input
                          value={antworten[it.id] || ""}
                          onChange={(e) => setAntworten((a) => ({ ...a, [it.id]: e.target.value }))}
                          onKeyDown={(e) => { if (e.key === "Enter") answer(it.id) }}
                          placeholder="Deine Antwort — die Box macht damit weiter"
                          maxLength={2000}
                          className="h-8 min-w-0 flex-1 rounded-lg border border-border/60 bg-background/60 px-2.5 text-xs text-foreground placeholder:text-muted-foreground/50 focus:border-red-400/50 focus:outline-none"
                        />
                        <button
                          onClick={() => answer(it.id)}
                          disabled={answering || (antworten[it.id] || "").trim().length < 1}
                          className="flex h-8 shrink-0 items-center gap-1.5 rounded-lg border border-emerald-500/40 bg-emerald-500/10 px-3 text-[10px] font-bold uppercase tracking-wide text-emerald-300 transition-all hover:bg-emerald-500/20 cursor-pointer disabled:opacity-50">
                          {answering ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Send className="h-3.5 w-3.5" />} Antworten &amp; weiter
                        </button>
                      </div>
                    </div>
                  )}

                  {isOpen && canOpen && (
                    <div className="ml-2 -mt-1">
                      <div className="rounded-lg border border-border/50 bg-background/50 p-2">
                        {logEnabled && logData.isLoading ? (
                          <p className="text-[10px] text-muted-foreground animate-pulse">Log wird geladen …</p>
                        ) : logData.error ? (
                          <p className="text-[10px] text-red-400">Log nicht ladbar</p>
                        ) : logData.data?.available && logData.data.lines.length > 0 ? (
                          <pre className="max-h-64 overflow-auto font-mono text-[10px] leading-relaxed text-foreground/80 whitespace-pre-wrap">
                            {logData.data.lines.join("\n")}
                          </pre>
                        ) : (
                          <p className="text-[10px] text-muted-foreground">Kein Log verfügbar</p>
                        )}
                      </div>
                    </div>
                  )}
                </li>
              )
            })}
          </ul>
        )}
        {data?.available && !isLoading && items.length === 0 && (
          <p className="text-xs text-muted-foreground/70">Queue ist leer — wirf der Box eine Idee zu.</p>
        )}
      </div>
      {dialogElement}
    </section>
  )
}

function StateBadge({ item }: { item: AuftragItem }) {
  const st = item.status
  if (!st) return null
  const ui = STATE_UI[st.state]
  if (!ui) return null
  return (
    <span className={cn("flex items-center gap-1.5 rounded-lg border px-2 py-0.5 text-[10px] font-bold", ui.cls)} title={st.detail}>
      {ui.spin && <Loader2 className="h-3 w-3 animate-spin" />}
      {ui.label}
    </span>
  )
}

function ProposalCard({
  item, busy, onAct, showConfirm,
}: {
  item: AuftragItem
  busy: Record<string, boolean>
  onAct: (key: string, path: string, body: object, done?: string) => void
  showConfirm: (title: string, msg: string, ok: () => void) => void
}) {
  const [open, setOpen] = useState(false)
  const [diff, setDiff] = useState<string | null>(null)
  const [diffLoading, setDiffLoading] = useState(false)
  // Ablehnen mit Grund: der Grund ist das Lern-Gedächtnis des Kreislaufs (Radar/Specifier
  // bekommen ihn als „nie wieder vorschlagen"-Material) — deshalb ein kleines Inline-Feld.
  const [rejecting, setRejecting] = useState(false)
  const [grund, setGrund] = useState("")
  const running = item.status?.state === "laeuft" || item.status?.state === "rollback"
  const decided = item.status?.state === "eingespielt"
  // Lucy-Karten (Desktop-App): Annehmen baut am PC (dist) statt auf der Box zu deployen.
  const repo = item.repo ?? "mc2"
  const isLucy = repo === "lucy"
  const k = `${repo}:${item.branch}`

  async function loadDiff() {
    const next = !open
    setOpen(next)
    if (next && diff === null) {
      setDiffLoading(true)
      try {
        const r = await api<{ diff: string; truncated: boolean }>(`/api/auftragsbuch/diff?branch=${encodeURIComponent(item.branch)}&repo=${repo}`)
        setDiff(r.diff + (r.truncated ? "\n… (gekürzt)" : ""))
      } catch (e: any) {
        setDiff(`(Diff nicht ladbar: ${e?.message || e})`)
      } finally {
        setDiffLoading(false)
      }
    }
  }

  return (
    <div className="mc-card p-4 space-y-3">
      <div className="flex flex-wrap items-start justify-between gap-3">
        <div className="min-w-0">
          <div className="flex flex-wrap items-center gap-2">
            <span className="rounded-md border border-primary/30 bg-primary/10 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider text-primary">
              {KIND_LABEL[item.kind] ?? item.kind}
            </span>
            {isLucy && (
              <span className="rounded-md border border-pink-500/40 bg-pink-500/10 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider text-pink-300"
                title="Vorschlag fürs Lucy-Repo — Annehmen baut die App am PC neu und startet sie neu, wenn sie läuft.">
                Lucy
              </span>
            )}
            <span className="font-mono text-[11px] text-muted-foreground truncate" title={item.branch}>{item.branch}</span>
            <span className="flex items-center gap-1 text-[10px] text-muted-foreground/70"><Clock className="h-3 w-3" />{fmtWhen(item.ts)}</span>
            <StateBadge item={item} />
          </div>
          <p className="mt-1.5 text-sm font-semibold text-foreground">{item.subject || "(ohne Titel)"}</p>
          {item.body && <p className="mt-1 whitespace-pre-wrap text-xs leading-relaxed text-muted-foreground line-clamp-6">{item.body}</p>}
          {item.empfehlung && (
            <p className={cn("mt-1.5 flex items-start gap-1.5 rounded-lg border px-2 py-1 text-[11px]",
              item.empfehlung.empfehlung === "ANNEHMEN" && "border-emerald-500/30 bg-emerald-500/5 text-emerald-300",
              item.empfehlung.empfehlung === "ABLEHNEN" && "border-red-500/30 bg-red-500/5 text-red-300",
              item.empfehlung.empfehlung === "UNKLAR" && "border-amber-500/30 bg-amber-500/5 text-amber-300")}
              title={`Nächtliches Kurz-Gutachten (${item.empfehlung.richter}) — eine Meinung, kein Muss. Die Entscheidung bleibt bei dir.`}>
              <Sparkles className="mt-0.5 h-3 w-3 shrink-0" />
              <span><b>Empfehlung: {item.empfehlung.empfehlung === "ANNEHMEN" ? "Annehmen" : item.empfehlung.empfehlung === "ABLEHNEN" ? "Ablehnen" : "Unklar"}.</b> {item.empfehlung.satz}</span>
            </p>
          )}
        </div>

        {!running && !decided && (
          <div className="flex shrink-0 items-center gap-2">
            {!item.verwaist && (
            <button
              disabled={!!busy[k]}
              onClick={() =>
                showConfirm("Vorschlag annehmen?",
                  isLucy
                    ? `'${item.branch}' wird im Lucy-Repo auf main gemergt, dann baut dein PC die App neu (dist) und startet Lucy neu, falls sie gerade läuft. Der PC muss dafür an sein. Schlägt Build oder Start fehl, dreht die Box den Merge automatisch zurück — die laufende Lucy bleibt die alte.`
                    : `'${item.branch}' wird auf main gemergt und live eingespielt. Die Zentrale startet dabei kurz neu. Geht der Health-Check danach rot, rollt die Box automatisch zurück.${item.frontend_ohne_build ? "\n\nAchtung: Enthält Frontend-Quelltext ohne gebautes Bundle — die Oberfläche bleibt alt, bis am PC gebaut wird." : ""}`,
                  () => onAct(k, "/api/auftragsbuch/annehmen", { branch: item.branch, repo }))}
              className="flex h-9 items-center gap-1.5 rounded-lg border border-emerald-500/40 bg-emerald-500/10 px-3 text-[11px] font-bold uppercase tracking-wide text-emerald-300 transition-all hover:bg-emerald-500/20 cursor-pointer disabled:opacity-50">
              {busy[k] ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Check className="h-3.5 w-3.5" />} Annehmen
            </button>
            )}
            <button
              disabled={!!busy["ab:" + k]}
              onClick={() => setRejecting((v) => !v)}
              className="flex h-9 items-center gap-1.5 rounded-lg border border-red-500/30 bg-red-500/5 px-3 text-[11px] font-bold uppercase tracking-wide text-red-300/90 transition-all hover:bg-red-500/15 cursor-pointer disabled:opacity-50">
              {busy["ab:" + k] ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <X className="h-3.5 w-3.5" />} Ablehnen
            </button>
          </div>
        )}
      </div>

      {rejecting && !running && !decided && (
        <div className="space-y-2 rounded-xl border border-red-500/25 bg-red-500/5 p-3">
          <p className="text-[11px] text-muted-foreground">
            Der Branch '{item.branch}' wird auf Gitea gelöscht (die Arbeit bleibt in der Historie).
            Sag der Box kurz <b>warum</b> — dann schlägt sie dir so etwas nicht wieder vor.
          </p>
          <input
            value={grund}
            onChange={(e) => setGrund(e.target.value)}
            placeholder={"Grund (optional) — z. B. „gibt es schon“ oder „will ich nicht“"}
            maxLength={400}
            className="w-full rounded-lg border border-border/60 bg-background/60 px-2.5 py-1.5 text-xs text-foreground placeholder:text-muted-foreground/50 focus:border-red-400/50 focus:outline-none"
          />
          <div className="flex items-center gap-2">
            <button
              disabled={!!busy["ab:" + k]}
              onClick={() => {
                setRejecting(false)
                onAct("ab:" + k, "/api/auftragsbuch/ablehnen", { branch: item.branch, repo, grund: grund.trim() })
              }}
              className="flex h-8 items-center gap-1.5 rounded-lg border border-red-500/40 bg-red-500/10 px-3 text-[11px] font-bold uppercase tracking-wide text-red-300 transition-all hover:bg-red-500/20 cursor-pointer disabled:opacity-50">
              <X className="h-3.5 w-3.5" /> Wirklich ablehnen
            </button>
            <button
              onClick={() => setRejecting(false)}
              className="flex h-8 items-center rounded-lg border border-border/60 bg-background/40 px-3 text-[11px] font-semibold text-muted-foreground transition-all hover:text-foreground cursor-pointer">
              Abbrechen
            </button>
          </div>
        </div>
      )}

      {item.status && (running || !decided) && item.status.detail && (
        <p className="text-[11px] text-muted-foreground">{item.status.detail}</p>
      )}

      {item.verwaist && (
        <p className="rounded-lg border border-red-500/30 bg-red-500/5 px-2 py-1.5 text-[11px] text-red-300/90">
          <b>Kaputt aufgesetzt:</b> Dieser Branch hat keinen gemeinsamen Ursprung mit main (der Werkstatt-Worker
          hat nicht richtig geklont) — Annehmen ist technisch unmöglich, deshalb fehlt der Knopf.
          Am besten <b>ablehnen mit Grund</b>; die Idee kann danach frisch in die Queue.
        </p>
      )}

      <div className="flex flex-wrap items-center gap-2 text-[11px] text-muted-foreground">
        <span className="rounded bg-background/40 px-1.5 py-0.5 font-mono">{item.ahead} Commit{item.ahead === 1 ? "" : "s"}</span>
        {item.shortstat && <span className="rounded bg-background/40 px-1.5 py-0.5 font-mono">{item.shortstat.trim()}</span>}
        {item.verwaist && (
          <span className="flex items-center gap-1 rounded bg-red-500/10 px-1.5 py-0.5 text-red-300" title="Kein gemeinsamer Vorfahre mit main — git kann diesen Branch niemals mergen.">
            <AlertTriangle className="h-3 w-3" /> kein gemeinsamer Ursprung
          </span>
        )}
        {item.leer && (
          <span className="flex items-center gap-1 rounded bg-amber-500/10 px-1.5 py-0.5 text-amber-300" title="Der Diff gegen main ist leer — der Branch würde nichts verändern (Inhalt schon in main oder am Repo vorbei gearbeitet).">
            <AlertTriangle className="h-3 w-3" /> Diff leer — bringt nichts
          </span>
        )}
        {item.behind > 0 && !item.verwaist && (
          <span className="flex items-center gap-1 rounded bg-amber-500/10 px-1.5 py-0.5 text-amber-300" title="main ist seit diesem Vorschlag weitergelaufen — bei Konflikten versucht die Annahme automatisch einen Rebase.">
            <AlertTriangle className="h-3 w-3" /> {item.behind} hinter main
          </span>
        )}
        {item.frontend_ohne_build && (
          <span className="flex items-center gap-1 rounded bg-amber-500/10 px-1.5 py-0.5 text-amber-300" title="Die Box kann kein Frontend bauen (kein Node) — nach dem Annehmen bleibt die Oberfläche alt, bis am PC gebaut wird.">
            <AlertTriangle className="h-3 w-3" /> Frontend ohne Build
          </span>
        )}
      </div>

      <div>
        <button onClick={loadDiff}
          className="flex items-center gap-1.5 text-[11px] font-semibold text-muted-foreground hover:text-foreground transition-colors cursor-pointer">
          {open ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
          <FileDiff className="h-3.5 w-3.5" /> {item.files.length} Datei{item.files.length === 1 ? "" : "en"} — Änderungen ansehen
        </button>
        {open && (
          <div className="mt-2 space-y-2">
            <div className="flex flex-wrap gap-1.5">
              {item.files.map((f) => (
                <span key={f.path} className="rounded bg-background/40 px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground">
                  <b className={cn(f.status.startsWith("A") && "text-emerald-400", f.status.startsWith("D") && "text-red-400", f.status.startsWith("M") && "text-sky-400")}>{f.status}</b> {f.path}
                </span>
              ))}
              {item.files_truncated && <span className="text-[10px] text-muted-foreground/60">…</span>}
            </div>
            <div className="max-h-96 overflow-auto rounded-xl border border-border/50 bg-background/50 p-3">
              {diffLoading
                ? <span className="text-[11px] text-muted-foreground">Diff wird geladen …</span>
                : <pre className="text-[10px] leading-relaxed text-foreground/80 whitespace-pre">{
                    (diff ?? "").split("\n").map((ln, i) => (
                      <span key={i} className={cn("block", ln.startsWith("+") && !ln.startsWith("+++") && "text-emerald-300/90 bg-emerald-500/5", ln.startsWith("-") && !ln.startsWith("---") && "text-red-300/90 bg-red-500/5", (ln.startsWith("@@") || ln.startsWith("diff ")) && "text-sky-300/90")}>{ln || " "}</span>
                    ))
                  }</pre>}
            </div>
          </div>
        )}
      </div>
    </div>
  )
}

function KandidatCard({
  k, busy, onAct, showConfirm,
}: {
  k: SkillKandidat
  busy: Record<string, boolean>
  onAct: (key: string, path: string, body: object, done?: string) => void
  showConfirm: (title: string, msg: string, ok: () => void) => void
}) {
  const [open, setOpen] = useState(false)
  return (
    <div className="rounded-2xl border border-violet-500/25 bg-violet-500/[0.04] p-4 shadow-lg shadow-black/15 backdrop-blur-md space-y-3">
      <div className="flex flex-wrap items-start justify-between gap-3">
        <div className="min-w-0">
          <div className="flex flex-wrap items-center gap-2">
            <span className="rounded-md border border-violet-500/30 bg-violet-500/10 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider text-violet-300">Traum-Idee</span>
            <span className="font-mono text-[11px] text-muted-foreground truncate">{k.file}</span>
            <span className="flex items-center gap-1 text-[10px] text-muted-foreground/70"><Clock className="h-3 w-3" />{fmtWhen(k.mtime)}</span>
          </div>
          <p className="mt-1.5 text-sm font-semibold text-foreground">{k.title}</p>
        </div>
        <div className="flex shrink-0 items-center gap-2">
          <button
            disabled={!!busy["sk:" + k.file]}
            onClick={() =>
              showConfirm("Werkstatt beauftragen?",
                `Der Kandidat '${k.title}' geht als Auftrag an die Werkstatt (Orchestrator, propose-only). Das Ergebnis erscheint später als neuer Patch-Vorschlag hier im Auftragsbuch — nichts geht ungefragt live.`,
                () => onAct("sk:" + k.file, "/api/auftragsbuch/skill/annehmen", { file: k.file },
                  "Die Werkstatt arbeitet. Das Ergebnis taucht als neuer Vorschlag im Auftragsbuch auf (das kann einige Minuten dauern)."))}
            className="flex h-9 items-center gap-1.5 rounded-lg border border-violet-500/40 bg-violet-500/10 px-3 text-[11px] font-bold uppercase tracking-wide text-violet-300 transition-all hover:bg-violet-500/20 cursor-pointer disabled:opacity-50">
            {busy["sk:" + k.file] ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Hammer className="h-3.5 w-3.5" />} Beauftragen
          </button>
          <button
            disabled={!!busy["skab:" + k.file]}
            onClick={() =>
              showConfirm("Kandidat verwerfen?",
                `'${k.title}' wandert ins Archiv des Wissens-Vaults (verworfen/). Der Traum schlägt ihn nicht erneut vor.`,
                () => onAct("skab:" + k.file, "/api/auftragsbuch/skill/ablehnen", { file: k.file }))}
            className="flex h-9 items-center gap-1.5 rounded-lg border border-red-500/30 bg-red-500/5 px-3 text-[11px] font-bold uppercase tracking-wide text-red-300/90 transition-all hover:bg-red-500/15 cursor-pointer disabled:opacity-50">
            {busy["skab:" + k.file] ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <X className="h-3.5 w-3.5" />} Verwerfen
          </button>
        </div>
      </div>
      <button onClick={() => setOpen((o) => !o)}
        className="flex items-center gap-1.5 text-[11px] font-semibold text-muted-foreground hover:text-foreground transition-colors cursor-pointer">
        {open ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
        <Inbox className="h-3.5 w-3.5" /> Vorschlag lesen
      </button>
      {open && (
        <pre className="max-h-72 overflow-auto rounded-xl border border-border/50 bg-background/50 p-3 text-[11px] leading-relaxed text-foreground/85 whitespace-pre-wrap">{k.preview}</pre>
      )}
    </div>
  )
}
