"use client";

import { useEffect, useMemo, useState } from "react";
import {
  AlertCircle,
  ArrowRight,
  Building2,
  CheckCircle2,
  ChevronDown,
  ChevronRight,
  CreditCard,
  Home,
  Landmark,
  ListTodo,
  Pencil,
  Maximize2,
  PiggyBank,
  Table2,
  Plus,
  Settings2,
  ShoppingCart,
  Tornado,
  Trash2,
  TrendingUp,
  Wallet,
} from "lucide-react";
import { Timeline } from "@/components/Timeline";
import { Sparkline } from "@/components/Sparkline";
import { CapitalDistributionDialog, RateDistributionDialog } from "@/components/DistributionDialogs";
import { capitalPot } from "@/lib/distribution";
import {
  isRetirementTransition,
  openTransitionCount,
  decisionsText,
  type DecisionCounts,
  transitionInactive as inactiveAtTransition,
  TRANSITION_CATEGORIES,
  retirementConfirmed,
} from "@/lib/decisions";
import { AHV_REFERENCE_AGE } from "@/lib/constants";
import {
  RETIREMENT_CATEGORIES,
  withRetirementDefaults,
  type RetirementDecision,
} from "@/lib/retirement-decision";
import { RetirementFields } from "@/components/RetirementFields";
import { RetirementPanel } from "@/components/RetirementPanel";
import { Button, EmptyState, InspectorShell, Modal, useConfirm, useToast } from "@/components/ui";
import { ElementDetailDialog, PhaseDetailDialog } from "@/components/DetailView";
import {
  CashTransitionFields,
  ElementDetail,
  ElementPhaseFields,
  ElementTransitionFields,
  cashTransitionSummary,
  isCashTransitionAnswered,
  isTransitionAnswered,
  withCashTransitionDefaults,
  withTransitionDefaults,
  type CellContext,
} from "@/components/ElementDetail";
import { PhaseDetail } from "@/components/PhaseDetail";
import { PlanProfileFields, type ProfileDraft } from "@/components/PlanProfileFields";
import { RetirementAdjuster } from "@/components/RetirementAdjuster";
import { MoneyField, SelectField, TextField } from "@/components/FormField";
import { api } from "@/lib/api-client";
import { formatChf } from "@/lib/format";
import { DATA_SOURCE_OPTIONS, type DataSource } from "@/lib/dataview";
import {
  CATEGORY_LABELS,
  CATEGORY_ORDER,
  PERSON_ONLY_CATEGORIES,
  inheritedPhaseValues,
  num,
  ownerLabel,
  type CashTransitionData,
  type ElementCategory,
  type PhaseData,
  type TransitionData,
} from "@/lib/elements";
import { computePlan, type PhaseComputed, type PlanComputed } from "@/lib/calculations";
import type { ScenarioDiff } from "@/lib/diff";
import type { ElementInput, PlanInput } from "@/lib/types";

const CATEGORY_ICON: Record<ElementCategory, React.ReactNode> = {
  INCOME: <Wallet className="h-4 w-4" />,
  EXPENSE: <ShoppingCart className="h-4 w-4" />,
  AHV: <Landmark className="h-4 w-4" />,
  PENSION_FUND: <Building2 className="h-4 w-4" />,
  PILLAR_3A: <PiggyBank className="h-4 w-4" />,
  REAL_ESTATE: <Home className="h-4 w-4" />,
  OTHER_ASSET: <TrendingUp className="h-4 w-4" />,
  OTHER_DEBT: <CreditCard className="h-4 w-4" />,
};

// Kategorien mit einem Übergangs-Entscheid. AHV ist dabei ein Sonderfall: nur beim
// Pensions-Übergang ist die Beitragskarriere zu prüfen (siehe transitionInactive).
const VALUE_CATEGORIES: ElementCategory[] = [
  "PENSION_FUND",
  "PILLAR_3A",
  "REAL_ESTATE",
  "OTHER_ASSET",
  "OTHER_DEBT",
];

type Column =
  | { kind: "phase"; phase: PhaseComputed }
  | { kind: "transition"; fromPhase: PhaseComputed; toPhase: PhaseComputed };

// Alle Einzel-Bearbeitungen laufen über EIN rechtes Inspector-Panel (statt Modals):
// die Matrix bleibt sichtbar, ein Klick auf eine andere Zelle wechselt den Inhalt.
// Genau ein Panel kann offen sein -- deshalb eine Union statt sechs einzelner Zustände.
type Panel =
  | { kind: "phase"; phaseId: string }
  | { kind: "elementMeta"; elementId: string }
  | { kind: "cell"; elementId: string; phaseId: string }
  | { kind: "transition"; elementId: string; fromPhaseId: string }
  | { kind: "cashTransition"; fromPhaseId: string }
  | { kind: "cashInitial" }
  | { kind: "profile" };

export function PlanView({
  plan,
  computed: planComputed,
  actualComputed = null,
  actualYears = [],
  diff,
  onChanged,
  onOpenSpec,
  onOpenSensitivity,
}: {
  plan: PlanInput;
  computed: PlanComputed;
  // Zweiter Rechenlauf mit den effektiven Werten; null, wenn keine erfasst sind.
  actualComputed?: PlanComputed | null;
  // Kalenderjahre mit Ist-Satz (Marker auf der Zeitachse).
  actualYears?: number[];
  // Abweichungen gegenüber dem Eltern-Szenario; null im Basisszenario (nichts zu markieren).
  diff: ScenarioDiff | null;
  onChanged: () => void;
  onOpenSpec?: (anchor: string) => void;
  onOpenSensitivity?: () => void;
}) {
  // Planzahlen oder effektive Zahlen (inkl. Abweichung). Bewusst zwei Möglichkeiten statt
  // dreier: Plan UND Ist als Rohwerte nebeneinander wären mit nominal/real acht Zahlen je
  // Zelle (siehe SPEZIFIKATION 9.29).
  const [dataSource, setDataSource] = useState<DataSource>("PLAN");
  const hasActuals = actualComputed !== null;
  const computed = dataSource === "ACTUAL" && actualComputed ? actualComputed : planComputed;

  // Planwert derselben Zelle -- Grundlage der Abweichung. In der Plan-Sicht null, dann zeigt
  // die Zelle gar keine Abweichung an (statt eine von 0 zu behaupten).
  const showDeviation = dataSource === "ACTUAL" && actualComputed !== null;
  const planEndOf = (elementId: string | undefined, phaseId: string): number | null => {
    if (!showDeviation || !elementId) return null;
    const ph = planComputed.phases.find((p) => p.id === phaseId);
    const el = ph?.elements.find((e) => e.elementId === elementId);
    return el ? el.endValue : null;
  };
  const confirmDialog = useConfirm();
  const toast = useToast();
  // Markierungs-Klassen: geändert = gelb, neu = grün, entfernt = grau.
  const cellDiff = (elementId: string, phaseId: string) =>
    diff?.phaseCell.has(`${elementId}:${phaseId}`) ? "bg-diff-soft ring-1 ring-inset ring-diff/40" : "";
  const transDiff = (elementId: string, fromPhaseId: string) =>
    diff?.transitionCell.has(`${elementId}:${fromPhaseId}`) ? "bg-diff-soft ring-1 ring-inset ring-diff/40" : "";
  const rowDiff = (elementId: string) => {
    const k = diff?.elementRow.get(elementId);
    return k === "added"
      ? "bg-diff-added-soft"
      : k === "changed"
        ? "bg-diff-soft"
        : "";
  };
  const [panel, setPanel] = useState<Panel | null>(null);
  const [collapsedCats, setCollapsedCats] = useState<Set<ElementCategory>>(new Set());
  const [showAdd, setShowAdd] = useState(false);
  const [addCategory, setAddCategory] = useState<ElementCategory | null>(null);
  const [showAddPhase, setShowAddPhase] = useState(false);
  const [reviewFromPhaseId, setReviewFromPhaseId] = useState<string | null>(null);
  // Offener Verteil-Dialog (Kapital bzw. Spar-/Verzehrquote) -- bewusst ein eigenes Popup,
  // weil beide mehrere Elemente auf einmal bearbeiten.
  const [distribute, setDistribute] = useState<{ kind: "capital" | "rates"; phaseId: string } | null>(null);
  const [valueMode, setValueMode] = useState<ValueMode>("nominal");
  // Matrix oder Pensionierung. Kein URL-Zustand: Es ist eine Arbeitsansicht, kein Ort.
  const [view, setView] = useState<"matrix" | "retirement">("matrix");
  // Die Tour (Start-Knopf, Auto-Start bei Plan-Erstellung, Rendering) liegt seit dem
  // Layout-Umbau in AppShell -- sie liest die data-tour-Ziele im DOM dieser Ansicht.
  // Nur-Lese-Detailansicht (Roadmap Nr. 43). Der Rechenweg wird erst beim Öffnen erzeugt.
  const [detailFor, setDetailFor] = useState<{ kind: "element"; id: string } | { kind: "phase"; id: string } | null>(null);

  // Erklärte Berechnung: bewusst NUR wenn eine Detailansicht offen ist. computePlan ist rein
  // und läuft im Browser -- es braucht dafür weder einen API-Aufruf noch eine grössere
  // Server-Antwort, und das Ergebnis ist per Konstruktion identisch zum Serverergebnis.
  const explained = useMemo(
    () => (detailFor ? computePlan(plan, undefined, { explain: true }) : null),
    [detailFor, plan]
  );

  useEffect(() => {
    const stored = typeof window !== "undefined" ? window.localStorage.getItem(VALUE_MODE_KEY) : null;
    // eslint-disable-next-line react-hooks/set-state-in-effect -- einmalige Übernahme der gespeicherten Wahl
    if (stored === "nominal" || stored === "both" || stored === "real") setValueMode(stored);
  }, []);
  function changeValueMode(m: ValueMode) {
    setValueMode(m);
    if (typeof window !== "undefined") window.localStorage.setItem(VALUE_MODE_KEY, m);
  }

  const columns = useMemo<Column[]>(() => {
    const cols: Column[] = [];
    computed.phases.forEach((p, i) => {
      cols.push({ kind: "phase", phase: p });
      if (i < computed.phases.length - 1) {
        cols.push({ kind: "transition", fromPhase: p, toPhase: computed.phases[i + 1] });
      }
    });
    return cols;
  }, [computed.phases]);

  // Anzeigename einer Person: eigener Name, sonst Fallback "Person A"/"Person B".
  function personLabel(role: string): string {
    const p = plan.persons.find((x) => x.role === role);
    if (p?.name && p.name.trim()) return p.name.trim();
    return role === "PERSON_A" ? "Person A" : "Person B";
  }

  const personAxes = plan.persons.map((p) => ({
    role: p.role,
    label: personLabel(p.role),
    currentAge: p.age,
    retirementAge: p.retirementAge,
    color: p.role === "PERSON_A" ? "var(--person-a)" : "var(--person-b)",
  }));

  const elementsByCategory = useMemo(() => {
    const map = new Map<ElementCategory, ElementInput[]>();
    for (const cat of CATEGORY_ORDER) map.set(cat, []);
    for (const e of [...plan.elements].sort((a, b) => a.orderIndex - b.orderIndex)) {
      map.get(e.category)!.push(e);
    }
    return map;
  }, [plan.elements]);

  function computedElement(phaseId: string, elementId: string) {
    return computed.phases.find((p) => p.id === phaseId)?.elements.find((e) => e.elementId === elementId);
  }

  // isRetirementTransition/transitionInactive/openTransitionCount kommen aus lib/decisions --
  // der Bericht zaehlt mit derselben Regel (sonst meldet er andere Zahlen als die Matrix).

  // Beitragskarriere des Element-Besitzers (nur für AHV relevant).
  function careerFor(element: ElementInput) {
    if (element.category !== "AHV" || !element.ownerRole || element.ownerRole === "HOUSEHOLD") return null;
    const person = plan.persons.find((p) => p.role === element.ownerRole);
    return computed.ahvCareer.find((c) => c.personId === person?.id) ?? null;
  }

  // Anzahl Phasen NACH dieser (für den Fortschreibungs-Warnhinweis).
  function laterPhaseCount(phase: PhaseComputed): number {
    return computed.phases.length - phase.sequenceNumber;
  }

  // Phasen in ihrer Reihenfolge -- Grundlage der Vererbung (Roadmap Nr. 44, Punkt A).
  const orderedPhaseIds = computed.phases.map((p) => p.id);

  // Alter des Element-Besitzers im ersten Jahr einer Phase. Ohne Personenzuordnung gilt
  // Person A, weil die Zeitachse ohnehin an ihr hängt.
  function ownerAgeAtPhaseStart(phase: PhaseComputed, element: ElementInput): number {
    const role = element.ownerRole && element.ownerRole !== "HOUSEHOLD" ? element.ownerRole : "PERSON_A";
    return phase.persons.find((p) => p.role === role)?.startAge ?? 0;
  }

  // Baut den Kontext für eine Phasenzelle.
  function buildPhaseContext(phase: PhaseComputed, element: ElementInput): CellContext {
    const ce = computedElement(phase.id, element.id);
    const ownerWorking =
      element.ownerRole && element.ownerRole !== "HOUSEHOLD"
        ? phase.persons.find((p) => p.role === element.ownerRole)?.working ?? false
        : phase.type !== "PENSION";
    return {
      kind: "phase",
      phaseId: phase.id,
      ownerWorking,
      isConsumption: phase.isConsumption,
      durationYears: phase.durationYears,
      isRetirementTransition: false,
      carriedEndValue: ce?.endValue ?? 0,
      carried: ce?.carried ?? false,
      derivedStart: ce?.baseValue ?? 0,
      derivedMortgage: ce?.mortgageStart ?? 0,
      mortgageEnd: ce?.mortgageEnd ?? 0,
      // Verkehrswert = Eigenkapital + Resthypothek (beides am Phasenende).
      propertyValueEnd: (ce?.endValue ?? 0) + (ce?.mortgageEnd ?? 0),
      deflatorStart: phase.cumulativeInflationStart,
      laterPhaseCount: laterPhaseCount(phase),
      ahvCareer: careerFor(element),
      inheritedValues: inheritedPhaseValues(element.phaseValues, orderedPhaseIds, phase.id),
      ownerAgeStart: ownerAgeAtPhaseStart(phase, element),
      investTargets: [],
      capitalFromTransfer: ce?.capitalFromTransfer ?? 0,
      capitalFromTransferSources: ce?.capitalFromTransferSources ?? [],
    };
  }

  function buildTransitionContext(fromPhase: PhaseComputed, toPhase: PhaseComputed | undefined, element: ElementInput): CellContext {
    const ce = computedElement(fromPhase.id, element.id);
    return {
      kind: "transition",
      phaseId: fromPhase.id,
      ownerWorking: true,
      isConsumption: fromPhase.isConsumption,
      durationYears: fromPhase.durationYears,
      isRetirementTransition: toPhase ? isRetirementTransition(element, fromPhase, toPhase) : false,
      carriedEndValue: ce?.endValue ?? 0,
      carried: ce?.carried ?? false,
      derivedStart: 0,
      derivedMortgage: 0,
      mortgageEnd: ce?.mortgageEnd ?? 0,
      // Verkehrswert = Eigenkapital + Resthypothek (beides am Phasenende).
      propertyValueEnd: (ce?.endValue ?? 0) + (ce?.mortgageEnd ?? 0),
      deflatorStart: fromPhase.cumulativeInflationStart,
      laterPhaseCount: laterPhaseCount(fromPhase),
      ahvCareer: careerFor(element),
      inheritedValues: {},
      ownerAgeStart: ownerAgeAtPhaseStart(fromPhase, element),
      // Ziele fuer die Anlage-Quote beim Kapitalbezug (Punkt C).
      investTargets: investTargetsOf(plan, toPhase),
      capitalFromTransfer: 0,
      capitalFromTransferSources: [],
    };
  }

  // Am Übergang nichts (mehr) zu tun: verkauft/getilgt ODER PK/3a nach der Pensionierung
  // (Besitzer ist zu Beginn der Von-Phase bereits pensioniert -> bereits bezogen/verrentet)
  // ODER AHV ausserhalb des Pensions-Übergangs.
  const transitionInactive = (el: ElementInput, fromPhase: PhaseComputed, toPhase?: PhaseComputed) =>
    inactiveAtTransition(computed, el, fromPhase, toPhase);

  function cashTransitionFor(phaseId: string): CashTransitionData {
    return plan.phases.find((p) => p.id === phaseId)?.cashTransition ?? {};
  }

  // Anzahl offener (noch nicht getroffener) Übergangs-Entscheide an einer Grenze.
  // Der Cash-Entscheid (einmalige Sonderein-/ausgaben) zählt mit.
  const transitionOpenCount = (fromPhase: PhaseComputed, toPhase: PhaseComputed) =>
    openTransitionCount(plan, computed, fromPhase, toPhase);

  // Ist der Übergangs-Entscheid dieses Elements noch offen?
  function transitionUnanswered(el: ElementInput, fromPhase: PhaseComputed, toPhase: PhaseComputed): boolean {
    if (transitionInactive(el, fromPhase, toPhase)) return false;
    const retire = isRetirementTransition(el, fromPhase, toPhase);
    return !isTransitionAnswered(el.category, retire, el.transitionValues[fromPhase.id] ?? {});
  }

  function transitionElements(fromPhase: PhaseComputed, toPhase?: PhaseComputed): ElementInput[] {
    return plan.elements
      .filter((el) => TRANSITION_CATEGORIES.includes(el.category))
      .filter((el) => !transitionInactive(el, fromPhase, toPhase))
      .sort((a, b) => a.orderIndex - b.orderIndex);
  }

  async function handleAddPhase(payload: { name?: string; durationYears?: number }) {
    await api.post(`/api/scenarios/${plan.id}/phases`, payload);
    setShowAddPhase(false);
    onChanged();
  }

  // Kurzfassung der Pensionierung fuer die Matrix-Ansicht: die eine Zahl, die zaehlt, plus
  // der Hinweis auf ungepruefte Vorgaben. Ein Klick fuehrt in den Bildschirm.
  const retirementSummaryText = (() => {
    const r = computed.retirement;
    if (r.gapAnnual === null) return null;
    const unconfirmed = plan.elements.filter(
      (e) => RETIREMENT_CATEGORIES.includes(e.category) && !retirementConfirmed(e)
    ).length;
    const luecke = r.gapAnnual < 0 ? `Rentenluecke ${formatChf(r.gapAnnual)}/Jahr` : `Ueberschuss ${formatChf(r.gapAnnual)}/Jahr`;
    return unconfirmed > 0 ? `${luecke} · ${unconfirmed} Vorgaben ungeprueft` : luecke;
  })();

  const hasPhases = computed.phases.length > 0;
  const firstPhase = computed.phases[0] ?? null;
  const lastPhase = computed.phases[computed.phases.length - 1] ?? null;

  return (
    <div className="flex flex-col gap-5">
      <NextSteps
        plan={plan}
        computed={computed}
        columns={columns}
        openCountFor={transitionOpenCount}
        onReview={(fromPhaseId) => setReviewFromPhaseId(fromPhaseId)}
        onAddElement={(cat) => {
          setAddCategory(cat);
          setShowAdd(true);
        }}
        onAddPhase={() => setShowAddPhase(true)}
        onOpenSensitivity={onOpenSensitivity}
      />

      {/* Kompakter Klotz: Grundprofil (25 %) · Zeitachse (50 %) · Kennzahlen (25 %). Auf
          schmalen Screens stapeln die drei. */}
      <div className="grid grid-cols-1 gap-4 lg:grid-cols-4">
        {/* Grundprofil -- Daten untereinander, «Bearbeiten» oben rechts. */}
        <div
          data-tour="profil"
          className={`flex flex-col gap-2 rounded-xl border px-4 py-3 shadow-sm lg:col-span-1 ${
            diff?.profileChanged ? "border-diff bg-diff-soft" : "border-border bg-surface"
          }`}
        >
          <div className="flex items-start justify-between gap-2">
            <span className="text-xs font-semibold uppercase tracking-wide text-faint">
              Grundprofil{diff?.profileChanged ? " · abweichend" : ""}
            </span>
            <button
              type="button"
              onClick={() => setPanel({ kind: "profile" })}
              className="flex shrink-0 items-center gap-1 rounded-lg border border-border px-2 py-0.5 text-xs font-medium text-muted hover:bg-surface-2"
            >
              <Settings2 className="h-3.5 w-3.5" /> Bearbeiten
            </button>
          </div>
          <div className="flex flex-col gap-1">
            {plan.persons.map((p) => (
              <span key={p.role} className="text-xs text-muted">
                {personLabel(p.role)}: {p.age} J., Pension {p.retirementAge}
              </span>
            ))}
            <span className="text-xs text-muted">Inflation {plan.inflationRateDefault}%</span>
          </div>
        </div>

        {/* Zeitachse */}
        <div data-tour="timeline" className="lg:col-span-2">
          <Timeline
            phases={computed.phases}
            persons={personAxes}
            ruinAge={computed.ruinAge}
            actualYears={actualYears}
            startYear={plan.startYear}
          />
        </div>

        {/* Kennzahlen: Endvermögen nominal + real (+ Ruin, falls vorhanden). */}
        <div data-tour="endwealth" className="flex flex-col justify-center gap-2 rounded-xl border border-border bg-surface px-4 py-3 shadow-sm lg:col-span-1">
          <span className="text-xs font-semibold uppercase tracking-wide text-faint">Endvermögen</span>
          <div>
            <div className="text-[11px] text-muted">Nominal</div>
            <div className="text-lg font-bold text-fg">
              {lastPhase ? formatChf(Math.round(lastPhase.endWealthNominal)) : "—"}
            </div>
          </div>
          <div>
            <div className="text-[11px] text-muted">Real (heutige Kaufkraft)</div>
            <div className="text-lg font-bold text-fg">
              {lastPhase ? formatChf(Math.round(lastPhase.endWealthReal)) : "—"}
            </div>
          </div>
          {computed.ruinAge !== null && (
            <div className="mt-0.5 rounded-lg bg-danger-soft px-2 py-1 text-xs font-medium text-danger">
              Kapital reicht nur bis Alter {computed.ruinAge}
            </div>
          )}
        </div>
      </div>

      {/* Ansicht: Matrix oder Pensionierung. Die Pensionierung steht bewusst gleichrangig
          NEBEN der Matrix und nicht in ihr: Sie ist keine Zelle, sondern eine eigene Frage --
          und die einzige, die man zwischen Szenarien systematisch variiert. */}
      {hasPhases && (
        <div className="flex flex-wrap items-center gap-2">
          <div className="inline-flex rounded-lg border border-border bg-surface p-0.5 text-sm">
            {(["matrix", "retirement"] as const).map((v) => (
              <button
                key={v}
                type="button"
                onClick={() => setView(v)}
                className={`flex items-center gap-1.5 rounded-md px-3 py-1 font-medium ${
                  view === v ? "bg-accent text-accent-fg" : "text-muted hover:bg-surface-2"
                }`}
              >
                {v === "matrix" ? <Table2 className="h-4 w-4" /> : <PiggyBank className="h-4 w-4" />}
                {v === "matrix" ? "Matrix" : "Pensionierung"}
              </button>
            ))}
          </div>
          {view === "matrix" && retirementSummaryText && (
            <button
              type="button"
              onClick={() => setView("retirement")}
              className="truncate text-xs text-muted underline decoration-dotted underline-offset-2 hover:text-fg"
              title="Zur Pensionierung"
            >
              {retirementSummaryText}
            </button>
          )}
        </div>
      )}

      {view === "retirement" && (
        <RetirementPanel plan={plan} computed={computed} onSaved={onChanged} />
      )}

      {/* Anzeige-Umschalter (nominal/real, Plan/Ist) -- direkt über der Matrix, weil er nur
          deren Zahlen steuert. */}
      {hasPhases && view === "matrix" && (
        <div className="flex items-center gap-2">
          <span className="text-xs text-muted">Anzeige</span>
          <div className="inline-flex rounded-lg border border-border bg-surface p-0.5 text-xs">
            {(["nominal", "both", "real"] as ValueMode[]).map((m) => (
              <button
                key={m}
                type="button"
                onClick={() => changeValueMode(m)}
                className={`rounded-md px-2.5 py-1 font-medium ${
                  valueMode === m ? "bg-accent text-accent-fg" : "text-muted hover:bg-surface-2"
                }`}
              >
                {m === "nominal" ? "Nominal" : m === "both" ? "Beide" : "Real"}
              </button>
            ))}
          </div>
          <span className="text-[11px] text-faint">real = kaufkraftbereinigt (Planbeginn)</span>

          {/* Zweite Achse: Datenquelle. Erscheint nur, wenn es überhaupt Ist-Werte gibt --
              sonst wäre es ein Umschalter ohne Gegenstück. */}
          {hasActuals && (
            <>
              <span className="ml-2 text-xs text-muted">Zahlen</span>
              <div className="inline-flex rounded-lg border border-border bg-surface p-0.5 text-xs">
                {DATA_SOURCE_OPTIONS.map((o) => (
                  <button
                    key={o.value}
                    type="button"
                    onClick={() => setDataSource(o.value)}
                    title={o.label}
                    className={`rounded-md px-2.5 py-1 font-medium ${
                      dataSource === o.value ? "bg-accent text-accent-fg" : "text-muted hover:bg-surface-2"
                    }`}
                  >
                    {o.short}
                  </button>
                ))}
              </div>
              {dataSource === "ACTUAL" && (
                <span className="text-[11px] text-faint">Abweichung gegenüber Plan farbig</span>
              )}
            </>
          )}
        </div>
      )}

      {!hasPhases && (
        <EmptyState
          icon={<Plus className="h-6 w-6" />}
          title="Noch keine Lebensphasen"
          text="Deine Planung besteht aus Lebensphasen – Abschnitten, in denen deine Finanzen ähnlich laufen (z. B. «bis zur Pension»). Leg die erste an, danach kommen die finanziellen Elemente."
        >
          <Button onClick={() => setShowAddPhase(true)}>
            <Plus className="h-4 w-4" /> Erste Lebensphase
          </Button>
        </EmptyState>
      )}

      {hasPhases && (
        <div className="flex flex-wrap items-center gap-2">
          <Button size="sm" className="px-3 py-1.5 text-sm" onClick={() => setShowAdd(true)}>
            <Plus className="h-4 w-4" /> Finanzielles Element
          </Button>
          <button
            type="button"
            onClick={() => setShowAddPhase(true)}
            className="flex items-center gap-1.5 rounded-lg border border-dashed border-accent bg-accent-soft px-3 py-1.5 text-sm font-medium text-accent-soft-fg transition-colors hover:bg-accent-soft"
          >
            <Plus className="h-4 w-4" /> Lebensphase
          </button>
          {/* Alle Kategoriegruppen auf einen Schlag auf- oder zuklappen. */}
          <button
            type="button"
            onClick={() =>
              setCollapsedCats((prev) =>
                prev.size === 0 ? new Set(CATEGORY_ORDER.filter((c) => (elementsByCategory.get(c)?.length ?? 0) > 0)) : new Set()
              )
            }
            className="ml-auto flex items-center gap-1.5 rounded-lg border border-border px-2.5 py-1.5 text-xs font-medium text-muted transition-colors hover:bg-surface-2 hover:text-fg"
          >
            {collapsedCats.size === 0 ? (
              <>
                <ChevronRight className="h-3.5 w-3.5" /> Alle zuklappen
              </>
            ) : (
              <>
                <ChevronDown className="h-3.5 w-3.5" /> Alle aufklappen
              </>
            )}
          </button>
        </div>
      )}

      {computed.ruinAge !== null && (
        <div className="flex flex-wrap items-center gap-2 rounded-xl border border-danger bg-danger-soft px-4 py-2 text-sm font-medium text-danger">
          <AlertCircle className="h-4 w-4 shrink-0" />
          <span className="min-w-0 flex-1">
            Kapital aufgebraucht mit Alter {computed.ruinAge} – das Gesamtvermögen (inkl. Cash) fällt danach unter 0.
          </span>
          {onOpenSensitivity && (
            <button
              type="button"
              onClick={onOpenSensitivity}
              className="flex shrink-0 items-center gap-1.5 rounded-lg border border-danger px-2.5 py-1 text-xs font-semibold text-danger transition-colors hover:bg-danger hover:text-white"
            >
              <Tornado className="h-3.5 w-3.5" /> Wirksamsten Hebel finden
            </button>
          )}
        </div>
      )}

      {/* Matrix: eigener Scrollbereich, damit Phasen-Köpfe (oben) UND Elementnamen (links)
          beim Scrollen sichtbar bleiben. */}
      {hasPhases && view === "matrix" && (
        <div data-tour="matrix" className="max-h-[75vh] overflow-auto rounded-xl border border-border bg-surface shadow-sm">
          {/* Feste Spaltenbreiten: Alle Phasenspalten sind gleich breit -- bei einer einzigen
              Phase bleibt die Tabelle dadurch schmal, bei vielen wird horizontal gescrollt.
              Ein w-full haette die Spalten je nach Anzahl unterschiedlich gedehnt. */}
          <table className="border-collapse text-sm">
            <thead>
              <tr>
                <th className="sticky left-0 top-0 z-40 w-56 min-w-56 border-b border-r border-border bg-surface-2 px-3 py-2 text-left text-xs font-semibold uppercase tracking-wide text-faint">
                  Finanzielle Elemente
                </th>
                {columns.map((col) =>
                  col.kind === "phase" ? (
                    <PhaseHeader
                      key={col.phase.id}
                      phase={col.phase}
                      personLabel={personLabel}
                      mode={valueMode}
                      diffKind={diff?.phaseHeader.get(col.phase.id) ?? null}
                      onClick={() => setPanel({ kind: "phase", phaseId: col.phase.id })}
                      onExpand={() => setDetailFor({ kind: "phase", id: col.phase.id })}
                      onDistributeCapital={
                        col.phase.sequenceNumber > 1 ? () => setDistribute({ kind: "capital", phaseId: col.phase.id }) : null
                      }
                      onDistributeRates={() => setDistribute({ kind: "rates", phaseId: col.phase.id })}
                      onDelete={
                        col.phase.sequenceNumber === computed.phases.length
                          ? () => void deletePhase(col.phase.id, col.phase.name)
                          : null
                      }
                      active={panel?.kind === "phase" && panel.phaseId === col.phase.id}
                    />
                  ) : (
                    <TransitionHeader
                      key={`t-${col.fromPhase.id}`}
                      counts={transitionOpenCount(col.fromPhase, col.toPhase)}
                      onClick={() => setReviewFromPhaseId(col.fromPhase.id)}
                    />
                  )
                )}
              </tr>
            </thead>
            <tbody>
              {/* Cash-Zeile (systemseitig, immer sichtbar, read-only). */}
              <tr className="bg-surface-2/50">
                <td data-tour="cash" className="sticky left-0 z-10 border-b border-r border-border bg-surface px-3 py-1.5">
                  <span className="flex items-center gap-1.5 text-xs font-semibold text-fg">
                    <Wallet className="h-4 w-4 text-accent" /> Cash
                  </span>
                  <span className="text-[10px] text-faint">verfügbares Kapital</span>
                </td>
                {columns.map((col) => {
                  const isFirst = col.kind === "phase" && col.phase.sequenceNumber === 1;
                  return col.kind === "phase" ? (
                    <td
                      key={`cash-${col.phase.id}`}
                      onClick={isFirst ? () => setPanel({ kind: "cashInitial" }) : undefined}
                      title={isFirst ? "Cash-Anfangswert bearbeiten" : undefined}
                      className={`border-b border-r border-border px-2 py-1.5 text-center text-xs ${
                        isFirst ? "cursor-pointer hover:bg-accent-soft" : ""
                      } ${col.phase.cashNegative ? "font-semibold text-danger" : "text-fg"} ${
                        isFirst && diff?.cashInitialChanged ? "bg-diff-soft ring-1 ring-inset ring-diff/40" : ""
                      }`}
                    >
                      <span className="whitespace-nowrap">
                        {valStr(col.phase.cashStart, col.phase.cumulativeInflationStart, valueMode)}{" "}
                        <span className="text-faint">→</span>{" "}
                        {valStr(col.phase.cashEnd, col.phase.cumulativeInflationEnd, valueMode)}
                      </span>
                    </td>
                  ) : (
                    (() => {
                      // Übergangszelle der Cash-Zeile: einmalige Sonderein-/ausgaben.
                      const ct = cashTransitionFor(col.fromPhase.id);
                      const open = !isCashTransitionAnswered(ct);
                      return (
                        <td
                          key={`cash-t-${col.fromPhase.id}`}
                          onClick={() => setPanel({ kind: "cashTransition", fromPhaseId: col.fromPhase.id })}
                          title="Einmalige Sonderein-/ausgaben"
                          className={`cursor-pointer border-b border-r border-border px-2 py-1.5 text-center text-[11px] ${
                            open
                              ? "bg-attention font-semibold text-attention-fg"
                              : diff?.cashTransitionCell.has(col.fromPhase.id)
                                ? "bg-diff-soft text-diff ring-1 ring-inset ring-diff/40"
                                : "bg-accent-soft/40 text-accent"
                          }`}
                        >
                          {cashTransitionSummary(ct)}
                        </td>
                      );
                    })()
                  );
                })}
              </tr>
              {CATEGORY_ORDER.map((cat) => {
                const els = elementsByCategory.get(cat)!;
                if (els.length === 0) return null;
                const collapsed = collapsedCats.has(cat);
                return (
                  <FragmentRows key={cat}>
                    <tr className="bg-surface-2">
                      <td
                        className="sticky left-0 z-10 cursor-pointer border-b border-r border-border bg-surface-2 px-3 py-1.5"
                        onClick={() =>
                          setCollapsedCats((prev) => {
                            const next = new Set(prev);
                            if (next.has(cat)) next.delete(cat);
                            else next.add(cat);
                            return next;
                          })
                        }
                      >
                        <span className="flex items-center gap-1.5 text-xs font-semibold text-muted">
                          {collapsed ? <ChevronRight className="h-3.5 w-3.5" /> : <ChevronDown className="h-3.5 w-3.5" />}
                          <span className="text-accent">{CATEGORY_ICON[cat]}</span>
                          {CATEGORY_LABELS[cat]}
                        </span>
                      </td>
                      {/* Zugeklappt: Summe der Elementwerte je Phase -- sonst verschwindet die
                          Information beim Zuklappen. Aufgeklappt bleibt die Zeile leer, weil
                          man die Einzelwerte darunter sieht. */}
                      {collapsed
                        ? columns.map((col) =>
                            col.kind === "phase" ? (
                              <td
                                key={`sum-${cat}-${col.phase.id}`}
                                className="border-b border-r border-border bg-surface-2 px-2 py-1.5 text-center text-xs font-semibold text-muted"
                              >
                                {(() => {
                                  const rows = els
                                    .map((e) => computedElement(col.phase.id, e.id))
                                    .filter((ce): ce is NonNullable<typeof ce> => !!ce && ce.status === "ACTIVE");
                                  if (rows.length === 0) return "";
                                  const start = rows.reduce((sum, ce) => sum + ce.startValue, 0);
                                  const end = rows.reduce((sum, ce) => sum + ce.endValue, 0);
                                  return (
                                    <span className="whitespace-nowrap">
                                      {valStr(start, col.phase.cumulativeInflationStart, valueMode)}{" "}
                                      <span className="text-faint">→</span>{" "}
                                      {valStr(end, col.phase.cumulativeInflationEnd, valueMode)}
                                    </span>
                                  );
                                })()}
                              </td>
                            ) : (
                              <td key={`sum-t-${cat}-${col.fromPhase.id}`} className="border-b border-r border-border bg-surface-2" />
                            )
                          )
                        : <td colSpan={columns.length} className="border-b border-border" />}
                    </tr>
                    {!collapsed &&
                      els.map((el) => (
                        <tr key={el.id} className="hover:bg-surface-2">
                          <td className={`group/row sticky left-0 z-10 border-b border-r border-border px-3 py-1.5 ${rowDiff(el.id) || "bg-surface"}`}>
                            <div className="flex items-center gap-1 text-xs font-medium text-fg">
                              <span className="min-w-0 flex-1 truncate">{el.name}</span>
                              {diff?.elementRow.get(el.id) === "added" && (
                                <span className="rounded bg-diff-added px-1 text-[9px] font-semibold uppercase text-white">neu</span>
                              )}
                              {/* Aktionen zum Element selbst (phasenunabhängig): umbenennen,
                                  löschen, Detailansicht -- bewusst IMMER sichtbar, damit sie
                                  auch ohne Maus (Touch) erreichbar sind. */}
                              <button
                                type="button"
                                aria-label={`${el.name} umbenennen`}
                                title="Umbenennen und Zuordnung ändern"
                                onClick={(e) => {
                                  e.stopPropagation();
                                  setPanel({ kind: "elementMeta", elementId: el.id });
                                }}
                                className="shrink-0 rounded p-0.5 text-faint hover:bg-accent-soft hover:text-accent"
                              >
                                <Pencil className="h-3 w-3" />
                              </button>
                              <button
                                type="button"
                                aria-label={`${el.name} löschen`}
                                title="Element löschen"
                                onClick={(e) => {
                                  e.stopPropagation();
                                  void deleteElement(el.id, el.name);
                                }}
                                className="shrink-0 rounded p-0.5 text-faint hover:bg-danger-soft hover:text-danger"
                              >
                                <Trash2 className="h-3 w-3" />
                              </button>
                              <button
                                type="button"
                                aria-label={`Detailansicht ${el.name}`}
                                title="Detailansicht (nur lesen)"
                                onClick={(e) => {
                                  e.stopPropagation();
                                  setDetailFor({ kind: "element", id: el.id });
                                }}
                                className="shrink-0 rounded p-0.5 text-faint hover:bg-accent-soft hover:text-accent"
                              >
                                <Maximize2 className="h-3 w-3" />
                              </button>
                            </div>
                            {el.ownerRole && el.ownerRole !== "HOUSEHOLD" && (
                              <div className="text-[10px] text-faint">{personLabel(el.ownerRole)}</div>
                            )}
                            {/* Trend über alle Planjahre -- die per-Jahr-Werte existieren seit 0.11. */}
                            <Sparkline
                              values={computed.phases.flatMap(
                                (ph) => ph.elements.find((e) => e.elementId === el.id)?.yearly?.map((y) => y.value) ?? []
                              )}
                              className="mt-0.5"
                            />
                          </td>
                          {columns.map((col) => {
                            if (col.kind === "phase") {
                              const ce = computedElement(col.phase.id, el.id);
                              return (
                                <td
                                  key={col.phase.id}
                                  onClick={() => setPanel({ kind: "cell", elementId: el.id, phaseId: col.phase.id })}
                                  title={cellDiff(el.id, col.phase.id) ? "Weicht von der Vorlage ab" : undefined}
                                  className={`cursor-pointer border-b border-r border-border px-2 py-1.5 text-center text-xs ${
                                    ce?.locked ? "text-faint" : "text-fg"
                                  } ${cellDiff(el.id, col.phase.id)}`}
                                >
                                  {phaseCellContent(ce, col.phase, valueMode, planEndOf(ce?.elementId, col.phase.id))}
                                </td>
                              );
                            }
                            const locked = TRANSITION_CATEGORIES.includes(el.category) && transitionInactive(el, col.fromPhase, col.toPhase);
                            const canTransition = TRANSITION_CATEGORIES.includes(el.category) && !locked;
                            const open = canTransition && transitionUnanswered(el, col.fromPhase, col.toPhase);
                            return (
                              <td
                                key={`t-${col.fromPhase.id}`}
                                onClick={() =>
                                  canTransition &&
                                  setPanel({ kind: "transition", elementId: el.id, fromPhaseId: col.fromPhase.id })
                                }
                                title={transDiff(el.id, col.fromPhase.id) ? "Weicht von der Vorlage ab" : undefined}
                                className={`border-b border-r border-border px-2 py-1.5 text-center text-[11px] ${
                                  canTransition ? "cursor-pointer" : "text-faint"
                                } ${
                                  // Offene Entscheide tragen die Attention-Farbe (getrennt vom
                                  // Akzent: "hier fehlt was" ist nicht "hier kannst du klicken").
                                  open
                                    ? "bg-attention font-semibold text-attention-fg"
                                    : transDiff(el.id, col.fromPhase.id)
                                      ? "bg-diff-soft text-diff ring-1 ring-inset ring-diff/40"
                                      : canTransition
                                        ? "bg-accent-soft/40 text-accent"
                                        : ""
                                }`}
                              >
                                {canTransition ? transitionSummary(el, col.fromPhase, col.toPhase) : locked ? "–" : "→"}
                              </td>
                            );
                          })}
                        </tr>
                      ))}
                  </FragmentRows>
                );
              })}
              {/* Geisterzeilen: in der Vorlage vorhanden, in diesem Szenario gelöscht. */}
              {(diff?.removedElements ?? []).map((r) => (
                <tr key={`removed-${r.id}`} className="bg-diff-removed-soft">
                  <td className="sticky left-0 z-10 border-b border-r border-border bg-diff-removed-soft px-3 py-1.5">
                    <div className="flex items-center gap-1 truncate text-xs font-medium text-diff-removed line-through">
                      {r.name}
                    </div>
                    <div className="text-[10px] text-diff-removed">{CATEGORY_LABELS[r.category]} · entfernt</div>
                  </td>
                  <td colSpan={columns.length} className="border-b border-border px-3 py-1.5 text-center text-[11px] text-diff-removed">
                    In diesem Szenario entfernt
                  </td>
                </tr>
              ))}
              {plan.elements.length === 0 && (diff?.removedElements.length ?? 0) === 0 && (
                <tr>
                  <td className="sticky left-0 bg-surface px-3 py-4 text-xs text-faint" colSpan={columns.length + 1}>
                    Noch keine finanziellen Elemente. Füge oben dein erstes Element hinzu.
                  </td>
                </tr>
              )}
            </tbody>
          </table>
        </div>
      )}

      {/* Inspector-Panel: alle Einzel-Bearbeitungen. Der key erzwingt beim Wechsel der
          Auswahl einen Neuaufbau, damit kein Formular-Zustand der vorherigen Zelle
          übrig bleibt (gleiches Muster wie zuvor bei den Dialogen). */}
      {panel && renderPanel()}

      {showAdd && firstPhase && (
        <AddElementDialog
          plan={plan}
          firstPhase={firstPhase}
          initialCategory={addCategory}
          onClose={() => {
            setShowAdd(false);
            setAddCategory(null);
          }}
          onCreated={() => {
            setShowAdd(false);
            setAddCategory(null);
            toast("success", "Element angelegt.");
            onChanged();
          }}
        />
      )}

      {showAddPhase && (
        <AddPhaseDialog
          maxDurationYears={nextPhaseCap()}
          onClose={() => setShowAddPhase(false)}
          onCreate={handleAddPhase}
        />
      )}

      {reviewFromPhaseId && (() => {
        const fromPhase = computed.phases.find((p) => p.id === reviewFromPhaseId);
        if (!fromPhase) return null;
        const toIndex = computed.phases.findIndex((p) => p.id === fromPhase.id) + 1;
        const toPhase = computed.phases[toIndex];
        const els = transitionElements(fromPhase, toPhase);
        return (
          <TransitionReviewDialog
            plan={plan}
            computed={computed}
            fromPhase={fromPhase}
            toPhase={toPhase}
            elements={els}
            initialCash={cashTransitionFor(fromPhase.id)}
            buildContext={(el) => buildTransitionContext(fromPhase, toPhase, el)}
            isRetirement={(el) => (toPhase ? isRetirementTransition(el, fromPhase, toPhase) : false)}
            onClose={() => setReviewFromPhaseId(null)}
            onSaved={() => {
              setReviewFromPhaseId(null);
              onChanged();
            }}
          />
        );
      })()}

      {detailFor?.kind === "element" && explained && (() => {
        const el = plan.elements.find((e) => e.id === detailFor.id);
        if (!el) return null;
        return (
          <ElementDetailDialog
            key={`det-${detailFor.id}`}
            elementId={el.id}
            name={el.name}
            category={el.category}
            computed={explained}
            onClose={() => setDetailFor(null)}
            onOpenSpec={onOpenSpec}
          />
        );
      })()}

      {detailFor?.kind === "phase" && explained && (() => {
        const ph = explained.phases.find((p) => p.id === detailFor.id);
        if (!ph) return null;
        return (
          <PhaseDetailDialog
            key={`detph-${detailFor.id}`}
            phase={ph}
            computed={computed}
            isFirst={ph.sequenceNumber === 1}
            onClose={() => setDetailFor(null)}
            onOpenSpec={onOpenSpec}
          />
        );
      })()}

      {distribute?.kind === "capital" && (
        <CapitalDistributionDialog
          key={`cap-${distribute.phaseId}`}
          plan={plan}
          computed={computed}
          phaseId={distribute.phaseId}
          onClose={() => setDistribute(null)}
          onSaved={() => {
            setDistribute(null);
            toast("success", "Kapital verteilt.");
            onChanged();
          }}
        />
      )}

      {distribute?.kind === "rates" && (
        <RateDistributionDialog
          key={`rate-${distribute.phaseId}`}
          plan={plan}
          computed={computed}
          phaseId={distribute.phaseId}
          onClose={() => setDistribute(null)}
          onSaved={() => {
            setDistribute(null);
            toast("success", "Raten gespeichert.");
            onChanged();
          }}
        />
      )}
    </div>
  );

  // Nächste Phasen-Kappung (für das Phase-Popup).
  // Element loeschen -- neu auch direkt aus der Zeile heraus.
  async function deleteElement(elementId: string, elementName: string) {
    const ok = await confirmDialog({
      title: "Element löschen?",
      message: `«${elementName}» wird aus ALLEN Lebensphasen entfernt, samt Werten und Übergangs-Entscheiden.`,
      confirmLabel: "Endgültig löschen",
      danger: true,
    });
    if (!ok) return;
    try {
      await api.delete(`/api/elements/${elementId}`);
      setPanel(null);
      onChanged();
      toast("success", "Element gelöscht.");
    } catch (e) {
      toast("error", e instanceof Error ? e.message : "Löschen fehlgeschlagen.");
    }
  }

  async function deletePhase(phaseId: string, phaseName: string) {
    const ok = await confirmDialog({
      title: "Lebensphase löschen?",
      message: `«${phaseName}» wird mit allen erfassten Werten und Übergangs-Entscheiden entfernt.`,
      confirmLabel: "Endgültig löschen",
      danger: true,
    });
    if (!ok) return;
    try {
      await api.delete(`/api/phases/${phaseId}`);
      setPanel(null);
      onChanged();
      toast("success", "Lebensphase gelöscht.");
    } catch (e) {
      toast("error", e instanceof Error ? e.message : "Löschen fehlgeschlagen.");
    }
  }

  function nextPhaseCap(): number | null {
    // Simpel aus den Personen ableiten (Jahre nach Planbeginn = Summe der Dauern).
    const yearsBefore = plan.phases.reduce((s, p) => s + p.durationYears, 0);
    const caps = plan.persons
      .map((p) => p.retirementAge - (p.age + yearsBefore))
      .filter((d) => d > 0);
    return caps.length > 0 ? Math.min(...caps) : null;
  }

  // Inhalt des Inspector-Panels je nach Auswahl.
  function renderPanel() {
    if (!panel) return null;
    const close = () => setPanel(null);
    const closeAndRefresh = () => {
      setPanel(null);
      onChanged();
    };

    switch (panel.kind) {
      case "elementMeta": {
        const el = plan.elements.find((e) => e.id === panel.elementId);
        if (!el) return null;
        return (
          <InspectorShell key={`meta-${panel.elementId}`} title={`Element: ${el.name}`} onClose={close}>
            <ElementMetaPanel
              element={el}
              plan={plan}
              personLabel={personLabel}
              onSaved={closeAndRefresh}
              onDelete={() => deleteElement(el.id, el.name)}
            />
          </InspectorShell>
        );
      }
      case "phase": {
        const phase = computed.phases.find((p) => p.id === panel.phaseId);
        const phaseInput = plan.phases.find((p) => p.id === panel.phaseId);
        if (!phase || !phaseInput) return null;
        return (
          <InspectorShell key={`ph-${panel.phaseId}`} title={`Lebensphase: ${phase.name}`} onClose={close}>
            <PhaseDetail
              phase={phaseInput}
              allPhases={plan.phases}
              maxDurationYears={phase.maxDurationYears}
              isLast={phase.sequenceNumber === computed.phases.length}
              laterPhaseCount={laterPhaseCount(phase)}
              onSaved={closeAndRefresh}
              onDeleted={closeAndRefresh}
            />
          </InspectorShell>
        );
      }
      case "cell": {
        const element = plan.elements.find((e) => e.id === panel.elementId);
        const phase = computed.phases.find((p) => p.id === panel.phaseId);
        if (!element || !phase) return null;
        return (
          <InspectorShell
            key={`${panel.elementId}-${panel.phaseId}`}
            title={element.name}
            subtitle={`Lebensphase: ${phase.name}`}
            onClose={close}
          >
            <ElementDetail
              element={element}
              context={buildPhaseContext(phase, element)}
              phaseData={element.phaseValues[phase.id] ?? {}}
              // Für die Übernahme von Raten über Phasen hinweg: alle Phasen und die dort
              // bereits erfassten Werte (sie müssen beim Übertragen erhalten bleiben).
              allPhases={plan.phases.map((p) => ({ id: p.id, sequenceNumber: p.sequenceNumber }))}
              phaseDataByPhase={element.phaseValues}
              transitionData={{}}
              onSaved={closeAndRefresh}
              onDeleteElement={() => {
                setPanel(null);
                deleteElement(element.id, element.name);
              }}
            />
          </InspectorShell>
        );
      }
      case "transition": {
        const element = plan.elements.find((e) => e.id === panel.elementId);
        const fromPhase = computed.phases.find((p) => p.id === panel.fromPhaseId);
        if (!element || !fromPhase) return null;
        const toPhase = computed.phases[computed.phases.findIndex((p) => p.id === fromPhase.id) + 1];
        return (
          <TransitionCellPanel
            plan={plan}
            computed={computed}
            key={`${panel.elementId}-${panel.fromPhaseId}`}
            element={element}
            fromPhase={fromPhase}
            toPhase={toPhase}
            context={buildTransitionContext(fromPhase, toPhase, element)}
            onClose={close}
            onSaved={closeAndRefresh}
          />
        );
      }
      case "cashTransition": {
        const fromPhase = computed.phases.find((p) => p.id === panel.fromPhaseId);
        if (!fromPhase) return null;
        const toPhase = computed.phases[computed.phases.findIndex((p) => p.id === fromPhase.id) + 1];
        return (
          <CashTransitionPanel
            key={`ct-${panel.fromPhaseId}`}
            fromPhase={fromPhase}
            toPhase={toPhase}
            initial={cashTransitionFor(fromPhase.id)}
            onClose={close}
            onSaved={closeAndRefresh}
          />
        );
      }
      case "cashInitial":
        return <CashInitialPanel key="cash-init" plan={plan} onClose={close} onSaved={closeAndRefresh} />;
      case "profile":
        return <ProfilePanel key="profile" plan={plan} onClose={close} onSaved={closeAndRefresh} />;
    }
  }


  function transitionSummary(el: ElementInput, fromPhase: PhaseComputed, toPhase: PhaseComputed): string {
    const td = el.transitionValues[fromPhase.id] ?? {};
    switch (el.category) {
      case "AHV":
        return td.reviewed === true ? "Geprüft" : "?";
      case "OTHER_ASSET":
        return td.decision === "SELL"
          ? "Verkauf"
          : td.decision === "PARTIAL"
            ? `Teilverkauf −${formatChf(num(td.partialSaleAmount))}`
            : td.decision === "HOLD"
              ? "Halten"
              : "?";
      case "REAL_ESTATE":
        if (td.decision === "SELL") return "Verkauf";
        if (td.decision === undefined) return "?";
        return num(td.extraAmortization) > 0 ? `Sonderamort. −${formatChf(num(td.extraAmortization))}` : "Halten";
      case "PENSION_FUND":
        if (isRetirementTransition(el, fromPhase, toPhase)) {
          return td.payoutMode === "CAPITAL" ? "Kapital" : td.payoutMode === "COMBI" ? "Kombi" : td.payoutMode === "PENSION" ? "Rente" : "?";
        }
        if (td.withdrawalMode === undefined) return "?";
        return td.withdrawalMode === "AMOUNT" && num(td.withdrawal) > 0 ? `−${formatChf(num(td.withdrawal))}` : "Kein Bezug";
      case "PILLAR_3A":
        if (isRetirementTransition(el, fromPhase, toPhase)) return "Bezug";
        if (td.withdrawalMode === undefined) return "?";
        return td.withdrawalMode === "AMOUNT" && num(td.withdrawal) > 0 ? `−${formatChf(num(td.withdrawal))}` : "Kein Bezug";
      case "OTHER_DEBT":
        return num(td.immediateRepayment) > 0 ? "Tilgung" : "→";
      default:
        return "→";
    }
  }
}

// Zellinhalt: Start- UND Zielwert für wertbehaftete Elemente, sonst die Kennzahl.
const START_END_CATEGORIES: ElementCategory[] = [...VALUE_CATEGORIES, "INCOME", "EXPENSE"];

type ValueMode = "nominal" | "both" | "real";
const VALUE_MODE_KEY = "fpt-value-mode";

// Realwert = nominal / kumulierte Inflation (Kaufkraft zum Planbeginn).
function realOf(nominal: number, deflator: number): number {
  return Math.round(nominal / (deflator || 1));
}

// Reiner Text (fuer Titel/Tooltips und einzeilige Faelle).
function valStr(nominal: number, deflator: number, mode: ValueMode): string {
  if (mode === "real") return formatChf(realOf(nominal, deflator));
  if (mode === "both") return `${formatChf(nominal)} (${formatChf(realOf(nominal, deflator))})`;
  return formatChf(nominal);
}

// Im Modus "Beide" steht der Realwert in Klammern in einer EIGENEN Zeile unter dem nominalen
// Wert -- nebeneinander wird die Zeile zu lang und die Spalten unnoetig breit. Der Pfeil
// wiederholt sich auf der zweiten Zeile, damit der Zeitbezug Start -> Ende erhalten bleibt.
function ValuePair({
  start,
  end,
  deflatorStart,
  deflatorEnd,
  mode,
  devEnd,
}: {
  start: number;
  end: number;
  deflatorStart: number;
  deflatorEnd: number;
  mode: ValueMode;
  // Abweichung des ENDwerts gegenüber dem Plan (nur in der Ist-Sicht gesetzt). Bewusst nur
  // ein Wert statt Start und Ende: Die Zelle soll nicht zur zweiten Tabelle werden.
  devEnd?: number | null;
}) {
  const arrow = <span className="text-faint">→</span>;
  return (
    <span className="inline-flex flex-col">
      <span className="whitespace-nowrap tabular-nums">
        {mode === "real" ? formatChf(realOf(start, deflatorStart)) : formatChf(start)} {arrow}{" "}
        {mode === "real" ? formatChf(realOf(end, deflatorEnd)) : formatChf(end)}
        <Deviation value={devEnd} deflator={mode === "real" ? deflatorEnd : 1} />
      </span>
      {mode === "both" && (
        <span className="whitespace-nowrap tabular-nums text-faint">
          ({formatChf(realOf(start, deflatorStart))}) {arrow} ({formatChf(realOf(end, deflatorEnd))})
          <Deviation value={devEnd} deflator={deflatorEnd} />
        </span>
      )}
    </span>
  );
}

// Die Abweichung trägt als einziges Element Farbe -- würde man die Beträge selbst einfärben,
// entstünde ein Ampelteppich, in dem nichts mehr heraussticht.
function Deviation({ value, deflator = 1 }: { value?: number | null; deflator?: number }) {
  if (value == null || Math.round(value / (deflator || 1)) === 0) return null;
  const v = Math.round(value / (deflator || 1));
  return (
    <span className={`ml-1.5 font-semibold ${v > 0 ? "text-success" : "text-danger"}`}>
      {v > 0 ? "▲ +" : "▼ −"}
      {formatChf(Math.abs(v))}
    </span>
  );
}

// Einzelwert, gleiche Konvention.
function ValueSingle({ value, deflator, mode }: { value: number; deflator: number; mode: ValueMode }) {
  return (
    <span className="inline-flex flex-col">
      <span className="whitespace-nowrap tabular-nums">
        {mode === "real" ? formatChf(realOf(value, deflator)) : formatChf(value)}
      </span>
      {mode === "both" && (
        <span className="whitespace-nowrap tabular-nums text-faint">({formatChf(realOf(value, deflator))})</span>
      )}
    </span>
  );
}

function phaseCellContent(
  ce: ReturnType<PhaseComputed["elements"]["find"]> | undefined,
  phase: PhaseComputed,
  mode: ValueMode,
  // Endwert desselben Elements in derselben Phase laut PLAN -- null in der Plan-Sicht.
  planEnd?: number | null
): React.ReactNode {
  if (!ce) return "–";
  if (ce.note) return ce.note;
  const isFlow = ce.category === "INCOME" || ce.category === "EXPENSE";
  const dS = phase.cumulativeInflationStart;
  const dE = isFlow ? phase.flowDeflatorEnd : phase.cumulativeInflationEnd;
  const devEnd = planEnd == null ? null : ce.endValue - planEnd;
  if (START_END_CATEGORIES.includes(ce.category) && (ce.startValue !== 0 || ce.endValue !== 0)) {
    return (
      <ValuePair start={ce.startValue} end={ce.endValue} deflatorStart={dS} deflatorEnd={dE} mode={mode} devEnd={devEnd} />
    );
  }
  if ((ce.category === "AHV" || ce.category === "PENSION_FUND") && ce.startValue !== 0) {
    return (
      <span className="inline-flex flex-col">
        <span className="whitespace-nowrap">Rente {mode === "real" ? formatChf(realOf(ce.startValue, dS)) : formatChf(ce.startValue)}</span>
        {mode === "both" && (
          <span className="whitespace-nowrap tabular-nums text-faint">({formatChf(realOf(ce.startValue, dS))})</span>
        )}
      </span>
    );
  }
  return ce.summary || "–";
}

function PhaseHeader({
  phase,
  personLabel,
  mode,
  diffKind,
  onClick,
  onExpand,
  onDistributeCapital,
  onDistributeRates,
  onDelete,
  active,
}: {
  phase: PhaseComputed;
  personLabel: (role: string) => string;
  mode: ValueMode;
  diffKind: "changed" | "added" | "removed" | null;
  onClick: () => void;
  onExpand: () => void;
  // null = Phase 1: dort gibt es kein verteilbares Übergangs-Kapital (additionalInvestment
  // wird in der ersten Phase von der Berechnung ignoriert, dort zählt der Startwert).
  onDistributeCapital: (() => void) | null;
  onDistributeRates: () => void;
  // null = nicht die letzte Phase (nur die letzte lässt sich löschen).
  onDelete: (() => void) | null;
  active: boolean;
}) {
  const quotaLabel = phase.isConsumption ? "Verzehrquote" : "Sparquote";
  const pot = capitalPot(phase);
  const dS = phase.cumulativeInflationStart;
  const dE = phase.cumulativeInflationEnd; // Bestandswerte (Cash, Vermögen)
  const dF = phase.flowDeflatorEnd; // Flow-Werte (Einkommen, Ausgaben, Quote)
  return (
    <th
      onClick={onClick}
      className={`group/ph sticky top-0 z-30 w-52 min-w-52 max-w-52 cursor-pointer border-b border-r border-border px-2 py-2 text-left align-top ${
        active
          ? "bg-accent-soft"
          : diffKind === "added"
            ? "bg-diff-added-soft"
            : diffKind === "changed"
              ? "bg-diff-soft"
              : "bg-surface"
      }`}
    >
      <div className="flex items-center gap-1">
        <span className="min-w-0 flex-1 truncate text-xs font-semibold text-fg">{phase.name}</span>
        {diffKind === "added" && (
          <span className="rounded bg-diff-added px-1 text-[9px] font-semibold uppercase text-white">neu</span>
        )}
        {phase.cashNegative ? (
          <AlertCircle className="h-3.5 w-3.5 shrink-0 text-danger" />
        ) : (
          <CheckCircle2 className="h-3.5 w-3.5 shrink-0 text-success" />
        )}
        {/* Aktionen zur Phase. Der Kopf selbst öffnet ebenfalls das Bearbeiten-Panel; die
            Knöpfe müssen das Event deshalb stoppen, sonst gingen zwei Dinge gleichzeitig auf.
            Bewusst IMMER sichtbar (Touch hat kein Hover). */}
        <button
          type="button"
          aria-label={`${phase.name} bearbeiten`}
          title="Bezeichnung und Dauer ändern"
          onClick={(e) => {
            e.stopPropagation();
            onClick();
          }}
          className="shrink-0 rounded p-0.5 text-faint hover:bg-accent-soft hover:text-accent"
        >
          <Pencil className="h-3 w-3" />
        </button>
        {onDelete && (
          <button
            type="button"
            aria-label={`${phase.name} löschen`}
            title="Lebensphase löschen"
            onClick={(e) => {
              e.stopPropagation();
              onDelete();
            }}
            className="shrink-0 rounded p-0.5 text-faint hover:bg-danger-soft hover:text-danger"
          >
            <Trash2 className="h-3 w-3" />
          </button>
        )}
        <button
          type="button"
          aria-label={`Detailansicht ${phase.name}`}
          title="Detailansicht (nur lesen)"
          onClick={(e) => {
            e.stopPropagation();
            onExpand();
          }}
          className="shrink-0 rounded p-0.5 text-faint hover:bg-accent-soft hover:text-accent"
        >
          <Maximize2 className="h-3 w-3" />
        </button>
      </div>
      <div className="mt-0.5 flex flex-wrap gap-1">
        <span className="rounded bg-surface-2 px-1 text-[10px] text-muted">
          {phase.type === "ERWERB" ? "Erwerb" : phase.type === "PENSION" ? "Pension" : "Misch"}
        </span>
        <span className="text-[10px] text-faint">{phase.durationYears} J.</span>
      </div>
      <div className="mt-1 flex flex-col gap-1 text-[10px] leading-tight text-muted">
        <div>
          {phase.persons.map((p) => (
            <div key={p.role} className="whitespace-nowrap text-faint">
              {personLabel(p.role)} {p.startAge} → {p.endAge}
            </div>
          ))}
        </div>

        {/* Verfügbares Kapital -- die Grösse, die man für die Planung DIESER Phase braucht.
            Nur zeigen, wenn überhaupt etwas da ist. */}
        {onDistributeCapital && pot.total > 0 && (
          <div className="rounded-md bg-surface-2 px-1.5 py-1">
            <div className="flex items-baseline justify-between gap-2 whitespace-nowrap">
              <span className="font-medium text-fg">Verfügbares Kapital</span>
              <ValueSingle value={pot.total} deflator={dS} mode={mode} />
            </div>
            {pot.allocatedInvestments + pot.allocatedRepayments > 0 && (
              <div className="flex items-baseline justify-between gap-2 whitespace-nowrap pl-2 text-faint">
                <span>davon verteilt</span>
                <ValueSingle value={pot.allocatedInvestments + pot.allocatedRepayments} deflator={dS} mode={mode} />
              </div>
            )}
            <div className="flex items-baseline justify-between gap-2 whitespace-nowrap pl-2 text-faint">
              <span>Rest auf Cash</span>
              <ValueSingle value={pot.rest} deflator={dS} mode={mode} />
            </div>
            <button
              type="button"
              onClick={(e) => {
                e.stopPropagation();
                onDistributeCapital();
              }}
              className="mt-1 w-full rounded border border-accent px-1.5 py-0.5 text-[10px] font-semibold text-accent transition-colors hover:bg-accent hover:text-accent-fg"
            >
              Kapital verteilen
            </button>
          </div>
        )}

        {/* Spar- bzw. Verzehrquote */}
        <div className="rounded-md bg-surface-2 px-1.5 py-1">
          <div className="flex items-baseline justify-between gap-2 whitespace-nowrap">
            <span className={`font-medium ${phase.isConsumption ? "text-danger" : "text-fg"}`}>{quotaLabel}</span>
            <ValuePair start={phase.quotaStart} end={phase.quotaEnd} deflatorStart={dS} deflatorEnd={dF} mode={mode} />
          </div>
          <button
            type="button"
            onClick={(e) => {
              e.stopPropagation();
              onDistributeRates();
            }}
            className="mt-1 w-full rounded border border-accent px-1.5 py-0.5 text-[10px] font-semibold text-accent transition-colors hover:bg-accent hover:text-accent-fg"
          >
            {phase.isConsumption ? "Bezug verteilen" : "Sparquote verteilen"}
          </button>
        </div>

        <div className="flex items-baseline justify-between gap-2 whitespace-nowrap font-medium text-fg">
          <span>Vermögen</span>
          <ValuePair
            start={phase.startWealthNominal}
            end={phase.endWealthNominal}
            deflatorStart={dS}
            deflatorEnd={dE}
            mode={mode}
          />
        </div>

        {(phase.oneOffInflow > 0 || phase.oneOffOutflow > 0) && (
          <div className={`whitespace-nowrap ${phase.oneOffOutflow > phase.oneOffInflow ? "text-danger" : "text-success"}`}>
            {phase.oneOffInflow > 0 ? `+ ${phase.oneOffInflowLabel ?? "Zufluss"}` : ""}
            {phase.oneOffInflow > 0 && phase.oneOffOutflow > 0 ? " · " : ""}
            {phase.oneOffOutflow > 0 ? `− ${phase.oneOffOutflowLabel ?? "Kosten"}` : ""}
          </div>
        )}
        {/* Alles Weitere (Einkommen, Ausgaben, Raten im Detail, Kapitalinvestitionen) steht
            in der Detailansicht -- erreichbar über das Expand-Icon oben. */}
      </div>
    </th>
  );
}

function TransitionHeader({ counts, onClick }: { counts: DecisionCounts; onClick: () => void }) {
  // Eine ungeprüfte Vorgabe ist kein vergessenes Eingabefeld -- sie wird deshalb eigens
  // benannt und in gedeckterem Ton gezeigt (SPEZIFIKATION 3.5.3).
  const done = counts.open === 0 && counts.unconfirmed === 0;
  const onlyDefaults = counts.open === 0 && counts.unconfirmed > 0;
  return (
    <th
      onClick={onClick}
      data-tour="transition"
      title={done ? undefined : decisionsText(counts)}
      className={`sticky top-0 z-30 w-24 min-w-24 cursor-pointer border-b border-r border-border px-2 py-2 text-center align-top text-[11px] font-medium transition-colors ${
        done
          ? "bg-surface-2 text-success"
          : onlyDefaults
            ? "bg-surface-2 text-muted"
            : "bg-attention text-attention-fg"
      }`}
    >
      <div>Übergang</div>
      {done ? (
        <div className="mt-1 flex items-center justify-center gap-1 text-[10px] font-semibold">
          <CheckCircle2 className="h-3 w-3" /> geprüft
        </div>
      ) : (
        <div className="mt-1 flex flex-col gap-0.5">
          {counts.open > 0 && (
            <div className="rounded-full bg-attention-fg/20 px-1.5 py-0.5 text-[10px] font-semibold">
              {counts.open} offen
            </div>
          )}
          {counts.unconfirmed > 0 && (
            <div className="rounded-full border border-border px-1.5 py-0.5 text-[10px] font-semibold">
              {counts.unconfirmed} Vorgabe{counts.unconfirmed === 1 ? "" : "n"}
            </div>
          )}
        </div>
      )}
    </th>
  );
}

function FragmentRows({ children }: { children: React.ReactNode }) {
  return <>{children}</>;
}

// --- "Nächste Schritte": abgeleitete Checkliste ------------------------------------------
// Führt neue (und wiederkehrende) Nutzer zum jeweils nächsten sinnvollen Schritt, statt
// sie vor der Matrix raten zu lassen. Alles wird aus den vorhandenen Daten abgeleitet --
// kein eigener Zustand, kein API-Aufruf, verschwindet von selbst, wenn nichts offen ist.
function NextSteps({
  plan,
  computed,
  columns,
  openCountFor,
  onReview,
  onAddElement,
  onAddPhase,
  onOpenSensitivity,
}: {
  plan: PlanInput;
  computed: PlanComputed;
  columns: Column[];
  openCountFor: (fromPhase: PhaseComputed, toPhase: PhaseComputed) => DecisionCounts;
  onReview: (fromPhaseId: string) => void;
  onAddElement: (category: ElementCategory) => void;
  onAddPhase: () => void;
  onOpenSensitivity?: () => void;
}) {
  if (computed.phases.length === 0) return null;

  const items: { key: string; text: string; action: string; run: () => void }[] = [];

  // Offene Übergangs-Entscheide (inkl. Cash) -- der wichtigste Handlungsbedarf.
  let openTotal = 0;
  let firstOpenPhaseId: string | null = null;
  for (const col of columns) {
    if (col.kind !== "transition") continue;
    const n = openCountFor(col.fromPhase, col.toPhase);
    openTotal += n.open;
    if (n.open > 0 && firstOpenPhaseId === null) firstOpenPhaseId = col.fromPhase.id;
  }
  if (openTotal > 0 && firstOpenPhaseId) {
    const target = firstOpenPhaseId;
    items.push({
      key: "transitions",
      text: `${openTotal} Übergangs-Entscheid${openTotal === 1 ? "" : "e"} offen – z. B. ob eine Immobilie verkauft wird.`,
      action: "Jetzt durchgehen",
      run: () => onReview(target),
    });
  }

  if (!plan.elements.some((e) => e.category === "INCOME")) {
    items.push({
      key: "income",
      text: "Noch kein Einkommen erfasst – ohne Einkommen gibt es keine Sparquote und keine AHV-Rente.",
      action: "Einkommen erfassen",
      run: () => onAddElement("INCOME"),
    });
  }
  if (!plan.elements.some((e) => e.category === "EXPENSE")) {
    items.push({
      key: "expense",
      text: "Noch keine Ausgaben erfasst – sie sind erfahrungsgemäss der stärkste Hebel der ganzen Planung.",
      action: "Ausgaben erfassen",
      run: () => onAddElement("EXPENSE"),
    });
  }

  // Plan endet vor der Pensionierung: die spannendste Phase fehlt noch.
  const totalYears = computed.phases.reduce((s, p) => s + p.durationYears, 0);
  if (plan.persons.some((p) => p.age + totalYears < p.retirementAge)) {
    items.push({
      key: "pension-phase",
      text: "Dein Plan endet vor der Pensionierung – die entscheidende Phase fehlt noch.",
      action: "Phase anlegen",
      run: onAddPhase,
    });
  }

  if (computed.ruinAge !== null && onOpenSensitivity) {
    items.push({
      key: "ruin",
      text: `Das Kapital reicht nicht bis ans Planende (Ruin mit ${computed.ruinAge}). Finde heraus, welche Annahme am meisten bewegt.`,
      action: "Einflussfaktoren",
      run: onOpenSensitivity,
    });
  }

  if (items.length === 0) return null;

  return (
    <div className="rounded-xl border border-attention-soft-fg/30 bg-attention-soft px-4 py-3">
      <div className="mb-2 flex items-center gap-2 text-xs font-semibold uppercase tracking-wide text-attention-soft-fg">
        <ListTodo className="h-4 w-4" /> Nächste Schritte
      </div>
      <ul className="flex flex-col gap-1.5">
        {items.map((it) => (
          <li key={it.key} className="flex flex-wrap items-center gap-2 text-sm text-fg">
            <span className="min-w-0 flex-1">{it.text}</span>
            <button
              type="button"
              onClick={it.run}
              className="flex shrink-0 items-center gap-1 rounded-lg border border-attention-soft-fg/40 px-2.5 py-1 text-xs font-semibold text-attention-soft-fg transition-colors hover:bg-attention hover:text-attention-fg"
            >
              {it.action} <ArrowRight className="h-3 w-3" />
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

// --- Dialog: neues finanzielles Element mit Direkteingabe der Phase-1-Werte ---
function AddElementDialog({
  plan,
  firstPhase,
  initialCategory,
  onClose,
  onCreated,
}: {
  plan: PlanInput;
  firstPhase: PhaseComputed;
  initialCategory?: ElementCategory | null;
  onClose: () => void;
  onCreated: () => void;
}) {
  const [category, setCategory] = useState<ElementCategory>(initialCategory ?? "INCOME");
  const [name, setName] = useState("");
  const [ownerRole, setOwnerRole] = useState<string>("PERSON_A");
  const [pd, setPd] = useState<PhaseData>({});
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const needsPerson = PERSON_ONLY_CATEGORIES.includes(category);
  const isCouple = plan.householdType === "COUPLE";
  const pLabel = (role: string) => {
    const p = plan.persons.find((x) => x.role === role);
    return p?.name && p.name.trim() ? p.name.trim() : role === "PERSON_A" ? "Person A" : "Person B";
  };

  const ownerOptions = needsPerson
    ? isCouple
      ? [
          { value: "PERSON_A", label: pLabel("PERSON_A") },
          { value: "PERSON_B", label: pLabel("PERSON_B") },
        ]
      : [{ value: "PERSON_A", label: pLabel("PERSON_A") }]
    : isCouple
      ? [
          { value: "HOUSEHOLD", label: "Gemeinsam" },
          { value: "PERSON_A", label: pLabel("PERSON_A") },
          { value: "PERSON_B", label: pLabel("PERSON_B") },
        ]
      : [
          { value: "HOUSEHOLD", label: "Gemeinsam" },
          { value: "PERSON_A", label: pLabel("PERSON_A") },
        ];

  // Kontext für die Phase-1-Felder des neuen Elements.
  const owner = needsPerson || ownerRole !== "HOUSEHOLD" ? ownerRole : null;
  const ownerWorking =
    owner && owner !== "HOUSEHOLD"
      ? firstPhase.persons.find((p) => p.role === owner)?.working ?? false
      : firstPhase.type !== "PENSION";
  const context: CellContext = {
    kind: "phase",
    phaseId: firstPhase.id,
    ownerWorking,
    isConsumption: firstPhase.isConsumption,
    durationYears: firstPhase.durationYears,
    isRetirementTransition: false,
    carriedEndValue: 0,
    carried: false,
    derivedStart: 0,
    derivedMortgage: 0,
    mortgageEnd: 0,
    propertyValueEnd: 0,
    deflatorStart: firstPhase.cumulativeInflationStart,
    laterPhaseCount: 0, // beim Anlegen bewusst kein Warnhinweis
    ahvCareer: null,
    // Beim Anlegen gibt es keine Vorphase, aus der etwas zu erben waere.
    inheritedValues: {},
    ownerAgeStart: firstPhase.persons.find((p) => p.role === (owner ?? "PERSON_A"))?.startAge ?? 0,
    investTargets: [],
    capitalFromTransfer: 0,
    capitalFromTransferSources: [],
  };

  async function create() {
    setSaving(true);
    setError(null);
    try {
      const { element } = await api.post<{ element: { id: string } }>(`/api/scenarios/${plan.id}/elements`, {
        category,
        name: name.trim() || CATEGORY_LABELS[category],
        ownerRole,
      });
      // Ist-Zustand direkt in Phase 1 speichern (sofern Felder ausgefuellt).
      if (Object.keys(pd).length > 0) {
        await api.put(`/api/elements/${element.id}/phase/${firstPhase.id}`, pd);
      }
      onCreated();
    } catch (e) {
      setError(e instanceof Error ? e.message : "Erstellen fehlgeschlagen.");
    } finally {
      setSaving(false);
    }
  }

  return (
    <DialogShell title="Finanzielles Element" onClose={onClose} wide>
      <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
        <div>
          <label className="mb-1 block text-xs font-medium text-muted">Kategorie</label>
          <select
            value={category}
            onChange={(e) => {
              const c = e.target.value as ElementCategory;
              setCategory(c);
              setPd({});
              if (PERSON_ONLY_CATEGORIES.includes(c) && ownerRole === "HOUSEHOLD") setOwnerRole("PERSON_A");
              if (!name) setName(CATEGORY_LABELS[c]);
            }}
            className="w-full rounded-lg border border-border bg-input px-3 py-2 text-sm text-fg focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/25"
          >
            {CATEGORY_ORDER.map((c) => (
              <option key={c} value={c}>
                {CATEGORY_LABELS[c]}
              </option>
            ))}
          </select>
        </div>
        <div>
          <label className="mb-1 block text-xs font-medium text-muted">Zuordnung</label>
          <select
            value={ownerRole}
            onChange={(e) => setOwnerRole(e.target.value)}
            className="w-full rounded-lg border border-border bg-input px-3 py-2 text-sm text-fg focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/25"
          >
            {ownerOptions.map((o) => (
              <option key={o.value} value={o.value}>
                {o.label}
              </option>
            ))}
          </select>
        </div>
        <div className="sm:col-span-2">
          <label className="mb-1 block text-xs font-medium text-muted">Bezeichnung</label>
          <input
            value={name}
            onChange={(e) => setName(e.target.value)}
            placeholder={CATEGORY_LABELS[category]}
            className="w-full rounded-lg border border-border bg-input px-3 py-2 text-sm text-fg focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/25"
          />
        </div>
      </div>

      <div className="mt-1 border-t border-border pt-3">
        <div className="mb-2 text-xs font-semibold uppercase tracking-wide text-faint">Werte (erste Lebensphase)</div>
        <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
          <ElementPhaseFields
            element={{ category }}
            context={context}
            pd={pd}
            setP={(patch) => setPd((prev) => ({ ...prev, ...patch }))}
          />
        </div>
      </div>

      {error && <p className="text-sm text-danger">{error}</p>}
      <DialogActions saving={saving} onConfirm={create} onClose={onClose} confirmLabel="Erstellen" />
    </DialogShell>
  );
}

// --- Dialog: neue Lebensphase ---
function AddPhaseDialog({
  maxDurationYears,
  onClose,
  onCreate,
}: {
  maxDurationYears: number | null;
  onClose: () => void;
  onCreate: (payload: { name?: string; durationYears?: number }) => void;
}) {
  const cap = maxDurationYears;
  const [name, setName] = useState("");
  const [durationYears, setDurationYears] = useState(cap ?? 10);
  const [saving, setSaving] = useState(false);

  return (
    <DialogShell title="Neue Lebensphase" onClose={onClose}>
      <div className="grid grid-cols-1 gap-3">
        <div>
          <label className="mb-1 block text-xs font-medium text-muted">Bezeichnung (optional)</label>
          <input
            value={name}
            onChange={(e) => setName(e.target.value)}
            placeholder="automatisch (Erwerb/Pension)"
            className="w-full rounded-lg border border-border bg-input px-3 py-2 text-sm text-fg focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/25"
          />
        </div>
        <div>
          <label className="mb-1 block text-xs font-medium text-muted">
            Dauer (Jahre){cap != null ? ` · max. ${cap}` : ""}
          </label>
          <input
            type="number"
            value={durationYears}
            min={1}
            max={cap ?? undefined}
            onFocus={(e) => e.currentTarget.select()}
            onChange={(e) => {
              const v = e.target.valueAsNumber || 1;
              setDurationYears(cap != null ? Math.min(v, cap) : v);
            }}
            className="w-full rounded-lg border border-border bg-input px-3 py-2 text-sm text-fg focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/25"
          />
        </div>
      </div>
      <DialogActions
        saving={saving}
        onConfirm={() => {
          setSaving(true);
          onCreate({ name: name.trim() || undefined, durationYears });
        }}
        onClose={onClose}
        confirmLabel="Erstellen"
      />
    </DialogShell>
  );
}

// Ziele für die Anlage-Quote der Kapitalverwendung (Roadmap Nr. 44, Punkt C): alle
// Vermögens-Elemente, die in der Folgephase noch aktiv sind.
// Der Name allein genuegt hier nicht: Zwei Personen nennen ihr Wertschriftendepot beide "ETF",
// und im Dropdown waehlt man dann blind das falsche -- was erst Phasen spaeter auffaellt.
function investTargetsOf(plan: PlanInput, toPhase: PhaseComputed | undefined): { id: string; name: string }[] {
  return (toPhase?.elements ?? [])
    .filter((e) => e.category === "OTHER_ASSET" && e.status === "ACTIVE")
    .map((e) => {
      const owner = plan.elements.find((x) => x.id === e.elementId)?.ownerRole ?? null;
      return { id: e.elementId, name: `${e.name} · ${ownerLabel(plan.persons, owner)}` };
    });
}

// --- Panel: Szenario-Profil (Grundprofil bearbeiten) ---
function ProfilePanel({ plan, onClose, onSaved }: { plan: PlanInput; onClose: () => void; onSaved: () => void }) {
  const [draft, setDraft] = useState<ProfileDraft>({
    householdType: plan.householdType,
    inflationRateDefault: plan.inflationRateDefault,
    startYear: plan.startYear ?? new Date().getFullYear(),
    persons: plan.persons.map((p) => ({ role: p.role, name: p.name ?? "", age: p.age, retirementAge: p.retirementAge })),
  });
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function save() {
    setSaving(true);
    setError(null);
    try {
      await api.patch(`/api/scenarios/${plan.id}`, draft);
      onSaved();
    } catch (e) {
      setError(e instanceof Error ? e.message : "Speichern fehlgeschlagen.");
    } finally {
      setSaving(false);
    }
  }

  return (
    <InspectorShell title="Szenario-Profil" subtitle="Haushalt, Personen, Planstart und Inflation dieses Szenarios" onClose={onClose}>
      <div className="flex flex-col gap-3">
        <PlanProfileFields draft={draft} onChange={setDraft} lockRetirement={plan.phases.length > 0} />
        {error && <p className="text-sm text-danger">{error}</p>}
        <div className="flex gap-2 pt-1">
          <Button disabled={saving} onClick={save}>{saving ? "…" : "Speichern"}</Button>
          <Button variant="secondary" onClick={onClose}>Abbrechen</Button>
        </div>

        {plan.phases.length > 0 && (
          <div className="mt-3 border-t border-border pt-3">
            <h3 className="mb-2 text-sm font-semibold text-fg">Pensionsalter anpassen</h3>
            {/* Wirkt SOFORT (eigener Endpunkt), weil dabei Phasendauern verschoben werden --
                das lässt sich nicht sinnvoll mit dem Profil-Formular zusammen abspeichern. */}
            <RetirementAdjuster plan={plan} onSaved={onSaved} />
          </div>
        )}
      </div>
    </InspectorShell>
  );
}

// --- Dialog: geführter Übergang ---
// Pensionsalter des Element-Besitzers -- Bezugspunkt fuer die Vorgaben des Entscheids.
function retirementAgeOf(plan: PlanInput, el: ElementInput): number {
  return plan.persons.find((p) => p.role === el.ownerRole)?.retirementAge ?? AHV_REFERENCE_AGE;
}

function TransitionReviewDialog({
  plan,
  computed,
  fromPhase,
  toPhase,
  elements,
  initialCash,
  buildContext,
  isRetirement,
  onClose,
  onSaved,
}: {
  plan: PlanInput;
  computed: PlanComputed;
  fromPhase: PhaseComputed;
  toPhase: PhaseComputed | undefined;
  elements: ElementInput[];
  initialCash: CashTransitionData;
  buildContext: (el: ElementInput) => CellContext;
  isRetirement: (el: ElementInput) => boolean;
  onClose: () => void;
  onSaved: () => void;
}) {
  const [tds, setTds] = useState<Record<string, TransitionData>>(() =>
    Object.fromEntries(
      elements.map((e) => [e.id, withTransitionDefaults(e.category, isRetirement(e), e.transitionValues[fromPhase.id] ?? {})])
    )
  );
  const [ct, setCt] = useState<CashTransitionData>(() => withCashTransitionDefaults(initialCash));
  // Pensionierungs-Entscheide: eigener Entwurf, weil sie an einem anderen Ort liegen als die
  // Übergangswerte -- und beim Speichern über einen eigenen Endpunkt gehen.
  const [rds, setRds] = useState<Record<string, RetirementDecision>>({});
  const effectiveRd = (el: ElementInput) =>
    withRetirementDefaults(el.category, retirementAgeOf(plan, el), {
      ...(el.retirementDecision ?? {}),
      ...(rds[el.id] ?? {}),
    });
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function saveAll() {
    setSaving(true);
    setError(null);
    try {
      await api.put(`/api/phases/${fromPhase.id}/cash-transition`, ct);
      for (const e of elements) {
        await api.put(`/api/elements/${e.id}/transition/${fromPhase.id}`, tds[e.id] ?? {});
      }
      // Pensionierungs-Entscheide liegen am ELEMENT und haben deshalb einen eigenen Endpunkt.
      for (const [elementId, changes] of Object.entries(rds)) {
        const el = plan.elements.find((x) => x.id === elementId);
        await api.put(`/api/elements/${elementId}/retirement`, { ...(el?.retirementDecision ?? {}), ...changes });
      }
      onSaved();
    } catch (err) {
      setError(err instanceof Error ? err.message : "Speichern fehlgeschlagen.");
    } finally {
      setSaving(false);
    }
  }

  return (
    <DialogShell title={`Übergang prüfen: ${fromPhase.name} → ${toPhase?.name ?? "Ende"}`} onClose={onClose} wide>
      <p className="text-sm text-muted">
        Geh die Positionen durch und triff je Element den Übergangs-Entscheid (Halten, Verkaufen,
        Bezug). Danach werden gehaltene Werte automatisch in die nächste Phase fortgeschrieben.
      </p>
      <div className="flex flex-col gap-3">
        {/* Cash zuerst: einmalige Sonderein-/ausgaben betreffen jeden Übergang. */}
        <div className="rounded-xl border border-border bg-surface-2 p-3">
          <div className="mb-2 flex items-center gap-2">
            <span className="text-accent">
              <Wallet className="h-4 w-4" />
            </span>
            <span className="text-sm font-semibold text-fg">Cash</span>
            <span className="text-xs text-faint">Einmalige Sonderein-/ausgaben</span>
          </div>
          <p className="mb-2 text-xs text-accent-soft-fg">
            Einmalige Ereignisse wie Erbschaft, Autokauf oder Poolbau werden hier direkt dem Cash-Konto
            gutgeschrieben bzw. belastet.
          </p>
          <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
            <CashTransitionFields
              ct={ct}
              setC={(patch) => setCt((prev) => ({ ...prev, ...patch }))}
              deflatorEnd={fromPhase.cumulativeInflationEnd}
            />
          </div>
        </div>

        {elements.length === 0 && (
          <p className="rounded-lg border border-dashed border-border bg-surface-2 p-3 text-sm text-muted">
            An diesem Übergang gibt es sonst keine zu entscheidenden Positionen.
          </p>
        )}
        {elements.map((el) => {
          const ctx = buildContext(el);
          const retire = isRetirement(el);
          const hint =
            el.category === "AHV" && retire
              ? "Pensionierung: Beitragskarriere prüfen – die Rente hängt vom Durchschnittseinkommen ab."
              : (el.category === "PENSION_FUND" || el.category === "PILLAR_3A") && !retire
                ? "Hier könntest du optional Kapital beziehen."
                : el.category === "PENSION_FUND" && retire
                  ? "Pensionierung: Bezugsart wählen (Rente / Kapital / Kombination)."
                  : el.category === "PILLAR_3A" && retire
                    ? "Wird bei Pensionierung vollständig bezogen."
                    : null;
          return (
            <div key={el.id} className="rounded-xl border border-border bg-surface-2 p-3">
              <div className="mb-2 flex items-center gap-2">
                <span className="text-accent">{CATEGORY_ICON[el.category]}</span>
                <span className="text-sm font-semibold text-fg">{el.name}</span>
                <span className="text-xs text-faint">{CATEGORY_LABELS[el.category]}</span>
              </div>
              {hint && <p className="mb-2 text-xs text-accent-soft-fg">{hint}</p>}
              <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
                <ElementTransitionFields
                  element={el}
                  context={ctx}
                  td={tds[el.id] ?? {}}
                  setT={(patch) => setTds((prev) => ({ ...prev, [el.id]: { ...prev[el.id], ...patch } }))}
                  retirement={
                    <RetirementFields
                      plan={plan}
                      el={el}
                      defaultOpen
                      rd={effectiveRd(el)}
                      patch={(id, patch) => setRds((prev) => ({ ...prev, [id]: { ...prev[id], ...patch } }))}
                      summary={computed.retirement.perPerson.find((x) => x.role === el.ownerRole)}
                    />
                  }
                />
              </div>
            </div>
          );
        })}
      </div>
      {error && <p className="text-sm text-danger">{error}</p>}
      <DialogActions saving={saving} onConfirm={saveAll} onClose={onClose} confirmLabel="Alle speichern" />
    </DialogShell>
  );
}

// --- Panel: Cash-Übergang (einmalige Sonderein-/ausgaben) ---
function CashTransitionPanel({
  fromPhase,
  toPhase,
  initial,
  onClose,
  onSaved,
}: {
  fromPhase: PhaseComputed;
  toPhase: PhaseComputed | undefined;
  initial: CashTransitionData;
  onClose: () => void;
  onSaved: () => void;
}) {
  // Vorbelegung, damit ein blosses "Speichern" den sichtbaren Default (1:1) persistiert.
  const [ct, setCt] = useState<CashTransitionData>(() => withCashTransitionDefaults(initial));
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function save() {
    setSaving(true);
    setError(null);
    try {
      await api.put(`/api/phases/${fromPhase.id}/cash-transition`, ct);
      onSaved();
    } catch (e) {
      setError(e instanceof Error ? e.message : "Speichern fehlgeschlagen.");
    } finally {
      setSaving(false);
    }
  }

  return (
    <InspectorShell
      title="Übergang: Cash"
      subtitle={`Einmalige Sonderein-/ausgaben · ${fromPhase.name} → ${toPhase?.name ?? "Ende"}`}
      onClose={onClose}
    >
      <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
        <CashTransitionFields
          ct={ct}
          setC={(patch) => setCt((prev) => ({ ...prev, ...patch }))}
          deflatorEnd={fromPhase.cumulativeInflationEnd}
        />
      </div>
      {error && <p className="mt-3 text-sm text-danger">{error}</p>}
      <PanelActions saving={saving} onConfirm={save} onClose={onClose} confirmLabel="Speichern" />
    </InspectorShell>
  );
}

// --- Panel: Cash-Anfangswert (erste Lebensphase) ---
function CashInitialPanel({ plan, onClose, onSaved }: { plan: PlanInput; onClose: () => void; onSaved: () => void }) {
  const [value, setValue] = useState(plan.initialCash);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function save() {
    setSaving(true);
    setError(null);
    try {
      await api.patch(`/api/scenarios/${plan.id}`, { initialCash: value });
      onSaved();
    } catch (e) {
      setError(e instanceof Error ? e.message : "Speichern fehlgeschlagen.");
    } finally {
      setSaving(false);
    }
  }

  return (
    <InspectorShell
      title="Cash-Anfangswert"
      subtitle="Startbestand des Ausgleichskontos zu Beginn der ersten Lebensphase"
      onClose={onClose}
    >
      <div className="flex flex-col gap-3">
        <MoneyField label="Anfangswert (CHF)" value={value} onChange={setValue} />
        {error && <p className="text-sm text-danger">{error}</p>}
        <PanelActions saving={saving} onConfirm={save} onClose={onClose} confirmLabel="Speichern" />
      </div>
    </InspectorShell>
  );
}

// --- Panel: einzelner Übergangs-Entscheid (per Klick auf eine Übergangszelle) ---
function TransitionCellPanel({
  plan,
  computed,
  element,
  fromPhase,
  toPhase,
  context,
  onClose,
  onSaved,
}: {
  plan: PlanInput;
  computed: PlanComputed;
  element: ElementInput;
  fromPhase: PhaseComputed;
  toPhase: PhaseComputed | undefined;
  context: CellContext;
  onClose: () => void;
  onSaved: () => void;
}) {
  const [td, setTd] = useState<TransitionData>(() =>
    withTransitionDefaults(element.category, context.isRetirementTransition, element.transitionValues[fromPhase.id] ?? {})
  );
  const [rd, setRd] = useState<RetirementDecision>({});
  const effectiveRd = (el: ElementInput) =>
    withRetirementDefaults(el.category, retirementAgeOf(plan, el), {
      ...(el.retirementDecision ?? {}),
      ...rd,
    });
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function save() {
    setSaving(true);
    setError(null);
    try {
      await api.put(`/api/elements/${element.id}/transition/${fromPhase.id}`, td);
      if (Object.keys(rd).length > 0) {
        await api.put(`/api/elements/${element.id}/retirement`, {
          ...(element.retirementDecision ?? {}),
          ...rd,
        });
      }
      onSaved();
    } catch (e) {
      setError(e instanceof Error ? e.message : "Speichern fehlgeschlagen.");
    } finally {
      setSaving(false);
    }
  }

  return (
    <InspectorShell
      title={`Übergang: ${element.name}`}
      subtitle={`${CATEGORY_LABELS[element.category]} · ${fromPhase.name} → ${toPhase?.name ?? "Ende"}`}
      onClose={onClose}
    >
      <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
        <ElementTransitionFields
          element={element}
          context={context}
          td={td}
          setT={(patch) => setTd((prev) => ({ ...prev, ...patch }))}
          retirement={
            <RetirementFields
              plan={plan}
              el={element}
              defaultOpen
              rd={effectiveRd(element)}
              patch={(_id: string, patch: Partial<RetirementDecision>) =>
                setRd((prev) => ({ ...prev, ...patch }))
              }
              summary={computed.retirement.perPerson.find((x) => x.role === element.ownerRole)}
            />
          }
        />
      </div>
      {error && <p className="mt-3 text-sm text-danger">{error}</p>}
      <PanelActions saving={saving} onConfirm={save} onClose={onClose} confirmLabel="Speichern" />
    </InspectorShell>
  );
}

// --- gemeinsame Bausteine ---
// Erstell-Flows und der geführte Übergang bleiben Modals (ui.Modal: ESC, Fokus-Falle,
// Animation); Einzel-Bearbeitungen laufen über das Inspector-Panel.
function DialogShell({
  title,
  onClose,
  children,
  wide,
}: {
  title: string;
  onClose: () => void;
  children: React.ReactNode;
  wide?: boolean;
}) {
  return (
    <Modal title={title} onClose={onClose} wide={wide}>
      {children}
    </Modal>
  );
}

function DialogActions({
  saving,
  onConfirm,
  onClose,
  confirmLabel,
}: {
  saving: boolean;
  onConfirm: () => void;
  onClose: () => void;
  confirmLabel: string;
}) {
  return (
    <div className="flex gap-2 pt-1">
      <Button disabled={saving} onClick={onConfirm}>
        {saving ? "…" : confirmLabel}
      </Button>
      <Button variant="secondary" onClick={onClose}>
        Abbrechen
      </Button>
    </div>
  );
}

// Aktionsleiste im Inspector-Panel.
function PanelActions({
  saving,
  onConfirm,
  onClose,
  confirmLabel,
}: {
  saving: boolean;
  onConfirm: () => void;
  onClose: () => void;
  confirmLabel: string;
}) {
  return (
    <div className="mt-4 flex gap-2 border-t border-border pt-3">
      <Button disabled={saving} onClick={onConfirm}>
        {saving ? "…" : confirmLabel}
      </Button>
      <Button variant="secondary" onClick={onClose}>
        Abbrechen
      </Button>
    </div>
  );
}

// --- Panel: Element umbenennen und zuordnen ----------------------------------------------
// Beides sind Eigenschaften des ELEMENTS, nicht einer Phase -- deshalb ein eigenes Panel,
// erreichbar über das Stift-Symbol in der Element-Zeile.
function ElementMetaPanel({
  element,
  plan,
  personLabel,
  onSaved,
  onDelete,
}: {
  element: ElementInput;
  plan: PlanInput;
  personLabel: (role: string) => string;
  onSaved: () => void;
  onDelete: () => void;
}) {
  const [name, setName] = useState(element.name);
  const [ownerRole, setOwnerRole] = useState<string>(element.ownerRole ?? "HOUSEHOLD");
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const needsPerson = PERSON_ONLY_CATEGORIES.includes(element.category);
  const isCouple = plan.householdType === "COUPLE";
  const options = needsPerson
    ? isCouple
      ? [
          { value: "PERSON_A", label: personLabel("PERSON_A") },
          { value: "PERSON_B", label: personLabel("PERSON_B") },
        ]
      : [{ value: "PERSON_A", label: personLabel("PERSON_A") }]
    : isCouple
      ? [
          { value: "HOUSEHOLD", label: "Gemeinsam" },
          { value: "PERSON_A", label: personLabel("PERSON_A") },
          { value: "PERSON_B", label: personLabel("PERSON_B") },
        ]
      : [
          { value: "HOUSEHOLD", label: "Gemeinsam" },
          { value: "PERSON_A", label: personLabel("PERSON_A") },
        ];

  async function save() {
    setSaving(true);
    setError(null);
    try {
      await api.patch(`/api/elements/${element.id}`, { name: name.trim() || element.name, ownerRole });
      onSaved();
    } catch (e) {
      setError(e instanceof Error ? e.message : "Speichern fehlgeschlagen.");
    } finally {
      setSaving(false);
    }
  }

  return (
    <div className="flex flex-col gap-4">
      <div className="flex items-center justify-between">
        <div className="text-xs font-semibold uppercase tracking-wide text-accent-soft-fg">
          {CATEGORY_LABELS[element.category]}
        </div>
        <button
          type="button"
          onClick={onDelete}
          className="flex items-center gap-1 rounded-lg border border-border px-2 py-1 text-xs text-muted hover:border-danger hover:bg-danger-soft hover:text-danger"
        >
          <Trash2 className="h-3.5 w-3.5" /> Element löschen
        </button>
      </div>

      <TextField label="Bezeichnung" value={name} onChange={setName} />
      <SelectField
        label="Zuordnung"
        help={
          needsPerson
            ? "AHV, Pensionskasse und Säule 3a hängen am individuellen Pensionierungszeitpunkt und gehören deshalb immer genau einer Person."
            : "Gemeinsame Elemente zählen zum Haushalt. Bei Paaren wird ein gemeinsames Einkommen NICHT für die AHV-Rente angerechnet."
        }
        value={ownerRole}
        onChange={setOwnerRole}
        options={options}
      />
      <p className="text-xs text-faint">
        Die Werte je Lebensphase bearbeitest du direkt in den Zellen der Matrix.
      </p>

      {error && <p className="text-sm text-danger">{error}</p>}
      <div className="flex gap-2">
        <Button disabled={saving} onClick={save}>
          {saving ? "Speichern…" : "Speichern"}
        </Button>
      </div>
    </div>
  );
}
