// PV-Rechner — Live-Schätzung von Ertrag, Einsparung und Amortisation einer
// Photovoltaikanlage.
const { Card, Slider, Button, Tooltip, Switch } = window.GoodDesignRamsDesignSystem_019e30;

// Symbolisierter Kompass für die Ausrichtung — Kreis mit Haupt-/Nebenticks in
// 22,5°-Schritten, Beschriftung an den vier Haupthimmelsrichtungen und einer
// Nadel in der Akzentfarbe. Ziehen, Klicken oder Pfeiltasten setzen den Wert;
// bewusst als eigenes, leichtgewichtiges SVG statt eines generischen Reglers,
// weil die Ausrichtung hier eine Richtung und keine bloße Zahl ist.
function Compass({ value, onChange, size = 96, color = "#1F9D55" }) {
  const ref = React.useRef(null);
  const draggingRef = React.useRef(false);

  const angleFromEvent = (e) => {
    const rect = ref.current.getBoundingClientRect();
    const cx = rect.left + rect.width / 2;
    const cy = rect.top + rect.height / 2;
    const dx = e.clientX - cx;
    const dy = e.clientY - cy;
    let deg = (Math.atan2(dx, -dy) * 180) / Math.PI;
    if (deg < 0) deg += 360;
    return Math.round(deg);
  };

  const handlePointerDown = (e) => {
    ref.current.setPointerCapture(e.pointerId);
    draggingRef.current = true;
    onChange(angleFromEvent(e));
  };
  const handlePointerMove = (e) => {
    if (!draggingRef.current) return;
    onChange(angleFromEvent(e));
  };
  const handlePointerUp = (e) => {
    draggingRef.current = false;
    try { ref.current.releasePointerCapture(e.pointerId); } catch (err) {}
  };
  const handleKeyDown = (e) => {
    if (e.key === "ArrowUp" || e.key === "ArrowRight") { e.preventDefault(); onChange((value + 1) % 360); }
    else if (e.key === "ArrowDown" || e.key === "ArrowLeft") { e.preventDefault(); onChange((value + 359) % 360); }
  };

  const ticks = [];
  for (let deg = 0; deg < 360; deg += 22.5) {
    const major = deg % 90 === 0;
    const r1 = major ? 32 : 36;
    const rad = (deg * Math.PI) / 180;
    ticks.push(
      <line key={deg}
        x1={50 + r1 * Math.sin(rad)} y1={50 - r1 * Math.cos(rad)}
        x2={50 + 42 * Math.sin(rad)} y2={50 - 42 * Math.cos(rad)}
        stroke={major ? "var(--text-secondary)" : "var(--border-hairline)"}
        strokeWidth={major ? 1.5 : 1}
      />
    );
  }
  const needleRad = (value * Math.PI) / 180;
  const tipX = 50 + 30 * Math.sin(needleRad), tipY = 50 - 30 * Math.cos(needleRad);

  return (
    <svg
      ref={ref} viewBox="0 0 100 100" width={size} height={size}
      role="slider" tabIndex={0} aria-label="Ausrichtung"
      aria-valuemin={0} aria-valuemax={360} aria-valuenow={value} aria-valuetext={`${value}°, ${compassLabel(value)}`}
      onPointerDown={handlePointerDown} onPointerMove={handlePointerMove}
      onPointerUp={handlePointerUp} onPointerCancel={handlePointerUp} onKeyDown={handleKeyDown}
      style={{ cursor: "pointer", touchAction: "none", outline: "none", flexShrink: 0 }}
    >
      <circle cx="50" cy="50" r="42" fill="var(--surface-card)" stroke="var(--border-hairline)" strokeWidth="1" />
      {ticks}
      {[{ deg: 0, text: "N" }, { deg: 90, text: "O" }, { deg: 180, text: "S" }, { deg: 270, text: "W" }].map((l) => {
        const rad = (l.deg * Math.PI) / 180;
        return (
          <text key={l.text} x={50 + 24 * Math.sin(rad)} y={50 - 24 * Math.cos(rad) + 3.5}
            textAnchor="middle" fontSize="9" fontFamily="var(--font-mono)" fontWeight="600" fill="var(--text-tertiary)">
            {l.text}
          </text>
        );
      })}
      <line x1="50" y1="50" x2={tipX} y2={tipY} stroke={color} strokeWidth="2.5" strokeLinecap="round" />
      <circle cx="50" cy="50" r="3" fill={color} />
      <circle cx={tipX} cy={tipY} r="4" fill={color} />
    </svg>
  );
}

function SliderRow({ label, value, min, max, step = "any", unit, info, onChange }) {
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
      <div style={{ flex: 1 }}>
        <Slider label={label} 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>
  );
}

const GERMANY_BOUNDS = [[47.2, 5.5], [55.2, 15.5]];

// Eine Farbe je Teilanlage — dieselbe Zuordnung zieht sich durch Kartenmarker,
// Kompassnadel, Kartenrahmen der Eingabegruppen und die Tagesverlaufs-
// Diagramme, damit Teilanlagen überall eindeutig unterscheidbar bleiben.
// Größtenteils Design-System-Tokens (accent-orange/slate/yellow) — ohne
// Grün, das ist der Summenkurve im Tagesverlauf-Diagramm vorbehalten. Statt
// accent-red (zu nah an orange) ein Violett, das mit den anderen harmoniert.
const SUBARRAY_COLORS = ["#FF6A00", "#64798A", "#6B46C1", "#FEBC2E"];
function subArrayColor(i) {
  return SUBARRAY_COLORS[i % SUBARRAY_COLORS.length];
}

function reverseGeocode(lat, lon) {
  const url = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lon}&zoom=12&accept-language=de`;
  return fetch(url)
    .then((r) => r.json())
    .then((data) => {
      const a = data.address || {};
      return a.city || a.town || a.village || a.municipality || a.county || data.display_name || null;
    })
    .catch(() => null);
}

// Leaflet-Karte mit ziehbarer Stecknadel, auf Deutschland begrenzt. Leaflet
// ist DOM-imperativ und kein React-Component — die Karteninstanz lebt in
// Refs, React-State synchronisiert nur Position und Zentrierung.
function LocationMap({ lat, lon, zoomRequest, onPick }) {
  const containerRef = React.useRef(null);
  const mapRef = React.useRef(null);
  const markerRef = React.useRef(null);

  React.useEffect(() => {
    const bounds = L.latLngBounds(GERMANY_BOUNDS).pad(0.1);
    const map = L.map(containerRef.current, {
      center: [lat, lon],
      zoom: 6,
      minZoom: 6,
      maxZoom: 16,
      maxBounds: bounds,
      maxBoundsViscosity: 1.0,
    });
    L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noopener">OpenStreetMap</a>',
      maxZoom: 19,
    }).addTo(map);

    const marker = L.marker([lat, lon], { draggable: true }).addTo(map);
    marker.on("dragend", () => {
      const p = marker.getLatLng();
      onPick(p.lat, p.lng);
    });
    map.on("click", (e) => {
      if (!bounds.contains(e.latlng)) return;
      marker.setLatLng(e.latlng);
      onPick(e.latlng.lat, e.latlng.lng);
    });

    mapRef.current = map;
    markerRef.current = marker;
    return () => map.remove();
  }, []);

  const appliedNonceRef = React.useRef(0);
  React.useEffect(() => {
    const marker = markerRef.current, map = mapRef.current;
    if (!marker || !map) return;
    const cur = marker.getLatLng();
    const moved = Math.abs(cur.lat - lat) > 1e-6 || Math.abs(cur.lng - lon) > 1e-6;
    if (moved) marker.setLatLng([lat, lon]);
    const nonce = zoomRequest ? zoomRequest.nonce : 0;
    if (zoomRequest && nonce !== appliedNonceRef.current) {
      appliedNonceRef.current = nonce;
      map.setView([lat, lon], zoomRequest.zoom);
    } else if (moved) {
      map.panTo([lat, lon]);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [lat, lon, zoomRequest && zoomRequest.nonce]);

  return (
    <div
      ref={containerRef}
      style={{ width: "100%", height: 260, borderRadius: "var(--radius-3)", overflow: "hidden", border: "var(--hairline)" }}
    />
  );
}

// Ortssuche über die freie Nominatim-API (OpenStreetMap) — auf Deutschland
// begrenzt, mit Debounce, um die öffentliche Schnittstelle nicht unnötig zu
// belasten.
function LocationSearch({ onSelect }) {
  const [query, setQuery] = React.useState("");
  const [results, setResults] = React.useState([]);
  const [open, setOpen] = React.useState(false);
  const timerRef = React.useRef(null);
  const boxRef = React.useRef(null);

  React.useEffect(() => {
    const onDocClick = (e) => { if (boxRef.current && !boxRef.current.contains(e.target)) setOpen(false); };
    document.addEventListener("mousedown", onDocClick);
    return () => document.removeEventListener("mousedown", onDocClick);
  }, []);

  const runSearch = (q) => {
    clearTimeout(timerRef.current);
    if (q.trim().length < 3) { setResults([]); return; }
    timerRef.current = setTimeout(() => {
      const url = `https://nominatim.openstreetmap.org/search?format=json&countrycodes=de&addressdetails=1&limit=6&accept-language=de&q=${encodeURIComponent(q)}`;
      fetch(url)
        .then((r) => r.json())
        .then((data) => { setResults(data); setOpen(true); })
        .catch(() => setResults([]));
    }, 450);
  };

  return (
    <div ref={boxRef} style={{ position: "relative" }}>
      <input
        type="text"
        value={query}
        placeholder="Ort suchen — Stadt, Adresse oder PLZ…"
        onChange={(e) => { setQuery(e.target.value); runSearch(e.target.value); }}
        onFocus={() => results.length && setOpen(true)}
        style={{
          width: "100%", height: 38, padding: "0 12px", boxSizing: "border-box",
          border: "var(--hairline-strong)", borderRadius: "var(--radius-2)",
          font: "var(--font-body)", fontSize: 14,
          background: "var(--surface-card)", color: "var(--text-primary)", outline: "none",
        }}
      />
      {open && results.length > 0 && (
        <div style={{
          position: "absolute", top: "calc(100% + 4px)", left: 0, right: 0, zIndex: 1000,
          background: "var(--surface-card)", border: "var(--hairline-strong)", borderRadius: "var(--radius-2)",
          boxShadow: "var(--shadow-1)", overflow: "hidden", maxHeight: 220, overflowY: "auto",
        }}>
          {results.map((r) => (
            <button
              key={r.place_id} type="button"
              onClick={() => {
                onSelect(parseFloat(r.lat), parseFloat(r.lon), r.display_name, 12);
                setQuery(r.display_name);
                setOpen(false);
              }}
              style={{
                display: "block", width: "100%", textAlign: "left", padding: "9px 12px",
                border: "none", borderBottom: "var(--hairline)", background: "transparent", cursor: "pointer",
                font: "var(--font-body)", fontSize: 13, color: "var(--text-primary)",
              }}
              onMouseEnter={(e) => (e.currentTarget.style.background = "var(--state-hover)")}
              onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
            >
              {r.display_name}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

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>
  );
}

// Balkendiagramm des Jahresverlaufs — gleiche visuelle Sprache wie die
// Liniendiagramme der anderen Rechner (Haarlinien-Raster, Mono-Beschriftung,
// ein Akzent). Jeder Balken ist gestapelt: unten Eigenverbrauch (Akzentfarbe),
// oben Einspeisung (helleres Grün) — Gesamthöhe ist der monatliche Ertrag.
function PVChart({ monthly, selfConsumed, months, color = "#1F9D55", colorFedIn = "rgba(31, 157, 85, 0.28)" }) {
  const width = 560, height = 220;
  const pad = { top: 16, right: 16, bottom: 32, left: 60 };
  const plotW = width - pad.left - pad.right;
  const plotH = height - pad.top - pad.bottom;
  const maxV = Math.max(...monthly, 1);
  const barGap = 8;
  const barW = (plotW - barGap * (monthly.length - 1)) / monthly.length;
  const yFor = (v) => pad.top + (1 - v / maxV) * plotH;
  const gridSteps = [0, 0.5, 1];

  return (
    <svg viewBox={`0 0 ${width} ${height}`} style={{ width: "100%", height: "auto", overflow: "visible" }}>
      {gridSteps.map((s) => {
        const v = s * maxV;
        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 + 4} textAnchor="end" fontSize="12" fontFamily="var(--font-mono)" fill="var(--text-secondary)">
              {Math.round(v).toLocaleString("de-DE")}
            </text>
          </g>
        );
      })}
      {monthly.map((v, i) => {
        const sc = Math.min(selfConsumed[i], v);
        const x = pad.left + i * (barW + barGap);
        const yTotal = yFor(v);
        const ySelf = yFor(sc);
        const hTotal = pad.top + plotH - yTotal;
        const hSelf = pad.top + plotH - ySelf;
        return (
          <g key={i}>
            <rect x={x} y={yTotal} width={barW} height={Math.max(0, hTotal)} rx="2" fill={colorFedIn} />
            <rect x={x} y={ySelf} width={barW} height={Math.max(0, hSelf)} rx="2" fill={color} />
          </g>
        );
      })}
      {months.map((m, i) => {
        const x = pad.left + i * (barW + barGap) + barW / 2;
        return (
          <text key={m} x={x} y={height - 8} textAnchor="middle" fontSize="11" fontFamily="var(--font-mono)" fill="var(--text-secondary)">
            {m}
          </text>
        );
      })}
    </svg>
  );
}

// Tagesverlauf-Werte liegen als Energie je Zeitschritt (kWh) vor — für die
// Anzeige als Leistungskurve wird durch die Schrittweite geteilt, um die
// durchschnittliche Leistung (kW) je Zeitpunkt zu erhalten.
function dailyPowerKW(values, hours) {
  const stepHours = hours[1] - hours[0];
  return values.map((v) => v / stepHours);
}

// Tagesverlauf einer Teilanlage — Sommer (durchgezogen) und Winter
// (gestrichelt) in derselben Farbe wie die Teilanlage, um die Zuordnung zum
// Kartenrahmen und zur Eingabegruppe zu erhalten.
function DailyChart({ hours, summer, winter, color }) {
  const width = 400, height = 120;
  const pad = { top: 10, right: 10, bottom: 20, left: 32 };
  const plotW = width - pad.left - pad.right;
  const plotH = height - pad.top - pad.bottom;
  const summerKW = dailyPowerKW(summer, hours);
  const winterKW = dailyPowerKW(winter, hours);
  const maxV = Math.max(...summerKW, ...winterKW, 0.1);
  const xFor = (i) => pad.left + (i / (hours.length - 1)) * plotW;
  const yFor = (v) => pad.top + (1 - v / maxV) * plotH;
  const pathFor = (values) => values.map((v, i) => `${i === 0 ? "M" : "L"} ${xFor(i)} ${yFor(v)}`).join(" ");
  const tickHours = [6, 12, 18];

  return (
    <svg viewBox={`0 0 ${width} ${height}`} style={{ width: "100%", height: "auto", overflow: "visible" }}>
      <line x1={pad.left} x2={width - pad.right} y1={pad.top} y2={pad.top} stroke="var(--border-hairline)" strokeWidth="1" />
      <text x={pad.left - 6} y={pad.top + 3} textAnchor="end" fontSize="9" fontFamily="var(--font-mono)" fill="var(--text-tertiary)">
        {maxV.toFixed(1)}
      </text>
      <line x1={pad.left} x2={width - pad.right} y1={pad.top + plotH} y2={pad.top + plotH} stroke="var(--border-hairline)" strokeWidth="1" />
      <path d={pathFor(winterKW)} fill="none" stroke={color} strokeWidth="1.5" strokeDasharray="3 3" opacity="0.8" />
      <path d={pathFor(summerKW)} fill="none" stroke={color} strokeWidth="2" />
      {tickHours.map((h) => {
        const idx = hours.indexOf(h);
        if (idx === -1) return null;
        return (
          <text key={h} x={xFor(idx)} y={height - 5} textAnchor="middle" fontSize="9" fontFamily="var(--font-mono)" fill="var(--text-tertiary)">
            {h}:00
          </text>
        );
      })}
    </svg>
  );
}

// Überlagerter Tagesverlauf aller Teilanlagen — dieselbe Sommer-/
// Winter-Darstellung wie DailyChart, aber alle Teilanlagen in einer
// gemeinsamen Grafik mit geteilter y-Achse, je in ihrer Teilanlagenfarbe,
// um Form und Anteil der einzelnen Dachflächen am durchschnittlichen Tag
// vergleichen zu können.
function DailyOverlayChart({ profiles }) {
  const width = 400, height = 160;
  const pad = { top: 10, right: 10, bottom: 20, left: 40 };
  const plotW = width - pad.left - pad.right;
  const plotH = height - pad.top - pad.bottom;
  const hours = profiles[0].hours;
  const series = profiles.map((p) => ({
    color: p.color,
    summer: dailyPowerKW(p.summer, hours),
    winter: dailyPowerKW(p.winter, hours),
  }));
  // Summenkurve über alle Teilanlagen — nur bei mehr als einer Teilanlage
  // eine eigene Linie wert, sonst deckungsgleich mit der einzigen Teilkurve.
  const total = hours.map((_, i) => ({
    summer: series.reduce((sum, s) => sum + s.summer[i], 0),
    winter: series.reduce((sum, s) => sum + s.winter[i], 0),
  }));
  const totalSummer = total.map((t) => t.summer);
  const totalWinter = total.map((t) => t.winter);
  const maxV = Math.max(...series.flatMap((s) => [...s.summer, ...s.winter]), ...totalSummer, ...totalWinter, 0.1);
  const xFor = (i) => pad.left + (i / (hours.length - 1)) * plotW;
  const yFor = (v) => pad.top + (1 - v / maxV) * plotH;
  const pathFor = (values) => values.map((v, i) => `${i === 0 ? "M" : "L"} ${xFor(i)} ${yFor(v)}`).join(" ");
  const tickHours = [6, 12, 18];

  return (
    <svg viewBox={`0 0 ${width} ${height}`} style={{ width: "100%", height: "auto", overflow: "visible" }}>
      <line x1={pad.left} x2={width - pad.right} y1={pad.top} y2={pad.top} stroke="var(--border-hairline)" strokeWidth="1" />
      <text x={pad.left - 6} y={pad.top + 3} textAnchor="end" fontSize="9" fontFamily="var(--font-mono)" fill="var(--text-tertiary)">
        {maxV.toFixed(1)} kW
      </text>
      <line x1={pad.left} x2={width - pad.right} y1={pad.top + plotH} y2={pad.top + plotH} stroke="var(--border-hairline)" strokeWidth="1" />
      {series.map((s, i) => (
        <g key={i}>
          <path d={pathFor(s.winter)} fill="none" stroke={s.color} strokeWidth="1.5" strokeDasharray="3 3" opacity="0.8" />
          <path d={pathFor(s.summer)} fill="none" stroke={s.color} strokeWidth="2" />
        </g>
      ))}
      {profiles.length > 1 && (
        <g>
          <path d={pathFor(totalWinter)} fill="none" stroke="#1F9D55" strokeWidth="2" strokeDasharray="3 3" opacity="0.8" />
          <path d={pathFor(totalSummer)} fill="none" stroke="#1F9D55" strokeWidth="2.5" />
        </g>
      )}
      {tickHours.map((h) => {
        const idx = hours.indexOf(h);
        if (idx === -1) return null;
        return (
          <text key={h} x={xFor(idx)} y={height - 5} textAnchor="middle" fontSize="9" fontFamily="var(--font-mono)" fill="var(--text-tertiary)">
            {h}:00
          </text>
        );
      })}
    </svg>
  );
}

const DEFAULTS = {
  lat: 50.11,
  lon: 8.68,
  locationLabel: "Frankfurt am Main",
  subArrays: [{ tilt: 35, azimuth: 180, sizeKWp: 8 }],
  investment: 12800,
  electricityPrice: 0.30,
  feedInTariff: 0.08,
  selfConsumptionShare: 30,
  batteryEnabled: false,
  batteryKWh: 8,
};

const DECIMALS = {
  investment: 0, electricityPrice: 2, feedInTariff: 2, selfConsumptionShare: 0, batteryKWh: 1,
};

// Startwerte für den Balkonkraftwerk-Rechner: kleine Anlage (800 Wp) am
// Geländer (senkrecht, Süd), günstige Investition, kein Einspeisetarif (bei
// Stecker-Solar meist verschenkter Überschuss) und ein hoher
// Eigenverbrauchsanteil, weil die geringe Leistung fast vollständig von der
// Grundlast des Haushalts aufgenommen wird.
const BALCONY_DEFAULTS = {
  lat: 50.11,
  lon: 8.68,
  locationLabel: "Frankfurt am Main",
  subArrays: [{ tilt: 90, azimuth: 180, sizeKWp: 0.8 }],
  investment: 600,
  electricityPrice: 0.35,
  feedInTariff: 0,
  selfConsumptionShare: 70,
  batteryEnabled: false,
  batteryKWh: 2,
};

// Presets, mit denen dieselbe Komponente entweder als voller PV-Rechner oder
// als abgespeckter Balkonkraftwerk-Rechner läuft (Leistung in Watt statt kWp,
// höchstens zwei Ausrichtungen, kleinerer Speicher, höherer Eigenverbrauch).
// Ohne Preset = voller Rechner; die `full`-Werte bilden das bisherige
// Verhalten exakt ab.
const PV_PRESETS = {
  full: {
    defaults: DEFAULTS,
    counts: [1, 2, 3, 4],
    addSizeKWp: 4,
    addTilt: 35,
    countTitle: "Teilanlagen",
    countHint: "Getrennte Dachflächen mit unterschiedlicher Ausrichtung oder Neigung, z. B. eine Ost/West-Aufteilung.",
    arrayCardTitle: "Ausrichtung, Neigung & Größe",
    subArrayLabel: "Teilanlage",
    tiltLabel: "Dachneigung",
    tiltInfo: "Neigungswinkel dieser Teilanlage gegenüber der Horizontalen. Optimal für Deutschland: ca. 35°.",
    sizeLabel: "Anlagengröße",
    sizeInfo: "Installierte Leistung dieser Teilanlage in Kilowatt-Peak.",
    size: { min: 0.5, max: 30, step: 0.5, unit: "kWp", fromKWp: (k) => k, toKWp: (v) => roundTo(v, 1) },
    battery: { min: 1, max: 30, step: 0.5 },
    investment: { min: 2000, max: 40000, step: 100 },
    suggest: { base: 30, cap: 75, scale: 1.2 },
  },
  balcony: {
    defaults: BALCONY_DEFAULTS,
    counts: [1, 2],
    addSizeKWp: 0.4,
    addTilt: 90,
    countTitle: "Ausrichtung",
    countHint: "Ein Balkonkraftwerk lässt sich auf bis zu zwei Richtungen aufteilen, z. B. Ost und West.",
    arrayCardTitle: "Ausrichtung, Neigung & Leistung",
    subArrayLabel: "Ausrichtung",
    tiltLabel: "Modulneigung",
    tiltInfo: "Neigungswinkel der Module: 0° = flach liegend, 90° = senkrecht am Geländer. Angewinkelt (30–60°) bringt in Summe meist mehr Ertrag.",
    sizeLabel: "Modulleistung",
    sizeInfo: "Modulleistung dieser Ausrichtung in Watt-Peak (Wp). Typische Balkonkraftwerke liegen bei 400–2000 Wp.",
    size: { min: 200, max: 4000, step: 50, unit: "W", fromKWp: (k) => Math.round(k * 1000), toKWp: (v) => Math.round(v) / 1000 },
    battery: { min: 1, max: 18, step: 0.5 },
    investment: { min: 200, max: 3000, step: 50 },
    suggest: { base: 70, cap: 95, scale: 1.0 },
  },
};
if (typeof window !== "undefined") window.PV_PRESETS = PV_PRESETS;

function PVCalculatorDeploy({ config }) {
  const cfg = config || PV_PRESETS.full;
  const [v, setV] = React.useState(cfg.defaults);
  const set = (key) => (e) => setV((s) => ({ ...s, [key]: roundTo(+e.target.value, DECIMALS[key]) }));
  const reset = () => setV(cfg.defaults);

  // Speicher an-/abwählen. Beim Aktivieren springt der Jahres-
  // Eigenverbrauchsanteil auf einen zur Speichergröße passenden Vorschlag;
  // danach bleibt er frei per Regler einstellbar. Beim Abwählen zurück auf den
  // speicherlosen Standardwert.
  const toggleBattery = (checked) => {
    setV((s) => {
      if (!checked) return { ...s, batteryEnabled: false, selfConsumptionShare: cfg.defaults.selfConsumptionShare };
      const totalKWp = s.subArrays.reduce((sum, sa) => sum + sa.sizeKWp, 0);
      return { ...s, batteryEnabled: true, selfConsumptionShare: suggestedSelfConsumptionShare(s.batteryKWh, totalKWp, cfg.suggest.base, cfg.suggest.cap, cfg.suggest.scale) };
    });
  };

  const updateSubArray = (index, key, value) => {
    setV((s) => ({
      ...s,
      subArrays: s.subArrays.map((sa, i) => (i === index ? { ...sa, [key]: value } : sa)),
    }));
  };

  const setSubArrayCount = (n) => {
    setV((s) => {
      const current = s.subArrays;
      if (n === current.length) return s;
      if (n < current.length) return { ...s, subArrays: current.slice(0, n) };
      const additions = [];
      for (let i = current.length; i < n; i++) {
        additions.push({ tilt: cfg.addTilt, azimuth: i === 3 ? 0 : i % 2 === 1 ? 90 : 270, sizeKWp: cfg.addSizeKWp });
      }
      return { ...s, subArrays: [...current, ...additions] };
    });
  };

  const geocodeTokenRef = React.useRef(0);
  const pickLocation = (lat, lon, label, forceZoom) => {
    const token = ++geocodeTokenRef.current;
    setV((s) => ({
      ...s, lat, lon, locationLabel: label || "Ausgewählter Standort",
      zoomRequest: forceZoom ? { zoom: forceZoom, nonce: (s.zoomRequest ? s.zoomRequest.nonce : 0) + 1 } : s.zoomRequest,
    }));
    if (!label) {
      // Token-Guard: falls inzwischen ein neuerer Klick/Drag ausgelöst wurde,
      // darf die spätere (aber zu einer älteren Anfrage gehörende) Antwort
      // das schon aktuellere Label nicht mehr überschreiben.
      reverseGeocode(lat, lon).then((resolved) => {
        if (resolved && geocodeTokenRef.current === token) {
          setV((s) => ({ ...s, locationLabel: resolved }));
        }
      });
    }
  };

  const irradiance = irradianceAt(v.lat, v.lon);
  const r = computePV({
    irradiance,
    subArrays: v.subArrays,
    electricityPrice: v.electricityPrice,
    feedInTariff: v.feedInTariff,
    selfConsumptionShare: v.selfConsumptionShare,
    investment: v.investment,
    batteryKWh: v.batteryEnabled ? v.batteryKWh : 0,
  });
  const dailyProfiles = v.subArrays.map((sa, i) => ({
    ...subArrayDailyProfiles(sa, irradiance, v.lat),
    color: subArrayColor(i),
  }));

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

      {/* ---- inputs ---- */}
      <div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
        <Card title="Standort" padding={24}>
          <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            <span style={{ font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color: "var(--text-tertiary)" }}>Standort</span>
            <LocationSearch onSelect={pickLocation} />
            <LocationMap lat={v.lat} lon={v.lon} zoomRequest={v.zoomRequest} onPick={pickLocation} />
            <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
              {PV_LOCATIONS.map((c) => {
                const active = c.name === v.locationLabel;
                return (
                  <button
                    key={c.name} type="button"
                    onClick={() => pickLocation(c.lat, c.lon, c.name, 12)}
                    style={{
                      padding: "5px 10px", borderRadius: 999, cursor: "pointer",
                      border: active ? "1px solid #1F9D55" : "var(--hairline-strong)",
                      background: active ? "rgba(31,157,85,0.08)" : "var(--surface-card)",
                      color: active ? "#1F9D55" : "var(--text-secondary)",
                      font: "var(--font-label)", fontSize: 11, letterSpacing: "var(--text-label-tracking)",
                    }}
                  >
                    {c.name}
                  </button>
                );
              })}
            </div>
            <span style={{ display: "flex", alignItems: "center", gap: 6, font: "var(--font-caption)", color: "var(--text-tertiary)" }}>
              <span style={{ width: 6, height: 6, borderRadius: 999, background: "#1F9D55", display: "inline-block", flexShrink: 0 }}></span>
              {v.locationLabel} — bestimmt die durchschnittliche Sonneneinstrahlung am Standort.
            </span>
          </div>
        </Card>

        <Card title={cfg.countTitle} padding={24}>
          <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            <span style={{ font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color: "var(--text-tertiary)" }}>Anzahl</span>
            <div style={{ display: "flex", gap: 8 }}>
              {cfg.counts.map((n) => {
                const active = v.subArrays.length === n;
                return (
                  <button
                    key={n} type="button"
                    onClick={() => setSubArrayCount(n)}
                    style={{
                      width: 40, height: 36, borderRadius: "var(--radius-2)", cursor: "pointer",
                      border: active ? "1px solid #1F9D55" : "var(--hairline-strong)",
                      background: active ? "rgba(31,157,85,0.08)" : "var(--surface-card)",
                      color: active ? "#1F9D55" : "var(--text-primary)",
                      font: "var(--font-data)", fontSize: 14, fontWeight: 500,
                    }}
                  >
                    {n}
                  </button>
                );
              })}
            </div>
            <span style={{ font: "var(--font-caption)", color: "var(--text-tertiary)" }}>
              {cfg.countHint}
            </span>
          </div>
        </Card>

        <Card title={cfg.arrayCardTitle} padding={24}>
          <div style={{ display: "flex", flexDirection: "column", gap: 24 }}>
            {v.subArrays.map((sa, i) => {
              const color = subArrayColor(i);
              const daily = dailyProfiles[i];
              return (
                <div
                  key={i}
                  style={{
                    display: "flex", flexDirection: "column", gap: 22,
                    paddingTop: i > 0 ? 24 : 0, borderTop: i > 0 ? "var(--hairline)" : "none",
                    borderLeft: `3px solid ${color}`, paddingLeft: 14,
                  }}
                >
                  {v.subArrays.length > 1 && (
                    <span style={{ font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color }}>
                      {cfg.subArrayLabel} {i + 1}
                    </span>
                  )}
                  <div style={{ display: "flex", alignItems: "center", gap: 16 }}>
                    <Compass value={sa.azimuth} onChange={(az) => updateSubArray(i, "azimuth", az)} size={96} color={color} />
                    <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
                      <span style={{ font: "var(--font-data)", fontSize: 20, fontWeight: 500, color: "var(--text-primary)", fontVariantNumeric: "tabular-nums" }}>{sa.azimuth}° · {compassLabel(sa.azimuth)}</span>
                      <span style={{ font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color: "var(--text-tertiary)" }}>Ausrichtung — ziehen oder Pfeiltasten, 180° = Süden</span>
                    </div>
                  </div>
                  <SliderRow label={cfg.tiltLabel} value={sa.tilt} min={0} max={90} step={1} unit="°" onChange={(e) => updateSubArray(i, "tilt", roundTo(+e.target.value, 0))} info={cfg.tiltInfo} />
                  <SliderRow label={cfg.sizeLabel} value={cfg.size.fromKWp(sa.sizeKWp)} min={cfg.size.min} max={cfg.size.max} step={cfg.size.step} unit={cfg.size.unit} onChange={(e) => updateSubArray(i, "sizeKWp", cfg.size.toKWp(+e.target.value))} info={cfg.sizeInfo} />
                  <div>
                    <span style={{ font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color: "var(--text-tertiary)" }}>Tagesverlauf — Sommer &amp; Winter</span>
                    <DailyChart hours={daily.hours} summer={daily.summer} winter={daily.winter} color={color} />
                    <div style={{ display: "flex", gap: 16, marginTop: 4 }}>
                      <span style={{ display: "flex", alignItems: "center", gap: 6, font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color: "var(--text-secondary)" }}>
                        <span style={{ width: 14, height: 2, background: color, display: "inline-block" }}></span>Sommer
                      </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: 14, height: 0, borderTop: `2px dashed ${color}`, display: "inline-block" }}></span>Winter
                      </span>
                      <span style={{ marginLeft: "auto", font: "var(--font-data)", fontSize: 10, color: "var(--text-tertiary)" }}>kW</span>
                    </div>
                  </div>
                </div>
              );
            })}
          </div>
        </Card>

        <Card title="Batteriespeicher" padding={24}>
          <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
            <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }}>
              <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
                <span style={{ font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color: "var(--text-tertiary)" }}>Speicher einplanen</span>
                <span style={{ font: "var(--font-caption)", color: "var(--text-tertiary)" }}>Verschiebt Solarstrom in Abend- und Nachtstunden und hebt den Eigenverbrauch an.</span>
              </div>
              <Switch checked={v.batteryEnabled} accent onChange={(e) => toggleBattery(e.target.checked)} />
            </div>
            {v.batteryEnabled && (
              <div style={{ display: "flex", flexDirection: "column", gap: 12, paddingTop: 4, borderTop: "var(--hairline)" }}>
                <SliderRow label="Speichergröße" value={v.batteryKWh} min={cfg.battery.min} max={cfg.battery.max} step={cfg.battery.step} unit="kWh" onChange={set("batteryKWh")} info="Nutzbare Kapazität des Batteriespeichers. Faustregel (HTW Berlin): etwa 1 kWh je kWp Anlagenleistung." />
                <span style={{ font: "var(--font-caption)", color: "var(--text-tertiary)" }}>
                  Geschätzte Speicherkosten: <strong style={{ color: "var(--text-secondary)" }}>{formatEUR(v.batteryKWh * BATTERY_COST_PER_KWH)}</strong> (rund {BATTERY_COST_PER_KWH} €/kWh installiert) — fließen in die Amortisation ein.
                </span>
              </div>
            )}
          </div>
        </Card>

        <Card title="Kosten & Nutzung" padding={24}>
          <div style={{ display: "flex", flexDirection: "column", gap: 22 }}>
            <SliderRow label="Investitionskosten" value={v.investment} min={cfg.investment.min} max={cfg.investment.max} step={cfg.investment.step} unit="€" onChange={set("investment")} info="Gesamtkosten der Anlage inklusive Installation, ohne Speicher." />
            <SliderRow label="Strompreis" value={v.electricityPrice} min={0.20} max={0.45} unit="€/kWh" onChange={set("electricityPrice")} info="Preis für Strom aus dem Netz, den selbst genutzter PV-Strom ersetzt." />
            <SliderRow label="Einspeisevergütung" value={v.feedInTariff} min={0} max={0.15} unit="€/kWh" onChange={set("feedInTariff")} info="Vergütung pro eingespeister Kilowattstunde nach EEG." />
            <SliderRow label="Eigenverbrauchsanteil" value={v.selfConsumptionShare} min={0} max={100} step={1} unit="%" onChange={set("selfConsumptionShare")} info="Anteil der Jahres-Stromerzeugung, den du selbst nutzt (Rest wird eingespeist). Er gilt fürs ganze Jahr — je Monat liegt er im Winter höher (wenig Ertrag, fast vollständig genutzt) und im Sommer niedriger. Ohne Speicher üblich: 20–35 %, mit Speicher deutlich mehr. Beim Aktivieren des Speichers wird ein passender Wert vorgeschlagen; du kannst ihn danach frei anpassen." />
          </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={{ font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color: "var(--text-tertiary)", marginBottom: 10 }}>
            Jährliche Einsparung
          </div>
          <span style={{ font: "var(--font-data)", fontSize: 40, fontWeight: 500, fontVariantNumeric: "tabular-nums", color: "#1F9D55" }}>
            {formatEUR(r.annualSavings)}
          </span>
          <div style={{ font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color: "var(--text-tertiary)", marginTop: 6 }}>
            {r.paybackYears > 0 ? `Amortisation nach ${r.paybackYears.toFixed(1)} Jahren` : "Keine Amortisation bei diesen Werten"}
            {v.batteryEnabled && ` · Investition inkl. Speicher: ${formatEUR(r.totalInvestment)}`}
          </div>
        </Card>

        <Card title="Durchschnittliche Tagesverläufe" padding={20}>
          <DailyOverlayChart profiles={dailyProfiles} />
          <div style={{ display: "flex", flexDirection: "column", gap: 6, marginTop: 12 }}>
            {v.subArrays.length > 1 && (
              <div style={{ display: "flex", flexWrap: "wrap", gap: 10 }}>
                {v.subArrays.map((sa, i) => (
                  <span key={i} style={{ display: "flex", alignItems: "center", gap: 5, font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color: "var(--text-secondary)" }}>
                    <span style={{ width: 8, height: 8, borderRadius: 999, background: subArrayColor(i), display: "inline-block" }}></span>
                    T{i + 1}
                  </span>
                ))}
                <span style={{ display: "flex", alignItems: "center", gap: 5, 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>
                  Gesamt
                </span>
              </div>
            )}
            <div style={{ display: "flex", gap: 16 }}>
              <span style={{ display: "flex", alignItems: "center", gap: 6, font: "var(--font-label)", letterSpacing: "var(--text-label-tracking)", color: "var(--text-secondary)" }}>
                <span style={{ width: 14, height: 2, background: "var(--text-secondary)", display: "inline-block" }}></span>Sommer
              </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: 14, height: 0, borderTop: "2px dashed var(--text-secondary)", display: "inline-block" }}></span>Winter
              </span>
            </div>
          </div>
        </Card>

        <Card title="Ertrag im Jahresverlauf" padding={20}>
          <PVChart monthly={r.monthly} selfConsumed={r.monthlySelfConsumed} months={PV_MONTHS} />
          <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>Eigenverbrauch
            </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: "rgba(31,157,85,0.28)", display: "inline-block" }}></span>Einspeisung
            </span>
            <span style={{ marginLeft: "auto", font: "var(--font-data)", fontSize: 10, color: "var(--text-tertiary)" }}>kWh pro Monat</span>
          </div>
        </Card>

        <Card padding={20}>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 18 }}>
            <Stat label="Jahresertrag" value={`${r.annualKWh.toLocaleString("de-DE")} kWh`} />
            <Stat label="Spez. Ertrag" value={`${r.specificYield.toLocaleString("de-DE")} kWh/kWp`} />
            <Stat label="CO₂ vermieden" value={formatCO2(r.co2Kg)} />
            <Stat label="Eigenverbrauch" value={`${r.selfConsumedKWh.toLocaleString("de-DE")} kWh`} />
            <Stat label="Einspeisung" value={`${r.fedInKWh.toLocaleString("de-DE")} kWh`} />
          </div>
        </Card>

        <p style={{ font: "var(--font-caption)", color: "var(--text-tertiary)", margin: 0 }}>
          Vereinfachtes Modell auf Basis von Standort, Ausrichtung, Neigung und Anlagengröße — keine zertifizierte Ertragsprognose. Der Eigenverbrauchsanteil gilt fürs gesamte Kalenderjahr; die monatliche Aufteilung folgt einem physikalischen Muster (winterlich hoher, sommerlich niedriger Anteil, in Anlehnung an die HTW-Berlin-Systematik zur Eigenverbrauchs- und Autarkieberechnung). Speicherkosten sind mit rund {BATTERY_COST_PER_KWH} €/kWh installiert angesetzt. Dieser Rechner dient ausschließlich der Information und stellt keine Kaufberatung dar.
        </p>
      </div>
    </div>
  );
}
