import { useEffect, useState } from "react"
import {
  Loader2, AlertTriangle, ChevronDown, ChevronRight, Clock, Hammer, Lightbulb, Send,
  HelpCircle, RotateCcw, Square, ArrowUp, ArrowDown, Code2, FileText, GitFork, X, Layers,
  RefreshCw, ClipboardCheck, Check,
} from "lucide-react"
import { api, type IdeenErgebnis, type IdeenItem, type IdeenKonzept, type IdeenProjekt } from "@/lib/api"
import { Markdown } from "@/lib/markdown"
import { useIdeen, useIdeenLog, useQueryClient, qk } from "@/lib/queries"
import { useDialog } from "@/lib/useDialog"
import { cn } from "@/lib/utils"
import { fmtWhen } from "@/lib/format"
import { SectionLabel } from "@/components/ui/SectionLabel"

// Ideen-Queue (natives Hermes-Kanban) — eigene Seite seit 25.07.2026.
// Vorher steckte sie als 703-Zeilen-Abschnitt in der Auftragsbuch-Ansicht und war damit
// die MEHRHEIT einer Seite, die nach etwas anderem benannt war. Die beiden laufen in
// entgegengesetzte Richtungen: Auftragsbuch = die Box schlaegt DIR vor (Eingang),
// Ideen = DU beauftragst die Box (Ausgang). Seit dem Umbau ist dieser Weg der
// Haupteingang des Systems (Idee -> Konzept -> Repo -> Zed) und gehoert nach vorn.

// ── 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: "gleich dran", cls: "text-sky-300/80 border-sky-500/30 bg-sky-500/5" },
  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" },
}

// Welche Welt hat die Karte durchlaufen? Vorher stand auf Idee UND IDE-Projekt dasselbe
// „IDE" — man sah der fertigen Karte nicht an, ob sie nur durchdacht oder als Repo
// vorbereitet wurde (User 21.07.). Jetzt drei ehrliche Marken.
const ART_UI: Record<string, { label: string; cls: string; icon: typeof Hammer; title: string }> = {
  box: {
    label: "BOX", icon: Hammer,
    cls: "border-border/60 bg-background/40 text-muted-foreground",
    title: "Box-Welt: die Werkstatt baut das selbst. Das Ergebnis kommt als Patch-Vorschlag zurück — nichts geht ohne deinen Klick live.",
  },
  idee: {
    label: "IDEE", icon: Lightbulb,
    cls: "border-sky-500/40 bg-sky-500/10 text-sky-300",
    title: "Nur durchdacht: die Box hat die Produkt-Idee mit voller Denk-Kaskade zu einem Konzept ausgearbeitet. Kein Gerüst, kein Bau — du liest es und entscheidest, ob ein Projekt draus wird.",
  },
  projekt: {
    label: "IDE", icon: Code2,
    cls: "border-violet-500/40 bg-violet-500/10 text-violet-300",
    title: "IDE-Welt: die Box bereitet nur vor (Konzept + Repo + Doku, kein Code). Gebaut wird von dir in Zed.",
  },
}

// 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:",
}

// Wie oft lag die Einordnung daneben? Der ganze zugeklappte Entwurf steht und fällt damit.
// Bewusst im Browser statt im Backend: zwei Zahlen brauchen keine Tabelle, keinen Endpunkt
// und keine Migration — und es geht niemanden ausser dem Nutzer etwas an. Sichtbar wird die
// Quote nur beim Aufklappen, damit sie beweisbar ist statt behauptet.
const QUOTE_KEY = "mc_ideen_quote"

function quoteLesen(): { gesendet: number; geaendert: number } {
  try {
    const d = JSON.parse(localStorage.getItem(QUOTE_KEY) || "{}")
    return { gesendet: Number(d.gesendet) || 0, geaendert: Number(d.geaendert) || 0 }
  } catch { return { gesendet: 0, geaendert: 0 } }
}

function quoteZaehlen(feld: "gesendet" | "geaendert"): void {
  try {
    const q = quoteLesen()
    q[feld] += 1
    localStorage.setItem(QUOTE_KEY, JSON.stringify(q))
  } catch { /* privater Modus o. ä. — die Zählung ist Beiwerk, nie ein Grund zu scheitern */ }
}

function IdeenSection() {
  const { data, isLoading } = useIdeen()
  const qc = useQueryClient()
  const { showAlert, showConfirm, dialogElement } = useDialog()
  const [text, setText] = useState("")
  // GENAU EINE Auswahl aus drei, in zwei Bereichen (26.07.2026):
  //   denken → du baust selbst, aber erst ein Konzept. Auf „gefällt mir" wird daraus ein Repo.
  //   repo   → du baust selbst, Konzept + Gitea-Repo sofort.
  //   box    → die Box baut, Ergebnis kommt als Patch-Vorschlag.
  //
  // Vorher (25.07.) waren das zwei angeblich unabhängige Dimensionen: „wer baut" plus ein
  // Haken „nur denken". Das war FALSCH modelliert — `weiterfuehren` macht aus einer
  // durchdachten Idee genau die IDE-Projekt-Karte, die `repo` direkt anlegt. Es sind also
  // zwei Stufen EINES Weges, nicht zwei Achsen. Und die Kombination box+denken schob einen
  // still in die IDE-Welt (submit setzte welt:"ide"), ohne das irgendwo zu sagen.
  const [modus, setModus] = useState<"denken" | "repo" | "box">("denken")
  const [tiefe, setTiefe] = useState<"schlank" | "gruendlich">("schlank")
  // Zugeklappt statt Assistent (26.07.2026): der Entscheidungsbaum EXISTIERT — Wer baut das?,
  // und nur im Zed-Ast: wie startest du? — aber du läufst ihn nur, wenn die Einordnung
  // danebenlag. Im Normalfall steht da eine Zeile und du drückst Enter.
  const [offen, setOffen] = useState(false)
  // Hat die Box den Text wirklich gelesen? Steuert nur das Etikett „gelesen" in der
  // zugeklappten Zeile. Ohne Einordnung (Text zu kurz, Aufruf gescheitert) steht dort die
  // Voreinstellung — und die darf sich nicht als Verständnis ausgeben.
  const [vorgeschlagen, setVorgeschlagen] = useState(false)
  const isIde = modus !== "box"        // denken UND repo laufen über das projektstart-Profil
  const isProjekt = modus === "repo"   // nur DANN sind Tiefe und Ziel jetzt schon nötig
  // Ziel-Plattform des Projekts (nur IDE-Welt): läuft es als LXC-Dienst oder in Docker?
  const [ziel, setZiel] = useState<"lxc" | "docker">("lxc")
  const [sending, setSending] = useState(false)
  const [busy, setBusy] = useState<Record<string, boolean>>({})
  const [openLog, setOpenLog] = useState<string | null>(null)
  const [openKonzept, setOpenKonzept] = useState<string | null>(null)
  const [antworten, setAntworten] = useState<Record<string, string>>({})

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

  // Die Box liest mit und wählt vor. Gemessen ~500 ms auf dem dauerwarmen Hirn, deshalb
  // reicht eine kurze Tipp-Pause. Bewusst ein VORSCHLAG: er setzt nur die Auswahl, die du
  // ohnehin siehst und mit einem Klick überschreibst. Scheitert er, passiert schlicht nichts
  // — kein Ladebalken, keine Fehlermeldung, das Tippen wird nie blockiert.
  useEffect(() => {
    const t = text.trim()
    if (t.length < 12) { setVorgeschlagen(false); return }
    let verworfen = false
    const timer = setTimeout(async () => {
      try {
        const r = await api<{ ok: boolean; wer?: "box" | "zed"; tiefe?: "schlank" | "gruendlich"; nur_denken?: boolean }>(
          "/api/ideen/einordnen", { method: "POST", body: JSON.stringify({ text: t }) })
        if (verworfen || !r?.ok) return
        // Die drei Karten aus der Einordnung ableiten: eine Frage („lohnt sich…?") wird zum
        // Durchdenken, ein eigenständiges Programm zum Repo, alles am Heimlabor zur Box.
        setModus(r.nur_denken ? "denken" : r.wer === "zed" ? "repo" : "box")
        if (r.tiefe) setTiefe(r.tiefe)
        setVorgeschlagen(true)
      } catch { /* stumm — die Voreinstellung bleibt einfach stehen */ }
    }, 700)
    return () => { verworfen = true; clearTimeout(timer) }
  }, [text])

  // 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 : "",
        // Abbildung auf die unveränderte API. Tiefe und Ziel gehen NUR beim Repo-Weg mit —
        // beim Durchdenken fragt die Weiter-Leiste sie später beim „gefällt mir", und dann
        // weiss man mehr als jetzt.
        welt: isIde ? "ide" : "box",
        tiefe: isProjekt ? (tiefe === "gruendlich" ? "gruendlich" : "simpel") : "",
        ziel: isProjekt ? ziel : "",
        art: modus === "denken" ? "idee" : "projekt",
      }) })
      quoteZaehlen("gesendet")
      setText("")
      setVorgeschlagen(false)
      setOffen(false)
      reload()
    } catch (e: any) {
      showAlert("Das hat nicht geklappt", String(e?.message || e))
    } finally {
      setSending(false)
    }
  }

  // Sammel-Aufräumen: fertige Karten wegräumen, ohne 18-mal auf das X zu klicken.
  // Fragt vorher nach — und sagt dabei ehrlich, dass nichts gelöscht wird, sondern
  // ins Kanban-Archiv wandert. Das Backend fasst ausschliesslich `done` an.
  function archiveAlle(projekt: string, anzahl: number, was: string) {
    showConfirm(
      `${anzahl} fertige Karte${anzahl === 1 ? "" : "n"} wegräumen?`,
      `${was} verschwindet aus der Liste. Gelöscht wird nichts — die Karten wandern ins ` +
      `Archiv der Queue und bleiben dort lesbar. Laufende, hängende und wartende Karten ` +
      `bleiben unberührt.`,
      async () => {
        const key = "arch:" + (projekt || "*")
        setBusy((b) => ({ ...b, [key]: true }))
        try {
          const r = await api<{ archiviert: number; fehlgeschlagen: { titel: string }[] }>(
            "/api/ideen/archivieren-alle", { method: "POST", body: JSON.stringify({ projekt }) })
          if (r.fehlgeschlagen?.length) {
            showAlert("Teilweise erledigt",
              `${r.archiviert} weggeräumt, ${r.fehlgeschlagen.length} nicht: ` +
              r.fehlgeschlagen.map((f) => f.titel).join(", "))
          }
          reload()
        } catch (e: any) {
          showAlert("Das hat nicht geklappt", String(e?.message || e))
        } finally {
          setBusy((b) => ({ ...b, [key]: 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 }))
    }
  }

  // Karten-Steuerung (Stopp / Neuer Versuch / Prio) — ein Helfer für alle drei Endpunkte.
  async function steuer(prefix: string, id: string, pfad: string, body: object) {
    setBusy((b) => ({ ...b, [prefix + id]: true }))
    try {
      await api(pfad, { method: "POST", body: JSON.stringify(body) })
      reload()
    } catch (e: any) {
      showAlert("Das hat nicht geklappt", String(e?.message || e))
    } finally {
      setBusy((b) => ({ ...b, [prefix + 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 }))
    }
  }

  // EINE Zeilen-Darstellung für alle Sichten (Projekt-Gruppen + Einzelkarten) — damit
  // Antwortfeld, Live-Log und Ergebnis-Panel überall identisch funktionieren.
  const zeile = (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
    // Aufklappbar: laufende Karten zeigen das Live-Log, FERTIGE das Ergebnis
    // (User-Wunsch 15.07.: „Dinge werden getestet, ich sehe das Ergebnis nicht").
    const canOpen = it.status === "triage" || it.status === "running" || it.status === "done"
    const blocked = it.status === "blocked"
    const answering = !!busy["ans:" + it.id]
    // Hänger-Verdacht: läuft, aber seit >10 min kein Lebenszeichen (Heartbeat) — der
    // Prozess kann trotzdem „leben" (wartet z. B. auf einen selbst gestarteten Server).
    const haengt = it.status === "running" && (it.puls_alter ?? 0) > 600
    const wartend = it.status === "todo" || it.status === "ready" || it.status === "scheduled"
    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" : haengt ? "border-amber-500/40" : "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>
          {haengt && (
            <span className="flex shrink-0 items-center gap-1 rounded-lg border border-amber-500/40 bg-amber-500/10 px-2 py-0.5 text-[10px] font-bold text-amber-300"
              title="Der Worker lebt, gibt aber kein Lebenszeichen mehr — vermutlich wartet er auf etwas, das nie kommt. „Neuer Versuch“ startet die Karte frisch.">
              <AlertTriangle className="h-3 w-3" /> {Math.round((it.puls_alter ?? 0) / 60)} min ohne Regung
            </span>
          )}
          {(it.prio ?? 0) !== 0 && (
            <span className="shrink-0 rounded bg-background/50 px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground"
              title="Priorität — höher kommt zuerst dran (bei gleicher Prio: Erstellzeit)">P{it.prio}</span>
          )}
          {(() => {
            const a = ART_UI[it.art || (it.welt === "ide" ? "projekt" : "box")]
            if (!a) return null
            const Icon = a.icon
            return (
              <span className={cn("flex shrink-0 items-center gap-1 rounded-lg border px-2 py-0.5 text-[10px] font-bold", a.cls)} title={a.title}>
                <Icon className="h-3 w-3" /> {a.label}
              </span>
            )
          })()}
          {/* Tooltip = der volle TITEL (der ist abgeschnitten), nicht der Auftragstext —
              der beginnt mit Vorspann-Textbausteinen und half beim Lesen nie. */}
          <span className="min-w-0 flex-1 truncate text-xs text-foreground" title={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>
          {wartend && (
            <span className="flex shrink-0 items-center">
              <button
                disabled={!!busy["pr:" + it.id]}
                onClick={() => steuer("pr:", it.id, "/api/ideen/prio", { id: it.id, richtung: "hoch" })}
                title="Vorziehen (Prio +1)"
                className="rounded-md p-1 text-muted-foreground/60 transition-colors hover:text-foreground cursor-pointer disabled:opacity-50">
                <ArrowUp className="h-3.5 w-3.5" />
              </button>
              <button
                disabled={!!busy["pr:" + it.id]}
                onClick={() => steuer("pr:", it.id, "/api/ideen/prio", { id: it.id, richtung: "runter" })}
                title="Zurückstellen (Prio −1)"
                className="rounded-md p-1 text-muted-foreground/60 transition-colors hover:text-foreground cursor-pointer disabled:opacity-50">
                <ArrowDown className="h-3.5 w-3.5" />
              </button>
            </span>
          )}
          {(it.status === "running" || blocked) && (
            <button
              disabled={!!busy["rt:" + it.id]}
              onClick={() =>
                showConfirm("Neuer Versuch?",
                  it.status === "running"
                    ? `Der laufende Worker von '${it.titel.slice(0, 60)}' wird beendet (samt gestarteter Test-Prozesse) und die Karte frisch gestartet. Bisheriger Zwischenstand im Workspace bleibt liegen.`
                    : `Die Karte '${it.titel.slice(0, 60)}' wird entsperrt und läuft weiter, sobald ein Platz frei ist.`,
                  () => steuer("rt:", it.id, "/api/ideen/retry", { id: it.id }))}
              title="Neuer Versuch — Karte frisch starten"
              className={cn("shrink-0 rounded-md p-1 transition-colors cursor-pointer disabled:opacity-50",
                haengt ? "text-amber-300 hover:text-amber-200" : "text-muted-foreground/60 hover:text-foreground")}>
              {busy["rt:" + it.id] ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <RotateCcw className="h-3.5 w-3.5" />}
            </button>
          )}
          {(it.status === "running" || wartend || it.status === "triage") && (
            <button
              disabled={!!busy["st:" + it.id]}
              onClick={() =>
                showConfirm("Karte stoppen?",
                  `'${it.titel.slice(0, 60)}' wird angehalten${it.status === "running" ? " und der laufende Worker beendet" : ""}. Sie bleibt als gestoppt im Board und kann jederzeit per „Neuer Versuch“ weiterlaufen.`,
                  () => steuer("st:", it.id, "/api/ideen/stopp", { id: it.id }))}
              title="Stoppen — Karte anhalten"
              className="shrink-0 rounded-md p-1 text-muted-foreground/60 transition-colors hover:text-red-300 cursor-pointer disabled:opacity-50">
              {busy["st:" + it.id] ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Square className="h-3.5 w-3.5" />}
            </button>
          )}
          {(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>
          )}
          {it.welt === "ide" && (it.status === "running" || it.status === "done") && (
            <button
              onClick={() => setOpenKonzept(openKonzept === it.id ? null : it.id)}
              title="Konzept ansehen (das ausgearbeitete KONZEPT.md)"
              className={cn("flex shrink-0 items-center gap-1 rounded-md px-1.5 py-1 text-[10px] font-bold transition-colors cursor-pointer",
                openKonzept === it.id ? "text-violet-300" : "text-violet-300/70 hover:text-violet-200")}>
              <FileText className="h-3.5 w-3.5" /> Konzept
            </button>
          )}
          {canOpen && (
            <button
              onClick={() => setOpenLog(isOpen ? null : it.id)}
              title={it.status === "done" ? "Ergebnis anzeigen" : "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>

        {openKonzept === it.id && (
          <KonzeptPanel
            id={it.id}
            istIdee={it.art === "idee" && it.status === "done"}
            onWeiter={reload}
          />
        )}

        {it.status === "running" && it.notiz && (
          <p className="-mt-1 ml-1 text-[11px] italic leading-relaxed text-sky-200/80" title="Jüngste Zwischenmeldung des Workers (Heartbeat)">
            „{it.notiz}“
          </p>
        )}
        {(it.status === "todo" || it.status === "scheduled" || it.status === "ready") && (it.wartet_auf?.length ?? 0) > 0 && (
          <p className="-mt-1 ml-1 text-[10px] text-muted-foreground/70">
            → wartet auf: {it.wartet_auf!.join(" · ")}
          </p>
        )}

        {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 && it.status !== "done" && (
          <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>
        )}

        {isOpen && it.status === "done" && <ErgebnisPanel id={it.id} />}
      </li>
    )
  }

  // 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">
      {/* Kopfzeile sagt in einem Blick, was DICH betrifft — vorher stand hier nur eine
          Gesamtzahl „offen", die nichts darüber verriet, ob etwas auf dich wartet.
          Rechts daneben das Sammel-Aufräumen, das nur erscheint, wenn es etwas zu räumen gibt. */}
      <div className="flex items-center justify-between gap-3">
      <SectionLabel icon={Lightbulb}>
        {(() => {
          if (!data) return "Ideen-Queue"
          const n = (s: string[]) => items.filter((i) => s.includes(i.status)).length
          const teile = [
            n(["blocked"]) > 0 ? `${n(["blocked"])} braucht dich` : "",
            n(["running", "triage", "review"]) > 0 ? `${n(["running", "triage", "review"])} in Arbeit` : "",
            n(["todo", "scheduled", "ready"]) > 0 ? `${n(["todo", "scheduled", "ready"])} wartet` : "",
            n(["done"]) > 0 ? `${n(["done"])} fertig` : "",
          ].filter(Boolean)
          // Nur der Zustand, nicht nochmal der Seitenname: „Ideen" steht bereits als
          // Überschrift darüber und ein drittes Mal in der Kopfzeile der App.
          return teile.length ? teile.join(" · ") : "noch nichts in der Queue"
        })()}
      </SectionLabel>
        {(() => {
          const fertig = items.filter((i) => i.status === "done").length
          if (fertig === 0) return null
          return (
            <button
              disabled={!!busy["arch:*"]}
              onClick={() => archiveAlle("", fertig, `Alle ${fertig} fertigen Karten`)}
              title="Räumt ALLE fertigen Karten weg — auch die in abgeschlossenen Projekt-Ketten. Laufende, hängende und wartende bleiben unberührt."
              className="flex shrink-0 items-center gap-1.5 rounded-lg border border-border/60 px-2.5 py-1 text-[11px] font-semibold text-muted-foreground transition-all hover:border-primary/40 hover:text-foreground disabled:opacity-50 cursor-pointer">
              {busy["arch:*"] ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <X className="h-3.5 w-3.5" />}
              {fertig} fertige wegräumen
            </button>
          )
        })()}
      </div>
      <div className="mc-card p-4 space-y-4">
        {/* Schreiben zuerst. Alles darunter ist eine Entscheidung ZU dem, was hier steht —
            nicht davor. Der Knopf heisst immer gleich; was passiert, steht bei den Karten. */}
        <div className="flex gap-2">
          <input
            value={text}
            onChange={(e) => { setText(e.target.value); setVorgeschlagen(false) }}
            onKeyDown={(e) => { if (e.key === "Enter") submit() }}
            placeholder="Was soll entstehen?"
            className="h-10 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-10 shrink-0 items-center gap-1.5 rounded-lg border border-primary/40 bg-primary/10 px-4 text-xs font-semibold text-primary transition-all hover:bg-primary/20 disabled:opacity-40 cursor-pointer">
            {sending ? <Loader2 className="h-4 w-4 animate-spin" /> : <Send className="h-4 w-4" />}
            Los
          </button>
        </div>

        {/* ZUGEKLAPPT STATT ASSISTENT. Der Entscheidungsbaum ist derselbe, den ein Wizard
            abfragen wuerde — Wer baut das?, und NUR im Zed-Ast: wie startest du? Nur laeufst
            du ihn erst, wenn die Einordnung danebenlag. Ein Assistent wuerde dich fragen, was
            die Box in ~500 ms schon weiss; das hier fragt nur im Zweifelsfall. */}
        {text.trim().length >= 3 && (
          offen ? (
            <div className="space-y-3 rounded-xl border border-border/40 bg-background/20 p-3">
              <div>
                <p className="mb-1.5 text-[11px] text-muted-foreground/70">Wer baut das?</p>
                <div className="grid gap-2 sm:grid-cols-2">
                  <Wahl aktiv={modus !== "box"}
                    onWahl={() => setModus(modus === "box" ? "denken" : modus)}
                    k={{ id: "zed", icon: Code2, titel: "Ich, in Zed",
                         text: "Du bekommst Konzept und Repo — den Code schreibst du selbst." }} />
                  <Wahl aktiv={modus === "box"}
                    onWahl={() => setModus("box")}
                    k={{ id: "box", icon: Hammer, titel: "Die Box, allein",
                         text: "Für dein Heimlabor. Das Ergebnis kommt als Patch-Vorschlag ins Auftragsbuch." }} />
                </div>
              </div>

              {/* Die zweite Frage gibt es NUR im Zed-Ast — eingerückt, damit die Verschachtelung
                  sichtbar ist. Bei „die Box" folgt keine Rückfrage: dann geht es direkt los. */}
              {modus !== "box" && (
                <div className="border-l-2 border-primary/40 pl-3">
                  <p className="mb-1.5 text-[11px] text-muted-foreground/70">Und wie startest du?</p>
                  <div className="grid gap-2 sm:grid-cols-2">
                    <Wahl aktiv={modus === "denken"} onWahl={() => setModus("denken")}
                      k={{ id: "denken", icon: Lightbulb, titel: "Erst durchdenken",
                           text: "Konzept zuerst — auf dein Ja wird ein Repo daraus." }} />
                    <Wahl aktiv={modus === "repo"} onWahl={() => setModus("repo")}
                      k={{ id: "repo", icon: GitFork, titel: "Direkt ein Repo",
                           text: "Konzept und Gitea-Repo in einem Zug." }} />
                  </div>
                  {modus === "repo" ? (
                    <div className="mt-2.5 flex flex-wrap items-center gap-x-3 gap-y-2">
                      <span className="text-[11px] text-muted-foreground/70">Wie gründlich?</span>
                      <div className="flex rounded-lg border border-border/60 p-0.5">
                        {([["schlank", "schlank"], ["gruendlich", "gründlich"]] as const).map(([id, label]) => (
                          <button key={id} onClick={() => setTiefe(id)}
                            className={cn("rounded px-2.5 py-1 text-[10px] font-bold uppercase tracking-wide transition-all cursor-pointer",
                              tiefe === id ? "bg-primary/15 text-primary" : "text-muted-foreground/60 hover:text-foreground")}>
                            {label}
                          </button>
                        ))}
                      </div>
                      <span className="ml-auto flex items-center gap-1.5">
                        <span className="text-[11px] text-muted-foreground/70">Läuft als</span>
                        <div className="flex rounded-lg border border-border/40 p-0.5">
                          {([["lxc", "LXC"], ["docker", "Docker"]] as const).map(([id, label]) => (
                            <button key={id} onClick={() => setZiel(id)}
                              title={id === "lxc"
                                ? "Dienst in einem eigenen Proxmox-LXC (systemd, kein Docker). Wird vom Backup-Server mitgesichert."
                                : "Natives Docker: Dockerfile + compose, portabel und pipeline-tauglich."}
                              className={cn("rounded px-2 py-0.5 text-[10px] font-semibold transition-all cursor-pointer",
                                ziel === id ? "bg-foreground/10 text-foreground" : "text-muted-foreground/50 hover:text-foreground/80")}>
                              {label}
                            </button>
                          ))}
                        </div>
                      </span>
                    </div>
                  ) : (
                    <p className="mt-2 flex items-start gap-1.5 text-[10px] leading-relaxed text-muted-foreground/60">
                      <HelpCircle className="mt-px h-3 w-3 shrink-0" />
                      Wie gründlich und wo es läuft, fragt die Box erst beim „gefällt mir" — dann weißt du mehr.
                    </p>
                  )}
                </div>
              )}

              {/* Beweisbar statt behauptet: die Quote, an der dieser ganze Entwurf haengt. */}
              {(() => {
                const q = quoteLesen()
                if (q.gesendet < 3) return null
                return (
                  <p className="border-t border-border/30 pt-2 text-[10px] text-muted-foreground/50">
                    Du hast {q.geaendert} von {q.gesendet} Vorschlägen geändert. Wird das die Regel,
                    bauen wir hier einen richtigen Schritt-für-Schritt-Assistenten.
                  </p>
                )
              })()}
            </div>
          ) : (
            <div className="flex flex-wrap items-center gap-2 rounded-xl border border-border/40 bg-background/20 px-3 py-2.5 text-xs">
              {modus === "box" ? <Hammer className="h-4 w-4 shrink-0 text-primary" />
                : modus === "repo" ? <GitFork className="h-4 w-4 shrink-0 text-primary" />
                  : <Lightbulb className="h-4 w-4 shrink-0 text-primary" />}
              <span className="text-muted-foreground">
                {modus === "box" ? "Ich baue das selbst und lege dir das Ergebnis als Vorschlag hin."
                  : modus === "repo" ? "Du baust selbst — ich lege dir Konzept und Repo an."
                    : "Du baust selbst — ich arbeite erst ein Konzept aus."}
              </span>
              {/* Nur wenn die Box wirklich gelesen hat. Ohne Einordnung (Text noch zu kurz
                  oder Aufruf gescheitert) steht hier die Voreinstellung — und die darf sich
                  nicht als Verständnis ausgeben. */}
              {vorgeschlagen && (
                <span className="shrink-0 rounded border border-primary/40 bg-primary/10 px-1.5 py-0.5 text-[10px] font-bold text-primary">
                  gelesen
                </span>
              )}
              <button
                onClick={() => { setOffen(true); quoteZaehlen("geaendert") }}
                className="ml-auto shrink-0 text-[11px] text-muted-foreground/60 underline underline-offset-2 transition-colors hover:text-foreground cursor-pointer">
                anders
              </button>
            </div>
          )
        )}
        {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 && (
          <div className="space-y-3">
            {(data?.projekte ?? []).map((p) => (
              <ProjektGruppe key={p.key} projekt={p} karten={items.filter((i) => i.projekt === p.key)}
                zeile={zeile} onArchivAlle={archiveAlle} raeumt={!!busy["arch:" + p.key]} />
            ))}
            {(() => {
              // Einzelkarten (keine Kette). Bis 25.07. eine flache Erstellzeit-Liste — bei
              // 19 Karten, davon 16 fertig, gingen die drei relevanten darin unter. Jetzt
              // dieselbe Ordnung, die ProjektGruppe für Ketten längst benutzt: aktiv zuerst
              // nach AKTIV_RANG, Wartende darunter, Fertige eingeklappt.
              const keys = new Set((data?.projekte ?? []).map((p) => p.key))
              const einzeln = items.filter((i) => !i.projekt || !keys.has(i.projekt))
              if (einzeln.length === 0) return null
              const aktiv = einzeln
                .filter((i) => AKTIV_RANG[i.status] !== undefined)
                .sort((a, b) => (AKTIV_RANG[a.status] - AKTIV_RANG[b.status]) || ((a.erstellt ?? 0) - (b.erstellt ?? 0)))
              const wartend = einzeln.filter((i) => ["todo", "scheduled"].includes(i.status))
              const fertig = einzeln.filter((i) => i.status === "done")
              return (
                <div className="space-y-2">
                  {aktiv.length > 0 && <ul className="space-y-1.5">{aktiv.map((it) => zeile(it))}</ul>}
                  {wartend.length > 0 && (
                    <details className="group">
                      <summary className="flex cursor-pointer list-none items-center gap-1.5 py-1 text-[11px] text-muted-foreground/70 hover:text-foreground">
                        <ChevronRight className="h-3 w-3 transition-transform group-open:rotate-90" />
                        <Clock className="h-3 w-3" /> Wartet ({wartend.length})
                      </summary>
                      <ul className="mt-1.5 space-y-1.5">{wartend.map((it) => zeile(it))}</ul>
                    </details>
                  )}
                  {fertig.length > 0 && (
                    <details className="group">
                      <summary className="flex cursor-pointer list-none items-center gap-1.5 py-1 text-[11px] text-muted-foreground/70 hover:text-foreground">
                        <ChevronRight className="h-3 w-3 transition-transform group-open:rotate-90" />
                        <Check className="h-3 w-3 text-emerald-400/70" /> Fertig ({fertig.length})
                      </summary>
                      <ul className="mt-1.5 space-y-1.5">{fertig.map((it) => zeile(it))}</ul>
                    </details>
                  )}
                </div>
              )
            })()}
          </div>
        )}
        {data?.available && !isLoading && items.length === 0 && (
          // Leerer Zustand als Einladung statt als Feststellung: genau jetzt möchte man
          // wissen, was man überhaupt hineinschreiben kann. Ein Klick füllt das Feld —
          // die Box ordnet den Beispieltext dann selbst ein.
          <div className="space-y-2">
            <p className="text-[11px] text-muted-foreground/70">Noch nichts hier. Zum Beispiel:</p>
            {[
              "Ein kleines Werkzeug, das meine Fotos nach Aufnahmedatum in Ordner sortiert",
              "Rippy soll die Untertitel-Spur automatisch auswählen statt zu fragen",
              "Lohnt sich ein Dashboard für die Stromzähler, oder reicht Home Assistant?",
            ].map((b) => (
              <button key={b} onClick={() => setText(b)}
                className="flex w-full items-center gap-2 rounded-lg border border-border/40 bg-background/20 px-3 py-2 text-left text-xs text-muted-foreground transition-all hover:border-primary/40 hover:text-foreground cursor-pointer">
                <Lightbulb className="h-3.5 w-3.5 shrink-0 text-muted-foreground/50" />
                {b}
              </button>
            ))}
          </div>
        )}
      </div>
      {dialogElement}
    </section>
  )
}

// Eine der drei Auswahl-Karten. Der Text steht AN der Option, nicht in einer Legende
// darunter — man liest nur, was zu dem gehört, was man gerade anschaut.
function Wahl({ k, aktiv, onWahl }: {
  k: { id: string; icon: typeof Hammer; titel: string; text: string }
  aktiv: boolean
  onWahl: () => void
}) {
  return (
    <button
      onClick={onWahl}
      className={cn("rounded-xl border p-3 text-left transition-all cursor-pointer",
        aktiv ? "border-primary/60 bg-primary/5" : "border-border/50 hover:border-border")}>
      <div className="flex items-center gap-2">
        <k.icon className={cn("h-4 w-4 shrink-0", aktiv ? "text-primary" : "text-muted-foreground")} />
        <span className="text-xs font-semibold text-foreground">{k.titel}</span>
      </div>
      <p className="mt-1 text-[11px] leading-relaxed text-muted-foreground">{k.text}</p>
    </button>
  )
}

// Karten-Familie (verkettete Karten = ein Projekt) als Block: Fortschritt oben, dann die
// AKTIVEN Karten in Ausführungs-Reihenfolge, Wartende und Fertige zusammengeklappt.
// Ersetzt für Ketten die flache Erstellzeit-Liste (User: „Reihenfolge komplett durcheinander").
const AKTIV_RANG: Record<string, number> = { blocked: 0, running: 1, review: 2, ready: 3, triage: 4 }

function ProjektGruppe({ projekt, karten, zeile, onArchivAlle, raeumt }: {
  projekt: IdeenProjekt
  karten: IdeenItem[]
  zeile: (it: IdeenItem) => React.ReactNode
  onArchivAlle?: (projekt: string, anzahl: number, was: string) => void
  raeumt?: boolean
}) {
  const [zeigWartende, setZeigWartende] = useState(false)
  const [zeigFertige, setZeigFertige] = useState(false)
  const aktiv = karten
    .filter((i) => AKTIV_RANG[i.status] !== undefined)
    .sort((a, b) => (AKTIV_RANG[a.status] - AKTIV_RANG[b.status]) || ((a.erstellt ?? 0) - (b.erstellt ?? 0)))
  const wartend = karten
    .filter((i) => i.status === "todo" || i.status === "scheduled")
    .sort((a, b) => ((a.wartet_auf?.length ? 1 : 0) - (b.wartet_auf?.length ? 1 : 0)) || ((a.erstellt ?? 0) - (b.erstellt ?? 0)))
  const fertig = karten
    .filter((i) => i.status === "done")
    .sort((a, b) => (b.fertig ?? 0) - (a.fertig ?? 0))
  const anteil = projekt.gesamt > 0 ? Math.round((projekt.fertig / projekt.gesamt) * 100) : 0
  return (
    <div className="space-y-2 rounded-xl border border-border/50 bg-background/20 p-3">
      <div className="flex items-center gap-2">
        <Layers className="h-4 w-4 shrink-0 text-primary/70" />
        <p className="min-w-0 flex-1 truncate text-xs font-semibold text-foreground" title={projekt.titel}>{projekt.titel}</p>
        <span className="shrink-0 text-[10px] text-muted-foreground">{projekt.fertig} von {projekt.gesamt} fertig</span>
      </div>
      <div className="h-1 overflow-hidden rounded-full bg-background/60" title={`${anteil}%`}>
        <div className="h-1 rounded-full bg-primary/70 transition-all" style={{ width: `${anteil}%` }} />
      </div>
      {aktiv.length > 0 && <ul className="space-y-1.5">{aktiv.map((it) => zeile(it))}</ul>}
      {wartend.length > 0 && (
        <div>
          <button
            onClick={() => setZeigWartende((v) => !v)}
            className="flex items-center gap-1.5 text-[11px] font-semibold text-muted-foreground hover:text-foreground transition-colors cursor-pointer">
            {zeigWartende ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
            {wartend.length} Karte{wartend.length === 1 ? "" : "n"} in der Kette — laufen automatisch der Reihe nach
          </button>
          {zeigWartende && <ul className="mt-1.5 space-y-1.5">{wartend.map((it) => zeile(it))}</ul>}
        </div>
      )}
      {fertig.length > 0 && (
        <div>
          <div className="flex items-center gap-2">
            <button
              onClick={() => setZeigFertige((v) => !v)}
              className="flex items-center gap-1.5 text-[11px] font-semibold text-emerald-300/70 hover:text-emerald-300 transition-colors cursor-pointer">
              {zeigFertige ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
              {fertig.length} fertig
            </button>
            {/* Ganze abgeschlossene Kette in einem Zug wegräumen — bei fünf fertigen
                Projekten waren das sonst ~18 Einzelklicks auf das X. */}
            {onArchivAlle && (
              <button
                disabled={raeumt}
                onClick={() => onArchivAlle(projekt.key, fertig.length, `„${projekt.titel}"`)}
                title="Räumt die fertigen Karten dieser Kette weg (Archiv, nichts wird gelöscht)."
                className="flex items-center gap-1 text-[11px] text-muted-foreground/60 transition-colors hover:text-foreground disabled:opacity-50 cursor-pointer">
                {raeumt ? <Loader2 className="h-3 w-3 animate-spin" /> : <X className="h-3 w-3" />}
                wegräumen
              </button>
            )}
          </div>
          {zeigFertige && <ul className="mt-1.5 space-y-1.5">{fertig.map((it) => zeile(it))}</ul>}
        </div>
      )}
    </div>
  )
}

// Konzept einer IDE-Karte: holt es aus dem Gitea-Repo — oder, wenn der Worker keins
// angelegt hat, aus seiner Datei in ~/konzepte (Bug-Fund 21.07.: dann stand hier vorher
// „noch kein Repo hinterlegt", obwohl das Konzept längst fertig war).
// Bei einer fertig durchdachten IDEE hängt unten der Weg nach vorn: „Gefällt mir".
function KonzeptPanel({ id, istIdee, onWeiter }: {
  id: string
  istIdee?: boolean
  onWeiter?: () => void
}) {
  const [data, setData] = useState<IdeenKonzept | null>(null)
  const [fehler, setFehler] = useState(false)
  const [versuch, setVersuch] = useState(0)
  useEffect(() => {
    let weg = false
    setData(null); setFehler(false)
    api<IdeenKonzept>(`/api/ideen/${id}/konzept`)
      .then((r) => { if (!weg) setData(r) })
      .catch(() => { if (!weg) setFehler(true) })
    return () => { weg = true }
  }, [id, versuch])
  return (
    <div className="ml-2 -mt-1">
      <div className="rounded-lg border border-violet-500/25 bg-violet-500/[0.04] p-3">
        <div className="mb-2 flex items-center gap-2">
          <p className="flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-wide text-violet-300">
            <FileText className="h-3 w-3" /> Konzept
          </p>
          {data?.repo && (
            <a href={`https://git.tobisniceshomelab.ddnsfree.com/${data.repo}`}
              className="flex items-center gap-1 text-[10px] text-violet-300/70 hover:text-violet-200">
              <GitFork className="h-3 w-3" /> {data.repo}
            </a>
          )}
          {!data?.repo && data?.quelle === "datei" && (
            <span className="truncate font-mono text-[10px] text-muted-foreground/70"
              title="Der Worker hat kein Repo angelegt — das Konzept liegt als Datei auf der Box.">
              {data.pfad}
            </span>
          )}
          <button onClick={() => setVersuch((v) => v + 1)} title="Neu laden"
            className="ml-auto rounded p-0.5 text-muted-foreground/50 hover:text-foreground cursor-pointer">
            <RefreshCw className="h-3 w-3" />
          </button>
        </div>
        {fehler ? (
          <p className="text-[11px] text-red-400">Konzept nicht ladbar — später nochmal versuchen.</p>
        ) : data === null ? (
          <p className="text-[10px] text-muted-foreground animate-pulse">Konzept wird geladen …</p>
        ) : data.konzept ? (
          <div className="max-h-[28rem] overflow-y-auto pr-1">
            <Markdown text={data.konzept} />
          </div>
        ) : (
          <p className="text-[11px] text-muted-foreground italic">
            {data.error || "Noch kein Konzept — das Projekt wird gerade vorbereitet."}
          </p>
        )}
        {istIdee && data?.konzept && <WeiterLeiste id={id} onFertig={onWeiter} />}
      </div>
    </div>
  )
}

// Was nach dem Lesen passieren kann. Zwei Wege, bewusst nebeneinander statt hintereinander:
// nachschärfen (ein Punkt passt nicht — häufiger Fall, darum oben und offen) oder annehmen.
function WeiterLeiste({ id, onFertig }: { id: string; onFertig?: () => void }) {
  const [ziel, setZiel] = useState<"lxc" | "docker">("lxc")
  const [tiefe, setTiefe] = useState<"simpel" | "gruendlich">("simpel")
  const [busy, setBusy] = useState(false)
  const [neu, setNeu] = useState<{ id: string; art: "projekt" | "runde" } | null>(null)
  const [fehler, setFehler] = useState("")
  const [hinweis, setHinweis] = useState("")
  const ruf = async (pfad: string, rumpf: object, art: "projekt" | "runde") => {
    setBusy(true); setFehler("")
    try {
      const r = await api<{ id: string }>(pfad, { method: "POST", body: JSON.stringify(rumpf) })
      setNeu({ id: r.id, art })
      onFertig?.()
    } catch (e: any) {
      setFehler(String(e?.message || e))
    } finally {
      setBusy(false)
    }
  }
  if (neu) {
    return (
      <p className="mt-3 border-t border-violet-500/20 pt-2 text-[11px] text-emerald-300">
        {neu.art === "projekt" ? (
          <>Projekt-Karte <span className="font-mono">{neu.id}</span> ist angelegt — die Box bereitet
            das Repo vor (Konzept übernimmt sie, sie denkt es nicht neu). Danach öffnest du es in Zed.</>
        ) : (
          <>Überarbeitung <span className="font-mono">{neu.id}</span> läuft — die Box arbeitet deine
            Rückmeldung ein und schreibt die neue Fassung an dieselbe Stelle. Danach hier wieder
            „Konzept" öffnen.</>
        )}
      </p>
    )
  }
  return (
    <div className="mt-3 space-y-3 border-t border-violet-500/20 pt-2.5">
      <div className="space-y-1.5">
        <p className="text-[11px] text-muted-foreground">
          <b className="text-sky-300/90">Passt etwas nicht?</b> Schreib hin, was anders werden soll —
          die Box arbeitet es ein und lässt den Rest stehen. Beliebig oft wiederholbar.
        </p>
        <div className="flex gap-2">
          <input
            value={hinweis}
            onChange={(e) => setHinweis(e.target.value)}
            onKeyDown={(e) => { if (e.key === "Enter" && hinweis.trim().length >= 3 && !busy) ruf("/api/ideen/nachschaerfen", { id, hinweis }, "runde") }}
            placeholder="z. B. „Punkt 3: kein WebSocket, das ist mir zu fragil — nimm Polling“"
            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-sky-400/50 focus:outline-none"
          />
          <button
            onClick={() => ruf("/api/ideen/nachschaerfen", { id, hinweis }, "runde")}
            disabled={busy || hinweis.trim().length < 3}
            className="flex h-8 shrink-0 items-center gap-1.5 rounded-lg border border-sky-500/40 bg-sky-500/10 px-3 text-[10px] font-bold uppercase tracking-wide text-sky-300 transition-all hover:bg-sky-500/20 cursor-pointer disabled:opacity-50">
            {busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <RefreshCw className="h-3.5 w-3.5" />}
            Nachschärfen
          </button>
        </div>
      </div>
      <p className="text-[11px] text-muted-foreground">
        <b className="text-violet-300/90">Passt es so?</b> Dann macht die Box daraus ein startklares
        Repo — Konzept wird übernommen (nicht neu gedacht), dazu Roadmap, AGENTS.md &amp; Co.
        Gebaut wird danach von dir in Zed.
      </p>
      <div className="flex flex-wrap items-center gap-x-3 gap-y-2">
        <span className="text-[11px] text-muted-foreground/70">Ziel</span>
        <div className="flex rounded-lg border border-border/40 p-0.5">
          {(["lxc", "docker"] as const).map((z) => (
            <button key={z} onClick={() => setZiel(z)}
              title={z === "lxc" ? "Dienst in einem eigenen Proxmox-LXC (systemd, kein Docker) — wird automatisch mitgesichert."
                : "Natives Docker: Dockerfile + compose, portabel und pipeline-tauglich."}
              className={cn("rounded px-2 py-0.5 text-[10px] font-semibold transition-all cursor-pointer",
                ziel === z ? "bg-foreground/10 text-foreground" : "text-muted-foreground/50 hover:text-foreground/80")}>
              {z === "lxc" ? "LXC" : "Docker"}
            </button>
          ))}
        </div>
        <span className="text-[11px] text-muted-foreground/70">Aufwand</span>
        <div className="flex rounded-lg border border-border/40 p-0.5">
          {(["simpel", "gruendlich"] as const).map((t) => (
            <button key={t} onClick={() => setTiefe(t)}
              title={t === "simpel" ? "Konzept ist schon gehärtet — nur kurz auf die Bau-Sicht nachschärfen (empfohlen)."
                : "Nochmal volle Kaskade über die Bau-Sicht: mehrere Rollen, Härtetests. Dauert entsprechend."}
              className={cn("rounded px-2 py-0.5 text-[10px] font-semibold transition-all cursor-pointer",
                tiefe === t ? "bg-foreground/10 text-foreground" : "text-muted-foreground/50 hover:text-foreground/80")}>
              {t === "simpel" ? "schlank" : "gründlich"}
            </button>
          ))}
        </div>
        <button
          onClick={() => ruf("/api/ideen/weiterfuehren", { id, ziel, tiefe }, "projekt")}
          disabled={busy}
          className="ml-auto flex h-8 shrink-0 items-center gap-1.5 rounded-lg border border-violet-500/40 bg-violet-500/10 px-3 text-[10px] font-bold uppercase tracking-wide text-violet-300 transition-all hover:bg-violet-500/20 cursor-pointer disabled:opacity-50">
          {busy ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Code2 className="h-3.5 w-3.5" />}
          Gefällt mir — Projekt daraus machen
        </button>
      </div>
      {fehler && <p className="text-[11px] text-red-400">{fehler}</p>}
    </div>
  )
}

// Ergebnis einer FERTIGEN Queue-Aufgabe: die Abschluss-Zusammenfassung des Workers
// (kanban_complete). Lazy — wird erst beim Aufklappen geladen, ändert sich nie mehr.
function ErgebnisPanel({ id }: { id: string }) {
  const [erg, setErg] = useState<IdeenErgebnis | null>(null)
  const [fehler, setFehler] = useState(false)
  // Fehler darf nicht kleben: fällt der erste Versuch in ein Deploy-Fenster
  // (Zentrale startet gerade neu), muss ein Klick neu laden können (Vorfall 16.07.).
  const [versuch, setVersuch] = useState(0)
  useEffect(() => {
    let weg = false
    setFehler(false)
    setErg(null)
    api<IdeenErgebnis>(`/api/ideen/${id}/ergebnis`)
      .then((r) => { if (!weg) setErg(r) })
      .catch(() => { if (!weg) setFehler(true) })
    return () => { weg = true }
  }, [id, versuch])
  const text = erg ? (erg.summary || erg.result) : ""
  return (
    <div className="ml-2 -mt-1">
      <div className="rounded-lg border border-emerald-500/25 bg-emerald-500/5 p-2.5">
        <p className="mb-1 flex items-center gap-1.5 text-[10px] font-bold uppercase tracking-wide text-emerald-300">
          <ClipboardCheck className="h-3 w-3" /> Ergebnis des Workers
        </p>
        {fehler ? (
          <button
            type="button"
            onClick={() => setVersuch((v) => v + 1)}
            className="text-[10px] text-red-400 underline decoration-dotted underline-offset-2 hover:text-red-300"
          >
            Ergebnis nicht ladbar — nochmal versuchen
          </button>
        ) : erg === null ? (
          <p className="text-[10px] text-muted-foreground animate-pulse">Wird geladen …</p>
        ) : text ? (
          <p className="max-h-64 overflow-y-auto whitespace-pre-wrap text-xs leading-relaxed text-foreground/85">{text}</p>
        ) : (
          <p className="text-[10px] text-muted-foreground italic">
            Der Worker hat keine Abschluss-Zusammenfassung hinterlassen (ältere Karte oder Protokollverstoß).
          </p>
        )}
      </div>
    </div>
  )
}

// Die Seite selbst: Kopf + die Queue. Bewusst duenn — die Logik lebt unveraendert in
// IdeenSection, damit dieser Umzug reines Verschieben bleibt und nichts still kippt.
export function IdeenView() {
  return (
    <div className="space-y-6">
      <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">
          Ideen
        </h1>
        <p className="text-sm text-muted-foreground">
          Schreib hin, was entstehen soll — die Box arbeitet es aus. Je nach Modus baut sie es selbst,
          denkt es nur durch, oder legt dir ein startklares Repo fuer Zed an.
        </p>
      </div>
      <IdeenSection />
    </div>
  )
}
