// Gesamtkosten-Rechner — Live-Vergleich TCO E-Auto vs. Verbrenner.
const { Card, Slider, Button, StatusDot, Tooltip, Tabs } = window.GoodDesignRamsDesignSystem_019e30;

function HoverReadout({ x, width, pad, year, evValue, iceValue, evColor, iceColor }) {
  const boxW = 134, boxH = 48;
  const bx = x + boxW + 12 > width - pad.right ? x - boxW - 10 : x + 10;
  const by = pad.top;
  return (
    <g pointerEvents="none">
      <rect x={bx} y={by} width={boxW} height={boxH} rx="6" fill="var(--surface-inverse)" opacity="0.94" />
      <text x={bx + 10} y={by + 16} fontSize="11" fontFamily="var(--font-mono)" fill="var(--gray-4)">
        Jahr {year.toFixed(1).replace(/\.0$/, "")}
      </text>
      <text x={bx + 10} y={by + 31} fontSize="12" fontFamily="var(--font-mono)" fontWeight="500" fill={evColor}>
        {Math.round(evValue).toLocaleString("de-DE")} €
      </text>
      <text x={bx + 10} y={by + 43} fontSize="12" fontFamily="var(--font-mono)" fontWeight="500" fill={iceColor}>
        {Math.round(iceValue).toLocaleString("de-DE")} €
      </text>
    </g>
  );
}

function TCOChart({ evSeries, iceSeries, chartYears, evColor = "#1F9D55", iceColor = "#FF6A00" }) {
  const width = 560, height = 220;
  // pad.right lässt Raum für die Endwert-Beschriftungen am rechten Rand.
  const pad = { top: 16, right: 78, bottom: 32, left: 70 };
  const plotW = width - pad.left - pad.right;
  const plotH = height - pad.top - pad.bottom;
  // Explizit übergeben statt aus den Serien abgeleitet: least nur ein
  // Fahrzeug, endet dessen Kurve vor der Haltedauer, während die andere
  // weiterläuft — die Zeitachse muss sich dann nach der längeren Kurve
  // richten, nicht nach einer beliebigen der beiden Serien.
  const maxYear = chartYears || 1;
  const allValues = [...evSeries.map((p) => p.cum), ...iceSeries.map((p) => p.cum)];
  const maxV = Math.max(...allValues);
  const minV = Math.min(...allValues);
  const range = maxV - minV || 1;
  const xFor = (year) => pad.left + (year / maxYear) * plotW;
  const yFor = (v) => pad.top + (1 - (v - minV) / range) * plotH;
  const yearForX = (x) => Math.min(maxYear, Math.max(0, ((x - pad.left) / plotW) * maxYear));

  // Ganz am Ende (letzter Jahrespunkt) sollen die Kosten nach Verkauf
  // gezeigt werden, also der Wert nach einem eventuellen Sprung (Restwert/
  // Schlussrate) — nicht der Wert unmittelbar davor.
  const interp = (series, year) => {
    if (year >= series[series.length - 1].year) return series[series.length - 1].cum;
    for (let i = 1; i < series.length; i++) {
      if (year <= series[i].year) {
        const a = series[i - 1], b = series[i];
        const t = b.year === a.year ? 0 : (year - a.year) / (b.year - a.year);
        return a.cum + (b.cum - a.cum) * t;
      }
    }
    return series[series.length - 1].cum;
  };

  const lineFor = (series) => series.map((p) => `${xFor(p.year)},${yFor(p.cum)}`).join(" ");

  const gridSteps = [0, 0.5, 1];
  const wholeYears = Math.floor(maxYear);
  const xLabelYears = wholeYears <= 8
    ? Array.from({ length: wholeYears + 1 }, (_, i) => i)
    : [0, Math.round(wholeYears / 2), wholeYears];

  const svgRef = React.useRef(null);
  const [hoverX, setHoverX] = React.useState(null);

  const onMove = (e) => {
    const rect = svgRef.current.getBoundingClientRect();
    const x = (e.clientX - rect.left) * (width / rect.width);
    setHoverX(Math.min(width - pad.right, Math.max(pad.left, x)));
  };

  const hoverYear = hoverX != null ? yearForX(hoverX) : null;
  const hoverLineX = hoverYear != null ? xFor(hoverYear) : null;
  const hoverEV = hoverYear != null ? interp(evSeries, hoverYear) : null;
  const hoverICE = hoverYear != null ? interp(iceSeries, hoverYear) : null;

  // Endwert-Beschriftungen: direkt lesbar ohne Hover, damit am Kurvenende
  // (wo beide Linien durch den Sprung dicht beieinanderliegen können)
  // sofort klar ist, welche Farbe welchen Wert hat. Bei zu geringem
  // Abstand werden die beiden Labels leicht auseinandergeschoben, damit
  // keines das andere verdeckt.
  const evFinal = evSeries[evSeries.length - 1];
  const iceFinal = iceSeries[iceSeries.length - 1];
  const minLabelGap = 16;
  let evLabelY = yFor(evFinal.cum);
  let iceLabelY = yFor(iceFinal.cum);
  if (Math.abs(evLabelY - iceLabelY) < minLabelGap) {
    const mid = (evLabelY + iceLabelY) / 2;
    if (evLabelY <= iceLabelY) {
      evLabelY = mid - minLabelGap / 2;
      iceLabelY = mid + minLabelGap / 2;
    } else {
      evLabelY = mid + minLabelGap / 2;
      iceLabelY = mid - minLabelGap / 2;
    }
  }
  const clampLabelY = (y) => Math.min(height - pad.bottom - 4, Math.max(pad.top + 8, y));
  evLabelY = clampLabelY(evLabelY);
  iceLabelY = clampLabelY(iceLabelY);

  return (
    <svg
      ref={svgRef}
      viewBox={`0 0 ${width} ${height}`}
      style={{ width: "100%", height: "auto", overflow: "visible", cursor: "crosshair" }}
      onMouseMove={onMove}
      onMouseLeave={() => setHoverX(null)}
    >
      {gridSteps.map((s) => {
        const v = minV + s * range;
        const y = yFor(v);
        return (
          <g key={s}>
            <line x1={pad.left} x2={width - pad.right} y1={y} y2={y} stroke="var(--border-hairline)" strokeWidth="1" />
            <text x={pad.left - 10} y={y + 5} textAnchor="end" fontSize="13" fontFamily="var(--font-mono)" fill="var(--text-secondary)">
              {Math.round(v).toLocaleString("de-DE")} €
            </text>
          </g>
        );
      })}
      <polyline points={lineFor(iceSeries)} fill="none" stroke={iceColor} strokeWidth="2.5" strokeLinejoin="round" strokeLinecap="round" />
      <polyline points={lineFor(evSeries)} fill="none" stroke={evColor} strokeWidth="2.5" strokeLinejoin="round" strokeLinecap="round" />
      {iceSeries.map((p, i) => <circle key={"i" + i} cx={xFor(p.year)} cy={yFor(p.cum)} r="3" fill={iceColor} />)}
      {evSeries.map((p, i) => <circle key={"e" + i} cx={xFor(p.year)} cy={yFor(p.cum)} r="3" fill={evColor} />)}
      {xLabelYears.map((y) => (
        <text key={y} x={xFor(y)} y={height - 8} textAnchor="middle" fontSize="13" fontFamily="var(--font-mono)" fill="var(--text-secondary)">
          Jahr {y}
        </text>
      ))}
      <text x={xFor(evFinal.year) + 8} y={evLabelY + 4} fontSize="12" fontFamily="var(--font-mono)" fontWeight="500" fill={evColor}>
        {Math.round(evFinal.cum).toLocaleString("de-DE")} €
      </text>
      <text x={xFor(iceFinal.year) + 8} y={iceLabelY + 4} fontSize="12" fontFamily="var(--font-mono)" fontWeight="500" fill={iceColor}>
        {Math.round(iceFinal.cum).toLocaleString("de-DE")} €
      </text>
      {hoverLineX != null && (
        <g>
          <line x1={hoverLineX} x2={hoverLineX} y1={pad.top} y2={height - pad.bottom} stroke="var(--text-tertiary)" strokeWidth="1" strokeDasharray="3 3" />
          <circle cx={hoverLineX} cy={yFor(hoverICE)} r="4" fill={iceColor} stroke="var(--surface-card)" strokeWidth="1.5" />
          <circle cx={hoverLineX} cy={yFor(hoverEV)} r="4" fill={evColor} stroke="var(--surface-card)" strokeWidth="1.5" />
          <HoverReadout x={hoverLineX} width={width} pad={pad} year={hoverYear} evValue={hoverEV} iceValue={hoverICE} evColor={evColor} iceColor={iceColor} />
        </g>
      )}
    </svg>
  );
}

function Stat({ label, value }) {
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
      <span style={{ font: "var(--font-data)", fontSize: 20, fontWeight: 500, fontVariantNumeric: "tabular-nums", color: "var(--text-primary)" }}>{value}</span>
      <span style={{ font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color: "var(--text-tertiary)" }}>{label}</span>
    </div>
  );
}

function SliderRow({ label, value, min, max, step = "any", unit, info, onChange, dot }) {
  const labelNode = dot ? (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
      <span style={{ width: 6, height: 6, borderRadius: 999, background: dot, display: "inline-block", flexShrink: 0 }}></span>
      {label}
    </span>
  ) : label;
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
      <div style={{ flex: 1 }}>
        <Slider label={labelNode} value={value} min={min} max={max} step={step} unit={unit} onChange={onChange} />
      </div>
      <input
        type="number" value={value} min={min} max={max} step="any" onChange={onChange}
        style={{
          width: 76, height: 32, padding: "0 8px", boxSizing: "border-box",
          border: "var(--hairline-strong)", borderRadius: "var(--radius-2)",
          font: "var(--font-data)", fontSize: 12, textAlign: "right",
          background: "var(--surface-card)", color: "var(--text-primary)", outline: "none",
        }}
      />
      {info && (
        <Tooltip content={info} side="top">
          <span style={{
            width: 18, height: 18, borderRadius: 999, border: "var(--hairline-strong)",
            display: "inline-flex", alignItems: "center", justifyContent: "center",
            font: "var(--font-data)", fontSize: 10, color: "var(--text-secondary)",
            flexShrink: 0, cursor: "default",
          }}>i</span>
        </Tooltip>
      )}
    </div>
  );
}

// Slider auf eine feste, nicht gleichmäßige Werteliste beschränkt (z. B.
// 0, 3000, 3500 … 6000). Die Position entlang der Leiste ist linear zum
// tatsächlichen Wert (0 -> 0%, 3000 -> 50%, 6000 -> 100%), nicht zum Index
// — die großen und kleinen Sprünge zwischen den Stufen sind so auch optisch
// unterschiedlich groß. Während des Ziehens folgt der Daumen der Maus
// kontinuierlich (siehe YearsSliderRow) und rastet erst beim Loslassen auf
// die nächstgelegene erlaubte Stufe ein.
function SteppedSliderRow({ label, value, steps, unit, info, onChange }) {
  const trackRef = React.useRef(null);
  const [dragPct, setDragPct] = React.useState(null);
  const [draft, setDraft] = React.useState(null);
  const min = steps[0];
  const max = steps[steps.length - 1];
  const pctFor = (v) => (max === min ? 0 : (v - min) / (max - min));
  const pct = dragPct != null ? dragPct : pctFor(value);
  const nearestStep = (raw) => steps.reduce((best, s) => (Math.abs(s - raw) < Math.abs(best - raw) ? s : best), steps[0]);

  const pctFromClientX = (clientX) => {
    const rect = trackRef.current.getBoundingClientRect();
    return Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));
  };
  const commitFromPct = (p) => {
    setDragPct(p);
    onChange(nearestStep(min + p * (max - min)));
  };
  const onPointerDown = (e) => {
    e.preventDefault();
    e.currentTarget.setPointerCapture(e.pointerId);
    commitFromPct(pctFromClientX(e.clientX));
  };
  const onPointerMove = (e) => {
    if (dragPct == null) return;
    commitFromPct(pctFromClientX(e.clientX));
  };
  const onPointerUp = () => setDragPct(null);
  const onKeyDown = (e) => {
    const idx = steps.indexOf(value);
    if (e.key === "ArrowRight" || e.key === "ArrowUp") { e.preventDefault(); onChange(steps[Math.min(steps.length - 1, idx + 1)]); }
    if (e.key === "ArrowLeft" || e.key === "ArrowDown") { e.preventDefault(); onChange(steps[Math.max(0, idx - 1)]); }
  };

  return (
    <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
      <div style={{ flex: 1 }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color: "var(--text-secondary)", marginBottom: 8 }}>
          <span>{label}</span>
          <span style={{ font: "var(--font-data)", fontSize: 11, color: "var(--text-primary)" }}>{value.toLocaleString("de-DE")}{unit ? ` ${unit}` : ""}</span>
        </div>
        <div
          ref={trackRef}
          role="slider"
          tabIndex={0}
          aria-label={label}
          aria-valuemin={min}
          aria-valuemax={max}
          aria-valuenow={value}
          onPointerDown={onPointerDown}
          onPointerMove={onPointerMove}
          onPointerUp={onPointerUp}
          onKeyDown={onKeyDown}
          style={{ position: "relative", height: 20, display: "flex", alignItems: "center", cursor: "pointer", touchAction: "none", outlineOffset: 4 }}
        >
          <div style={{ position: "absolute", left: 0, right: 0, height: 4, borderRadius: 2, background: "var(--gray-3)", boxShadow: "var(--shadow-inset)" }}></div>
          <div style={{ position: "absolute", left: 0, width: `${pct * 100}%`, height: 4, borderRadius: 2, background: "var(--gray-8)" }}></div>
          <div style={{
            position: "absolute", left: `calc(${pct * 100}% - 7px)`,
            width: 14, height: 20, borderRadius: 3,
            background: "var(--surface-card)", border: "var(--hairline-strong)", boxShadow: "var(--shadow-1)",
          }}>
            <div style={{ position: "absolute", left: "50%", top: 4, bottom: 4, width: 2, marginLeft: -1, background: "var(--accent-orange)", borderRadius: 1 }}></div>
          </div>
        </div>
      </div>
      <input
        type="number" value={draft != null ? draft : value} min={min} max={max} step="any"
        onChange={(e) => setDraft(e.target.value)}
        onBlur={() => {
          if (draft != null) onChange(nearestStep(+draft || 0));
          setDraft(null);
        }}
        onKeyDown={(e) => { if (e.key === "Enter") e.currentTarget.blur(); }}
        style={{
          width: 76, height: 32, padding: "0 8px", boxSizing: "border-box",
          border: "var(--hairline-strong)", borderRadius: "var(--radius-2)",
          font: "var(--font-data)", fontSize: 12, textAlign: "right",
          background: "var(--surface-card)", color: "var(--text-primary)", outline: "none",
        }}
      />
      {info && (
        <Tooltip content={info} side="top">
          <span style={{
            width: 18, height: 18, borderRadius: 999, border: "var(--hairline-strong)",
            display: "inline-flex", alignItems: "center", justifyContent: "center",
            font: "var(--font-data)", fontSize: 10, color: "var(--text-secondary)",
            flexShrink: 0, cursor: "default",
          }}>i</span>
        </Tooltip>
      )}
    </div>
  );
}

// Eigener Schieberegler für ganzzahlige Werte über wenige Schritte (z. B.
// Haltedauer, 1-15 Jahre). Ein natives <input type="range" step="1"> über
// so wenige Stufen quantisiert die Daumen-Position bei jedem Mousemove neu
// — der Daumen springt zwischen den Stufen und läuft dem Mauszeiger davon.
// Hier folgt der Daumen während des Ziehens der Mausposition kontinuierlich
// (dragPct), der gemeldete/gerechnete Wert ist trotzdem immer ganzzahlig;
// beim Loslassen rastet der Daumen exakt auf die passende Stufe ein.
function YearsSliderRow({ label, value, min, max, unit, info, onChange }) {
  const trackRef = React.useRef(null);
  const [dragPct, setDragPct] = React.useState(null);
  const clampValue = (v) => Math.min(max, Math.max(min, Math.round(v)));
  const pctFor = (val) => (max === min ? 0 : (val - min) / (max - min));
  const pct = dragPct != null ? dragPct : pctFor(value);

  const pctFromClientX = (clientX) => {
    const rect = trackRef.current.getBoundingClientRect();
    return Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));
  };

  const onPointerDown = (e) => {
    e.preventDefault();
    e.currentTarget.setPointerCapture(e.pointerId);
    const p = pctFromClientX(e.clientX);
    setDragPct(p);
    onChange(clampValue(min + p * (max - min)));
  };
  const onPointerMove = (e) => {
    if (dragPct == null) return;
    const p = pctFromClientX(e.clientX);
    setDragPct(p);
    onChange(clampValue(min + p * (max - min)));
  };
  const onPointerUp = () => setDragPct(null);
  const onKeyDown = (e) => {
    if (e.key === "ArrowRight" || e.key === "ArrowUp") { e.preventDefault(); onChange(clampValue(value + 1)); }
    if (e.key === "ArrowLeft" || e.key === "ArrowDown") { e.preventDefault(); onChange(clampValue(value - 1)); }
  };

  return (
    <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
      <div style={{ flex: 1 }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color: "var(--text-secondary)", marginBottom: 8 }}>
          <span>{label}</span>
          <span style={{ font: "var(--font-data)", fontSize: 11, color: "var(--text-primary)" }}>{value}{unit ? ` ${unit}` : ""}</span>
        </div>
        <div
          ref={trackRef}
          role="slider"
          tabIndex={0}
          aria-label={label}
          aria-valuemin={min}
          aria-valuemax={max}
          aria-valuenow={value}
          onPointerDown={onPointerDown}
          onPointerMove={onPointerMove}
          onPointerUp={onPointerUp}
          onKeyDown={onKeyDown}
          style={{ position: "relative", height: 20, display: "flex", alignItems: "center", cursor: "pointer", touchAction: "none", outlineOffset: 4 }}
        >
          <div style={{ position: "absolute", left: 0, right: 0, height: 4, borderRadius: 2, background: "var(--gray-3)", boxShadow: "var(--shadow-inset)" }}></div>
          <div style={{ position: "absolute", left: 0, width: `${pct * 100}%`, height: 4, borderRadius: 2, background: "var(--gray-8)" }}></div>
          <div style={{
            position: "absolute", left: `calc(${pct * 100}% - 7px)`,
            width: 14, height: 20, borderRadius: 3,
            background: "var(--surface-card)", border: "var(--hairline-strong)", boxShadow: "var(--shadow-1)",
          }}>
            <div style={{ position: "absolute", left: "50%", top: 4, bottom: 4, width: 2, marginLeft: -1, background: "var(--accent-orange)", borderRadius: 1 }}></div>
          </div>
        </div>
      </div>
      <input
        type="number" value={value} min={min} max={max} step={1}
        onChange={(e) => onChange(clampValue(+e.target.value))}
        style={{
          width: 76, height: 32, padding: "0 8px", boxSizing: "border-box",
          border: "var(--hairline-strong)", borderRadius: "var(--radius-2)",
          font: "var(--font-data)", fontSize: 12, textAlign: "right",
          background: "var(--surface-card)", color: "var(--text-primary)", outline: "none",
        }}
      />
      {info && (
        <Tooltip content={info} side="top">
          <span style={{
            width: 18, height: 18, borderRadius: 999, border: "var(--hairline-strong)",
            display: "inline-flex", alignItems: "center", justifyContent: "center",
            font: "var(--font-data)", fontSize: 10, color: "var(--text-secondary)",
            flexShrink: 0, cursor: "default",
          }}>i</span>
        </Tooltip>
      )}
    </div>
  );
}

const EV_COLOR = "#1F9D55";
const ICE_COLOR = "#FF6A00";

function VehicleGroup({ vehicle, children }) {
  const isEV = vehicle === "ev";
  const color = isEV ? EV_COLOR : ICE_COLOR;
  const label = isEV ? "E-Auto" : "Verbrenner";
  return (
    <div style={{ border: `1px solid ${color}`, borderRadius: "var(--radius-2)", padding: 16, display: "flex", flexDirection: "column", gap: 20 }}>
      <span style={{ display: "flex", alignItems: "center", gap: 6, font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color }}>
        <span style={{ width: 8, height: 8, borderRadius: 999, background: color, display: "inline-block" }}></span>
        {label}
      </span>
      {children}
    </div>
  );
}

const SUBSIDY_STEPS = [0, 3000, 3500, 4000, 4500, 5000, 5500, 6000];

const DEFAULTS = {
  evMode: "kauf",
  iceMode: "kauf",
  evPrice: 40000,
  icePrice: 35000,
  evResidualValue: 8000, // 20 % von 40.000 €
  iceResidualValue: 7000, // 20 % von 35.000 €
  evDownPayment: 4000, // 10 % von 40.000 €
  iceDownPayment: 3500, // 10 % von 35.000 €
  evMonthlyPayment: 450, // 1,125 % von 40.000 €
  iceMonthlyPayment: 394, // 1,125 % von 35.000 €
  evBalloon: 14000, // 35 % von 40.000 €
  iceBalloon: 12250, // 35 % von 35.000 €
  evFinancingYears: 5,
  iceFinancingYears: 5,
  evLeaseUpfront: 3000,
  iceLeaseUpfront: 2000,
  evLeaseRate: 380,
  iceLeaseRate: 320,
  evLeasingYears: 4,
  iceLeasingYears: 4,
  evSubsidy: 3000,
  priceHome: 0.30,
  pricePublic: 0.50,
  publicShare: 20,
  fuelPrice: 1.90,
  evConsumption: 18,
  iceConsumption: 7.0,
  annualKm: 15000,
  years: 10,
  evInsurance: 600,
  iceInsurance: 500,
  evUpkeep: 300,
  iceUpkeep: 800,
  kfzSteuerICE: 200,
  thgQuote: 300,
};

const DECIMALS = {
  evPrice: 0, icePrice: 0, evResidualValue: 0, iceResidualValue: 0,
  evDownPayment: 0, iceDownPayment: 0,
  evMonthlyPayment: 0, iceMonthlyPayment: 0, evBalloon: 0, iceBalloon: 0,
  evLeaseUpfront: 0, iceLeaseUpfront: 0, evLeaseRate: 0, iceLeaseRate: 0,
  evSubsidy: 0,
  priceHome: 2, pricePublic: 2, publicShare: 0, fuelPrice: 2,
  evConsumption: 1, iceConsumption: 1, annualKm: 0,
  evInsurance: 0, iceInsurance: 0, evUpkeep: 0, iceUpkeep: 0, kfzSteuerICE: 0, thgQuote: 0,
};

const MODE_TABS = [
  { value: "kauf", label: "Kauf" },
  { value: "finanzierung", label: "Finanzierung" },
  { value: "leasing", label: "Leasing" },
];

// Reale Leasingverträge laufen praktisch nie länger als 6 Jahre.
const LEASING_MAX_YEARS = 6;
// Finanzierungen darüber hinaus sind unüblich.
const FINANCING_MAX_YEARS = 8;

function TCOCalculatorDeploy() {
  const [v, setV] = React.useState(DEFAULTS);
  const set = (key) => (e) => setV((s) => ({ ...s, [key]: roundTo(+e.target.value, DECIMALS[key]) }));
  // Finanzierungslaufzeit darf nie länger als die Haltedauer sein (kann
  // aber kürzer sein) und zusätzlich nie länger als 8 Jahre; Leasinglaufzeit
  // entsprechend nie länger als 6 Jahre.
  const clampTermYears = (s, years) => ({
    evFinancingYears: Math.min(s.evFinancingYears, years, FINANCING_MAX_YEARS),
    iceFinancingYears: Math.min(s.iceFinancingYears, years, FINANCING_MAX_YEARS),
    evLeasingYears: Math.min(s.evLeasingYears, years, LEASING_MAX_YEARS),
    iceLeasingYears: Math.min(s.iceLeasingYears, years, LEASING_MAX_YEARS),
  });
  const setEvMode = (evMode) => setV((s) => ({ ...s, evMode }));
  const setIceMode = (iceMode) => setV((s) => ({ ...s, iceMode }));
  const setSubsidy = (value) => setV((s) => ({ ...s, evSubsidy: value }));
  const setYears = (years) => setV((s) => ({ ...s, years, ...clampTermYears(s, years) }));
  const setEvFinancingYears = (evFinancingYears) => setV((s) => ({ ...s, evFinancingYears: Math.min(evFinancingYears, s.years, FINANCING_MAX_YEARS) }));
  const setIceFinancingYears = (iceFinancingYears) => setV((s) => ({ ...s, iceFinancingYears: Math.min(iceFinancingYears, s.years, FINANCING_MAX_YEARS) }));
  const setEvLeasingYears = (evLeasingYears) => setV((s) => ({ ...s, evLeasingYears: Math.min(evLeasingYears, s.years, LEASING_MAX_YEARS) }));
  const setIceLeasingYears = (iceLeasingYears) => setV((s) => ({ ...s, iceLeasingYears: Math.min(iceLeasingYears, s.years, LEASING_MAX_YEARS) }));
  // Restwert (0-80 %), Anzahlung (0-50 %) und Schlussrate (20-70 %) werden
  // in € eingegeben, sind aber weiterhin an den Kaufpreis gekoppelt — bei
  // einem niedrigeren Kaufpreis werden zu hoch gewordene Werte gekappt.
  const setEvPrice = (e) => setV((s) => {
    const evPrice = roundTo(+e.target.value, 0);
    return {
      ...s, evPrice,
      evResidualValue: Math.min(s.evResidualValue, roundTo(evPrice * 0.8, 0)),
      evDownPayment: Math.min(s.evDownPayment, roundTo(evPrice * 0.5, 0)),
      evBalloon: Math.min(Math.max(s.evBalloon, roundTo(evPrice * 0.2, 0)), roundTo(evPrice * 0.7, 0)),
    };
  });
  const setIcePrice = (e) => setV((s) => {
    const icePrice = roundTo(+e.target.value, 0);
    return {
      ...s, icePrice,
      iceResidualValue: Math.min(s.iceResidualValue, roundTo(icePrice * 0.8, 0)),
      iceDownPayment: Math.min(s.iceDownPayment, roundTo(icePrice * 0.5, 0)),
      iceBalloon: Math.min(Math.max(s.iceBalloon, roundTo(icePrice * 0.2, 0)), roundTo(icePrice * 0.7, 0)),
    };
  });
  const reset = () => setV(DEFAULTS);

  const r = computeTCO(v);
  // percentCheaper (Kosten-pro-km-Verhältnis) bleibt auch dann fair, wenn
  // genau ein Fahrzeug least und die beiden Fahrzeuge intern auf
  // unterschiedliche Zeiträume bezogen werden — tcoDifference/totalTCO
  // wären in diesem Fall kein direkt vergleichbares Paar mehr.
  const cheaper = r.percentCheaper >= 0;
  // Least genau ein Fahrzeug, ist die Gesamtkosten-/Ersparnis-Summe für
  // das andere (Kauf/Finanzierung) nur eine Momentaufnahme zum
  // (kürzeren) Leasingende, nicht seine tatsächlichen Gesamtkosten — ein
  // irreführender Vergleich. In diesem Fall werden daher nur noch die
  // Kosten pro km und die Prozentzahl gezeigt, die als Verhältniswerte
  // weiterhin aussagekräftig bleiben; leasen beide oder keines der
  // Fahrzeuge, beziehen sich beide Seiten auf denselben Zeitraum und die
  // absoluten Summen sind wieder sinnvoll.
  const showTotals = (v.evMode === "leasing") === (v.iceMode === "leasing");
  // Wird sowohl bei Kauf (am Ende) als auch bei Finanzierung (vor dem
  // berechneten Zins/Gesamtanschaffung) angezeigt — hier einmal definiert.
  const evResidualRow = (
    <SliderRow label="Restwert nach Haltedauer" value={v.evResidualValue} min={0} max={v.evPrice * 0.8} step={100} unit="€" onChange={set("evResidualValue")} info="Geschätzter Wiederverkaufswert des E-Autos am Ende der Haltedauer, in €. Maximal 80 % des Kaufpreises." />
  );
  const evFoerderungRow = (
    <SteppedSliderRow label="Förderung" value={v.evSubsidy} steps={SUBSIDY_STEPS} unit="€" onChange={setSubsidy} info="Staatliche E-Auto-Förderung (z. B. Umweltbonus). Gilt unabhängig vom Zahlungsweg, linear über die Haltedauer angerechnet." />
  );
  // Bei Finanzierung nur zur Herleitung sinnvoller Wertebereiche für
  // Anzahlung/Schlussrate/Restwert nötig, nicht für die Berechnung selbst
  // (die auf den direkt eingegebenen €-Beträgen basiert) — daher optional.
  const evKaufpreisRow = (
    <SliderRow label={v.evMode === "finanzierung" ? "Kaufpreis (optional)" : "Kaufpreis"} value={v.evPrice} min={20000} max={70000} step={100} unit="€" onChange={setEvPrice} info="Anschaffungspreis des E-Autos (Neupreis)." />
  );
  const iceResidualRow = (
    <SliderRow label="Restwert nach Haltedauer" value={v.iceResidualValue} min={0} max={v.icePrice * 0.8} step={100} unit="€" onChange={set("iceResidualValue")} info="Geschätzter Wiederverkaufswert des Verbrenners am Ende der Haltedauer, in €. Maximal 80 % des Kaufpreises." />
  );
  const iceKaufpreisRow = (
    <SliderRow label={v.iceMode === "finanzierung" ? "Kaufpreis (optional)" : "Kaufpreis"} value={v.icePrice} min={15000} max={60000} step={100} unit="€" onChange={setIcePrice} info="Anschaffungspreis des Verbrenners (Neupreis)." />
  );

  return (
    <div style={{ display: "grid", gridTemplateColumns: "3fr 2fr", gap: 40, alignItems: "start" }}>

      {/* ---- inputs ---- */}
      <div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
        <Card title="Haltedauer" padding={24}>
          <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
            <YearsSliderRow label="Haltedauer" value={v.years} min={1} max={15} unit="Jahre" onChange={setYears} info="Betrachtungszeitraum für die Gesamtkosten-Berechnung — gilt für E-Auto und Verbrenner gleichermaßen und ist Grundlage aller Berechnungen (Restwert, Finanzierungs- und Leasinglaufzeit, Diagramm). Ist eine Leasinglaufzeit kürzer als die Haltedauer, bezieht sich der Kostenvergleich auf das (kürzere) Leasingende; im Diagramm endet dann nur die Kurve des leasenden Fahrzeugs früher." />
          </div>
        </Card>

        <Card title="Anschaffung" padding={24}>
          <div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
            <VehicleGroup vehicle="ev">
              <Tabs tabs={MODE_TABS} value={v.evMode} onChange={setEvMode} />

              {v.evMode === "kauf" && (
                <React.Fragment>
                  {evKaufpreisRow}
                  {evFoerderungRow}
                  {evResidualRow}
                </React.Fragment>
              )}

              {v.evMode === "finanzierung" && (
                <React.Fragment>
                  <YearsSliderRow label="Finanzierungslaufzeit" value={v.evFinancingYears} min={1} max={Math.min(v.years, FINANCING_MAX_YEARS)} unit="Jahre" onChange={setEvFinancingYears} info="Laufzeit der Finanzierung in Jahren — maximal 8 Jahre und kann kürzer als die Haltedauer sein. Die Schlussrate wird dann bereits vor Ende der Haltedauer fällig; danach laufen bis zum Ende der Haltedauer nur noch die laufenden Kosten weiter." />
                  {evKaufpreisRow}
                  <SliderRow label="Anzahlung" value={v.evDownPayment} min={0} max={v.evPrice * 0.5} step={100} unit="€" onChange={set("evDownPayment")} info="Anzahlung in €, maximal 50 % des Kaufpreises." />
                  <SliderRow label="Monatliche Rate" value={v.evMonthlyPayment} min={100} max={1200} unit="€/Monat" onChange={set("evMonthlyPayment")} info="Monatliche Finanzierungsrate laut Angebot, bis zum Ende der Finanzierungslaufzeit." />
                  <SliderRow label="Schlussrate" value={v.evBalloon} min={v.evPrice * 0.2} max={v.evPrice * 0.7} step={100} unit="€" onChange={set("evBalloon")} info="Schlussrate (Restkreditbetrag am Ende der Finanzierungslaufzeit) in €, zwischen 20 % und 70 % des Kaufpreises." />
                  {evFoerderungRow}
                  {evResidualRow}
                  <div style={{ background: "var(--surface-sunken)", borderRadius: "var(--radius-2)", padding: 16, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
                    <Stat label="Effektiver Zins (berechnet)" value={`${r.evFinancingInfo.effectiveRate.toFixed(1)} % p.a.`} />
                    <Stat label="Gesamtanschaffung (berechnet)" value={formatEUR(r.evFinancingInfo.totalFinancing)} />
                  </div>
                </React.Fragment>
              )}

              {v.evMode === "leasing" && (
                <React.Fragment>
                  <YearsSliderRow label="Leasingdauer" value={v.evLeasingYears} min={1} max={Math.min(v.years, LEASING_MAX_YEARS)} unit="Jahre" onChange={setEvLeasingYears} info="Laufzeit des Leasingvertrags in Jahren — maximal 6 Jahre und kann kürzer als die Haltedauer sein. Der Kostenvergleich (Gesamtkosten, Gesamtkosten/km) bezieht sich dann auf die kürzere Leasinglaufzeit; im Diagramm endet die Kurve dieses Fahrzeugs zum Leasingende, während die andere unverändert weiterläuft." />
                  <SliderRow label="Sonderzahlung" value={v.evLeaseUpfront} min={0} max={15000} unit="€" onChange={set("evLeaseUpfront")} info="Einmalige Leasing-Sonderzahlung zu Vertragsbeginn." />
                  <SliderRow label="Monatliche Rate" value={v.evLeaseRate} min={100} max={1200} unit="€/Monat" onChange={set("evLeaseRate")} info="Monatliche Leasingrate, bis zum Ende der Leasinglaufzeit." />
                  {evFoerderungRow}
                </React.Fragment>
              )}
            </VehicleGroup>

            <VehicleGroup vehicle="ice">
              <Tabs tabs={MODE_TABS} value={v.iceMode} onChange={setIceMode} />

              {v.iceMode === "kauf" && (
                <React.Fragment>
                  {iceKaufpreisRow}
                  {iceResidualRow}
                </React.Fragment>
              )}

              {v.iceMode === "finanzierung" && (
                <React.Fragment>
                  <YearsSliderRow label="Finanzierungslaufzeit" value={v.iceFinancingYears} min={1} max={Math.min(v.years, FINANCING_MAX_YEARS)} unit="Jahre" onChange={setIceFinancingYears} info="Laufzeit der Finanzierung in Jahren — maximal 8 Jahre und kann kürzer als die Haltedauer sein. Die Schlussrate wird dann bereits vor Ende der Haltedauer fällig; danach laufen bis zum Ende der Haltedauer nur noch die laufenden Kosten weiter." />
                  {iceKaufpreisRow}
                  <SliderRow label="Anzahlung" value={v.iceDownPayment} min={0} max={v.icePrice * 0.5} step={100} unit="€" onChange={set("iceDownPayment")} info="Anzahlung in €, maximal 50 % des Kaufpreises." />
                  <SliderRow label="Monatliche Rate" value={v.iceMonthlyPayment} min={100} max={1200} unit="€/Monat" onChange={set("iceMonthlyPayment")} info="Monatliche Finanzierungsrate laut Angebot, bis zum Ende der Finanzierungslaufzeit." />
                  <SliderRow label="Schlussrate" value={v.iceBalloon} min={v.icePrice * 0.2} max={v.icePrice * 0.7} step={100} unit="€" onChange={set("iceBalloon")} info="Schlussrate (Restkreditbetrag am Ende der Finanzierungslaufzeit) in €, zwischen 20 % und 70 % des Kaufpreises." />
                  {iceResidualRow}
                  <div style={{ background: "var(--surface-sunken)", borderRadius: "var(--radius-2)", padding: 16, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16 }}>
                    <Stat label="Effektiver Zins (berechnet)" value={`${r.iceFinancingInfo.effectiveRate.toFixed(1)} % p.a.`} />
                    <Stat label="Gesamtanschaffung (berechnet)" value={formatEUR(r.iceFinancingInfo.totalFinancing)} />
                  </div>
                </React.Fragment>
              )}

              {v.iceMode === "leasing" && (
                <React.Fragment>
                  <YearsSliderRow label="Leasingdauer" value={v.iceLeasingYears} min={1} max={Math.min(v.years, LEASING_MAX_YEARS)} unit="Jahre" onChange={setIceLeasingYears} info="Laufzeit des Leasingvertrags in Jahren — maximal 6 Jahre und kann kürzer als die Haltedauer sein. Der Kostenvergleich (Gesamtkosten, Gesamtkosten/km) bezieht sich dann auf die kürzere Leasinglaufzeit; im Diagramm endet die Kurve dieses Fahrzeugs zum Leasingende, während die andere unverändert weiterläuft." />
                  <SliderRow label="Sonderzahlung" value={v.iceLeaseUpfront} min={0} max={15000} unit="€" onChange={set("iceLeaseUpfront")} info="Einmalige Leasing-Sonderzahlung zu Vertragsbeginn." />
                  <SliderRow label="Monatliche Rate" value={v.iceLeaseRate} min={100} max={1200} unit="€/Monat" onChange={set("iceLeaseRate")} info="Monatliche Leasingrate, bis zum Ende der Leasinglaufzeit." />
                </React.Fragment>
              )}
            </VehicleGroup>
          </div>
        </Card>

        <Card title="Energiekosten" padding={24}>
          <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
            <VehicleGroup vehicle="ev">
              <SliderRow label="Strompreis · privat (zuhause)" value={v.priceHome} min={0} max={0.50} unit="€/kWh" onChange={set("priceHome")} info="Preis für Strom aus der eigenen Wallbox oder Steckdose zuhause." />
              <SliderRow label="Strompreis · öffentlich" value={v.pricePublic} min={0.25} max={0.90} unit="€/kWh" onChange={set("pricePublic")} info="Preis an öffentlichen Ladesäulen — meist teurer als Laden zuhause." />
              <SliderRow label="Anteil öffentliches Laden" value={v.publicShare} min={0} max={100} unit="%" onChange={set("publicShare")} info="Wie viel Prozent der Ladevorgänge öffentlich statt zuhause stattfinden." />
            </VehicleGroup>
            <VehicleGroup vehicle="ice">
              <SliderRow label="Benzin- / Dieselpreis" value={v.fuelPrice} min={1.40} max={2.80} unit="€/l" onChange={set("fuelPrice")} info="Aktueller Preis für Benzin oder Diesel pro Liter." />
            </VehicleGroup>
          </div>
        </Card>

        <Card title="Verbrauch" padding={24}>
          <div style={{ display: "flex", flexDirection: "column", gap: 22 }}>
            <SliderRow dot={EV_COLOR} label="Verbrauch E-Auto" value={v.evConsumption} min={12} max={28} unit="kWh/100km" onChange={set("evConsumption")} info="Durchschnittlicher Stromverbrauch des E-Autos pro 100 km." />
            <SliderRow dot={ICE_COLOR} label="Verbrauch Verbrenner" value={v.iceConsumption} min={4} max={13} unit="l/100km" onChange={set("iceConsumption")} info="Durchschnittlicher Kraftstoffverbrauch des Verbrenners pro 100 km." />
          </div>
        </Card>

        <Card title="Nutzung" padding={24}>
          <div style={{ display: "flex", flexDirection: "column", gap: 22 }}>
            <SliderRow label="Jährliche Fahrleistung" value={v.annualKm} min={5000} max={40000} step={100} unit="km/Jahr" onChange={set("annualKm")} info="Wie viele Kilometer pro Jahr gefahren werden." />
          </div>
        </Card>

        <Card title="Reparatur & Wartung" padding={24}>
          <div style={{ display: "flex", flexDirection: "column", gap: 22 }}>
            <SliderRow dot={EV_COLOR} label="Reparatur & Wartung · E-Auto" value={v.evUpkeep} min={200} max={2000} step={50} unit="€/Jahr" onChange={set("evUpkeep")} info="Jährliche Kosten für Wartung, Reparaturen, Reifen etc. beim E-Auto." />
            <SliderRow dot={ICE_COLOR} label="Reparatur & Wartung · Verbrenner" value={v.iceUpkeep} min={200} max={2000} step={50} unit="€/Jahr" onChange={set("iceUpkeep")} info="Jährliche Kosten für Wartung, Reparaturen, Reifen etc. beim Verbrenner." />
          </div>
        </Card>

        <Card title="Versicherung" padding={24}>
          <div style={{ display: "flex", flexDirection: "column", gap: 22 }}>
            <SliderRow dot={EV_COLOR} label="Versicherung · E-Auto" value={v.evInsurance} min={200} max={2000} step={50} unit="€/Jahr" onChange={set("evInsurance")} info="Jährliche Kfz-Versicherungskosten beim E-Auto." />
            <SliderRow dot={ICE_COLOR} label="Versicherung · Verbrenner" value={v.iceInsurance} min={200} max={2000} step={50} unit="€/Jahr" onChange={set("iceInsurance")} info="Jährliche Kfz-Versicherungskosten beim Verbrenner." />
          </div>
        </Card>

        <Card title="Steuern & Förderung" padding={24}>
          <div style={{ display: "flex", flexDirection: "column", gap: 22 }}>
            <SliderRow dot={EV_COLOR} label="THG-Quote-Erlös · E-Auto" value={v.thgQuote} min={0} max={500} step={10} unit="€/Jahr" onChange={set("thgQuote")} info="Jährlicher Erlös aus dem Verkauf der Treibhausgasminderungs-Quote für E-Auto-Halter:innen." />
            <SliderRow dot={ICE_COLOR} label="Kfz-Steuer · Verbrenner" value={v.kfzSteuerICE} min={50} max={400} step={10} unit="€/Jahr" onChange={set("kfzSteuerICE")} info="Jährliche Kfz-Steuer für den Verbrenner, abhängig von Hubraum und Emissionen." />
            <span style={{ font: "var(--font-caption)", color: "var(--text-tertiary)" }}>E-Auto: 0 € Kfz-Steuer (befreit bis 2035)</span>
          </div>
        </Card>

        <Button variant="ghost" size="sm" onClick={reset} style={{ alignSelf: "flex-start" }}>Werte zurücksetzen</Button>
      </div>

      {/* ---- results ---- */}
      <div style={{ position: "sticky", top: 24, display: "flex", flexDirection: "column", gap: 20 }}>
        <Card padding={24}>
          <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 14 }}>
            <StatusDot status={cheaper ? "on" : "attention"} label={cheaper ? "E-Auto günstiger" : "Verbrenner günstiger"} />
          </div>
          <span style={{ font: "var(--font-data)", fontSize: 40, fontWeight: 500, fontVariantNumeric: "tabular-nums", color: cheaper ? "#1F9D55" : "var(--text-primary)" }}>
            {showTotals ? formatEUR(Math.abs(r.tcoDifference)) : `${Math.abs(r.percentCheaper).toFixed(0)} %`}
          </span>
          <div style={{ font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color: "var(--text-tertiary)", marginTop: 6 }}>
            {showTotals
              ? `${cheaper ? "Ersparnis" : "Mehrkosten"} über ${r.compareYears} ${r.compareYears === 1 ? "Jahr" : "Jahre"}`
              : "günstiger pro km (nur ein Fahrzeug least)"}
          </div>
        </Card>

        <Card title="Gesamtkosten im Vergleich" padding={20}>
          <TCOChart evSeries={r.evSeries} iceSeries={r.iceSeries} chartYears={r.chartYears} />
          <div style={{ display: "flex", gap: 20, marginTop: 12 }}>
            <span style={{ display: "flex", alignItems: "center", gap: 6, font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color: "var(--text-secondary)" }}>
              <span style={{ width: 8, height: 8, borderRadius: 999, background: "#1F9D55", display: "inline-block" }}></span>E-Auto
            </span>
            <span style={{ display: "flex", alignItems: "center", gap: 6, font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color: "var(--text-secondary)" }}>
              <span style={{ width: 8, height: 8, borderRadius: 999, background: "#FF6A00", display: "inline-block" }}></span>Verbrenner
            </span>
            <span style={{ marginLeft: "auto", font: "var(--font-data)", fontSize: 10, color: "var(--text-tertiary)" }}>Kumulierte Gesamtkosten in €</span>
          </div>
        </Card>

        <Card padding={20}>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 18 }}>
            {showTotals && (
              <React.Fragment>
                <Stat label="E-Auto · Gesamtkosten" value={formatEUR(r.totalTCOEV)} />
                <Stat label="Verbrenner · Gesamtkosten" value={formatEUR(r.totalTCOICE)} />
                <Stat label="Günstiger" value={`${r.percentCheaper.toFixed(0)} %`} />
              </React.Fragment>
            )}
            <Stat label="E-Auto · Gesamtkosten/km" value={r.tcoPerKmEV.toFixed(3).replace(".", ",")} />
            <Stat label="Verbrenner · Gesamtkosten/km" value={r.tcoPerKmICE.toFixed(3).replace(".", ",")} />
            {!showTotals && <Stat label="Günstiger" value={`${r.percentCheaper.toFixed(0)} %`} />}
          </div>
        </Card>

        <p style={{ font: "var(--font-caption)", color: "var(--text-tertiary)", margin: 0 }}>
          Dieser Rechner dient ausschließlich der Information und stellt keine Kaufberatung dar.
        </p>
      </div>
    </div>
  );
}

Object.assign(window, { TCOCalculatorDeploy });
