const { useState, useMemo, useCallback, useEffect, useRef } = React;

// Firestore helpers for forecast persistence
const forecastRef = (year) => db.collection('forecast').doc(String(year));
// weeklyRef and actualsRef removed — fiscal year saves use forecastRef directly

function debounce(fn, ms) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), ms);
  };
}

// ─── Constants ────────────────────────────────────────────────────────────────
const DEFAULT_SOURCES = [
  { id: "Website", name: "Website", hidden: false },
  { id: "Collectieven", name: "Collectieven", hidden: false },
  { id: "Nijbegun", name: "Nijbegun", hidden: false },
  { id: "Vergelijkingssites", name: "Vergelijkingssites", hidden: false },
  { id: "Overig", name: "Overig", hidden: false },
];
const MAX_SOURCES = 12;

// ISO week helpers
function isoWeeksInYear(year) {
  // A year has 53 ISO weeks if Jan 1 is Thursday, or Dec 31 is Thursday
  const jan1 = new Date(year, 0, 1);
  const dec31 = new Date(year, 11, 31);
  return (jan1.getDay() === 4 || dec31.getDay() === 4) ? 53 : 52;
}

function mondayOfISOWeek(year, week) {
  const jan4 = new Date(year, 0, 4);
  const dayOfWeek = jan4.getDay() || 7;
  const firstMonday = new Date(jan4);
  firstMonday.setDate(jan4.getDate() - dayOfWeek + 1);
  const d = new Date(firstMonday);
  d.setDate(d.getDate() + (week - 1) * 7);
  return d;
}

function currentISOWeek() {
  const d = new Date();
  d.setHours(0, 0, 0, 0);
  d.setDate(d.getDate() + 3 - (d.getDay() + 6) % 7);
  const w1 = new Date(d.getFullYear(), 0, 4);
  return 1 + Math.round(((d - w1) / 86400000 - 3 + (w1.getDay() + 6) % 7) / 7);
}

const fmtDate = d => d.toLocaleDateString("nl-NL", { day: "numeric", month: "short" });

const COLOR_PALETTE = [
  "#3B82C4", "#D4763A", "#2A8F6B", "#C44B6C", "#7C6BBD",
  "#C4943A", "#4A9B8E", "#B85C8A", "#6B8FBD", "#8B6B3A",
];
function buildSrcColors(visibleSources) {
  const colors = {};
  visibleSources.forEach((s, i) => { colors[s.id] = COLOR_PALETTE[i % COLOR_PALETTE.length]; });
  return colors;
}

const INITIAL_DEFAULTS = {
  leads:           { Website: 100, Collectieven: 55,  Nijbegun: 108, Vergelijkingssites: 144, Overig: 15   },
  leadAfspraak:    { Website: 59,  Collectieven: 63,  Nijbegun: 55,  Vergelijkingssites: 30,  Overig: 50   },
  afspraakOfferte: { Website: 89,  Collectieven: 98,  Nijbegun: 64,  Vergelijkingssites: 83,  Overig: 88   },
  offerteOrder:    { Website: 54,  Collectieven: 83,  Nijbegun: 45,  Vergelijkingssites: 40,  Overig: 50   },
  orderwaarde:     { Website: 1480,Collectieven: 1431,Nijbegun: 4250,Vergelijkingssites: 1655,Overig: 1994 },
};

// Lag distributions (weeks offset, sum should = 100)
const DEFAULT_LEAD_LAG  = [0, 10, 50, 30, 10]; // W+0..W+4 — afspraak after lead
const DEFAULT_OFFERTE_LAG = [60, 30, 10]; // W+0..W+2 — offerte after afspraak
const DEFAULT_ORDER_LAG = [0, 15, 35, 25, 15, 10]; // W+0..W+5 — order after offerte

const METRICS = [
  { key: "leads",           label: "Leads / week",         suffix: "",  step: 1  },
  { key: "leadAfspraak",    label: "Lead → Afspraak %",    suffix: "%", step: 1  },
  { key: "afspraakOfferte", label: "Afspraak → Offerte %", suffix: "%", step: 1  },
  { key: "offerteOrder",    label: "Offerte → Order %",    suffix: "%", step: 1  },
  { key: "orderwaarde",     label: "Orderwaarde €",        suffix: "€", step: 10 },
];

// Pipeline stage colors (alternating warm/cool)
const STAGE_COLORS = {
  leads:     "#3B82C4",
  afspraken: "#2A8F6B",
  offertes:  "#C44B6C",
  orders:    "#C4943A",
  omzet:     "#D4763A",
};

// Chart metric definitions (used in Prognose charts)
const CHART_METRICS = [
  { key: "omzet",     label: "Omzet",     color: "#D4763A", gradient: ["#E08A50", "#D4763A"], format: n => "€\u202f" + Math.round(n).toLocaleString("nl-NL") },
  { key: "leads",     label: "Leads",     color: "#3B82C4", gradient: ["#5094D0", "#3B82C4"], format: n => Math.round(n).toLocaleString("nl-NL") },
  { key: "afspraken", label: "Afspraken", color: "#2A8F6B", gradient: ["#3AA57E", "#2A8F6B"], format: n => Math.round(n).toLocaleString("nl-NL") },
  { key: "offertes",  label: "Offertes",  color: "#C44B6C", gradient: ["#D0607E", "#C44B6C"], format: n => Math.round(n).toLocaleString("nl-NL") },
  { key: "orders",    label: "Orders",    color: "#C4943A", gradient: ["#D0A650", "#C4943A"], format: n => Math.round(n).toLocaleString("nl-NL") },
];

// Table cell style presets
const TH = {
  padding: "10px 12px", textAlign: "center",
  fontSize: 11, fontWeight: 600, whiteSpace: "nowrap",
  letterSpacing: "0.04em",
};
const TD  = { padding: "6px 8px", textAlign: "center" };
const TD2 = { padding: "9px 14px", color: "#2A1A0A", fontSize: 13 };

// Reusable style patterns
const CARD_STYLE = {
  background: "#F3EDE1", border: "1px solid #D9CEBC", borderRadius: 10,
  boxShadow: "0 1px 4px rgba(42,26,10,0.08)",
};
const SECTION_LABEL = {
  fontSize: 11, textTransform: "uppercase", letterSpacing: "0.08em",
  color: "#8A7158", fontWeight: 600,
};

// ─── Fiscal year (W14-W13) ───────────────────────────────────────────────────
const FISCAL_START = 14; // Fiscal year starts at ISO week 14
function fiscalQuarter(isoWeek) {
  // Q1=W14-W26, Q2=W27-W39, Q3=W40-W52/53, Q4=W01-W13
  if (isoWeek >= 14 && isoWeek <= 26) return 1;
  if (isoWeek >= 27 && isoWeek <= 39) return 2;
  if (isoWeek >= 40) return 3;
  return 4; // W01-W13
}
function fiscalQuarterBoundary(isoWeek) {
  return isoWeek === 14 || isoWeek === 27 || isoWeek === 40;
}
function fiscalQuarterLabel(q, fiscalYear) {
  const weeksInStartYear = isoWeeksInYear(fiscalYear);
  const ranges = { 1: [14, 26], 2: [27, 39], 3: [40, weeksInStartYear], 4: [1, 13] };
  const [s, e] = ranges[q];
  return `Q${q} — W${String(s).padStart(2,"0")} t/m W${String(e).padStart(2,"0")}`;
}

function buildFiscalWeeks(fiscalYear) {
  const weeks = [];
  // First half: W14..W52/53 of fiscalYear
  const weeksInStartYear = isoWeeksInYear(fiscalYear);
  for (let w = FISCAL_START; w <= weeksInStartYear; w++) {
    weeks.push({ calYear: fiscalYear, isoWeek: w, fiscalIdx: weeks.length });
  }
  // Second half: W01..W13 of fiscalYear+1
  for (let w = 1; w < FISCAL_START; w++) {
    weeks.push({ calYear: fiscalYear + 1, isoWeek: w, fiscalIdx: weeks.length });
  }
  return weeks;
}

// ─── Utils ────────────────────────────────────────────────────────────────────
const fmt    = n => Math.round(n).toLocaleString("nl-NL");
const fmtEur = n => "€\u202f" + Math.round(n).toLocaleString("nl-NL");
const sumSources = (obj, srcIds) => srcIds.reduce((a, s) => a + (obj[s] || 0), 0);

function initWeekly(numWeeks, defaults) {
  const defs = defaults || INITIAL_DEFAULTS;
  return Array.from({ length: numWeeks }, () =>
    Object.fromEntries(Object.entries(defs).map(([k, v]) => [k, { ...v }]))
  );
}

function initWeeklyNull(numWeeks, sourceIds) {
  return Array.from({ length: numWeeks }, () =>
    Object.fromEntries(METRICS.map(m => [m.key, Object.fromEntries(sourceIds.map(s => [s, null]))]))
  );
}

function blendWeekly(weekly, actuals) {
  return weekly.map((wk, wi) => {
    const act = actuals[wi];
    if (!act) return wk;
    const blended = {};
    for (const metric of Object.keys(wk)) {
      blended[metric] = {};
      for (const src of Object.keys(wk[metric])) {
        const av = act[metric] && act[metric][src];
        blended[metric][src] = (av !== null && av !== undefined) ? av : wk[metric][src];
      }
    }
    return blended;
  });
}

// ─── Sub-components ───────────────────────────────────────────────────────────

function CellInput({ value, onChange, step = 1, row, col, layer = "forecast", onGridPaste }) {
  const [focused, setFocused] = useState(false);
  return (
    <div style={{ position: "relative", display: "inline-block" }}>
      <input type="number" value={value} step={step} min="0"
        data-cell-row={row} data-cell-col={col} data-cell-layer={layer}
        onChange={e => onChange(Math.max(0, Number(e.target.value)))}
        onPaste={e => {
          if (!onGridPaste) return;
          const text = e.clipboardData.getData("text");
          if (!text || (!text.includes("\t") && !text.includes("\n"))) return;
          e.preventDefault();
          const rows = text.trim().split(/\r?\n/).map(r => r.split("\t").map(c => {
            const n = Number(c.replace(/[^\d.,-]/g, "").replace(",", "."));
            return isNaN(n) ? null : n;
          }));
          onGridPaste(row, col, rows);
        }}
        onKeyDown={e => {
          let nextRow = row, nextCol = col;
          if (e.key === "ArrowDown")  { e.preventDefault(); nextRow = row + 1; }
          else if (e.key === "ArrowUp")    { e.preventDefault(); nextRow = row - 1; }
          else if (e.key === "ArrowRight") { e.preventDefault(); nextCol = col + 1; }
          else if (e.key === "ArrowLeft")  { e.preventDefault(); nextCol = col - 1; }
          else return;
          const next = document.querySelector(`input[data-cell-row="${nextRow}"][data-cell-col="${nextCol}"][data-cell-layer="${layer}"]`);
          if (next) next.focus();
        }}
        style={{
          width: 56, padding: "5px 6px",
          background: "#FAF7F2", border: "1px solid #D9CEBC",
          borderRadius: 8, color: "#2A1A0A",
          fontFamily: "'DM Sans', sans-serif", fontSize: 12, textAlign: "right",
          outline: "none", transition: "border-color 0.15s, box-shadow 0.15s",
        }}
        onFocus={e => { setFocused(true); e.target.style.borderColor = "#B8622A"; e.target.style.boxShadow = "0 0 0 3px rgba(184,98,42,0.12)"; }}
        onBlur={e => { setFocused(false); e.target.style.borderColor = "#D9CEBC"; e.target.style.boxShadow = "none"; }}
      />
      {focused && (
        <div style={{
          position: "absolute", top: -14, left: "50%", transform: "translateX(-50%)",
          fontSize: 8, fontWeight: 600, letterSpacing: "0.04em",
          color: "#B8622A", whiteSpace: "nowrap", pointerEvents: "none",
          fontFamily: "'DM Sans', sans-serif",
        }}>prognose</div>
      )}
    </div>
  );
}

function ActualCellInput({ value, hasVal, step, row, col, onChange }) {
  const [focused, setFocused] = useState(false);
  return (
    <div style={{ position: "relative", display: "inline-block" }}>
      <input
        type="number"
        value={hasVal ? value : ""}
        placeholder="—"
        step={step}
        min="0"
        data-cell-row={row}
        data-cell-col={col}
        data-cell-layer="actuals"
        onChange={e => {
          const raw = e.target.value;
          onChange(raw === "" ? null : Math.max(0, Number(raw)));
        }}
        style={{
          width: 48, padding: "3px 4px",
          background: hasVal ? "rgba(46,125,90,0.06)" : "#FAF7F2",
          border: `1px solid ${hasVal ? "rgba(46,125,90,0.3)" : "#E8E0D4"}`,
          borderRadius: 6, color: hasVal ? "#2E7D5A" : "#B5AA9A",
          fontFamily: "'DM Sans', sans-serif", fontSize: 10, textAlign: "right",
          outline: "none", transition: "border-color 0.15s, background 0.15s",
        }}
        onFocus={e => { setFocused(true); e.target.style.borderColor = "#2E7D5A"; e.target.style.boxShadow = "0 0 0 2px rgba(46,125,90,0.12)"; }}
        onBlur={e => { setFocused(false); e.target.style.borderColor = hasVal ? "rgba(46,125,90,0.3)" : "#E8E0D4"; e.target.style.boxShadow = "none"; }}
        onKeyDown={e => {
          let nextRow = row, nextCol = col;
          if (e.key === "ArrowDown")  { e.preventDefault(); nextRow = row + 1; }
          else if (e.key === "ArrowUp")    { e.preventDefault(); nextRow = row - 1; }
          else if (e.key === "ArrowRight") { e.preventDefault(); nextCol = col + 1; }
          else if (e.key === "ArrowLeft")  { e.preventDefault(); nextCol = col - 1; }
          else return;
          const next = document.querySelector(`input[data-cell-row="${nextRow}"][data-cell-col="${nextCol}"][data-cell-layer="actuals"]`);
          if (next) next.focus();
        }}
      />
      {focused && (
        <div style={{
          position: "absolute", top: -14, left: "50%", transform: "translateX(-50%)",
          fontSize: 8, fontWeight: 600, letterSpacing: "0.04em",
          color: "#2E7D5A", whiteSpace: "nowrap", pointerEvents: "none",
          fontFamily: "'DM Sans', sans-serif",
        }}>actueel</div>
      )}
    </div>
  );
}

// Visual lag distribution editor
function LagDistEditor({ label, values, onChange, color = "#B8622A" }) {
  const total = values.reduce((a, v) => a + v, 0);
  const isOk  = Math.abs(total - 100) < 1;

  const addWeek = () => onChange([...values, 0]);
  const removeWeek = () => { if (values.length > 2) onChange(values.slice(0, -1)); };

  return (
    <div style={{ marginBottom: 20 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 10 }}>
        <span style={{ fontSize: 11, fontWeight: 600, color: "#8A7158", letterSpacing: "0.04em", textTransform: "uppercase" }}>{label}</span>
        <span style={{ fontFamily: "'DM Sans', sans-serif", fontSize: 11, fontWeight: 600,
          color: isOk ? "#2E7D5A" : "#B83232",
          background: isOk ? "rgba(46,125,90,.08)" : "rgba(184,50,50,.08)",
          padding: "2px 8px", borderRadius: 10,
        }}>{total}% {isOk ? "✓" : "≠ 100"}</span>
        <span style={{ flex: 1 }} />
        <span style={{ fontSize: 10, color: "#8A7158", fontWeight: 500 }}>{values.length}w</span>
        <button onClick={removeWeek} disabled={values.length <= 2} style={{
          width: 22, height: 22, borderRadius: 6, border: "1px solid #D9CEBC",
          background: "none", color: values.length <= 2 ? "#EAE1D1" : "#8A7158",
          fontSize: 14, cursor: values.length <= 2 ? "default" : "pointer",
          display: "flex", alignItems: "center", justifyContent: "center",
          fontFamily: "'DM Sans', sans-serif", lineHeight: 1, padding: 0,
          transition: "all 0.15s",
        }}>−</button>
        <button onClick={addWeek} style={{
          width: 22, height: 22, borderRadius: 6, border: "1px solid #D9CEBC",
          background: "none", color: "#8A7158", fontSize: 14, cursor: "pointer",
          display: "flex", alignItems: "center", justifyContent: "center",
          fontFamily: "'DM Sans', sans-serif", lineHeight: 1, padding: 0,
          transition: "all 0.15s",
        }}>+</button>
      </div>

      {/* Vertical rows: W+N label | bar | input */}
      <div style={{ display: "flex", flexDirection: "column", gap: 4, maxHeight: 360, overflowY: "auto", paddingRight: 4 }}>
        {values.map((v, i) => (
          <div key={i} style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <span style={{
              width: 32, fontSize: 10, color: "#8A7158", fontWeight: 500,
              fontFamily: "'DM Sans', sans-serif", flexShrink: 0,
            }}>W+{i}</span>
            <div style={{ flex: 1, height: 14, background: "#FAF7F2", borderRadius: 4, position: "relative", overflow: "hidden" }}>
              <div style={{
                width: `${Math.min(100, v)}%`, height: "100%",
                background: color, opacity: 0.85,
                transition: "width 0.2s ease",
              }} />
            </div>
            <input type="number" value={v} min={0} max={100} step={5}
              onChange={e => {
                const next = [...values];
                next[i] = Number(e.target.value);
                onChange(next);
              }}
              style={{
                width: 56, padding: "4px 6px",
                background: "#FAF7F2", border: "1px solid #D9CEBC",
                borderRadius: 6, color: "#2A1A0A",
                fontFamily: "'DM Sans', sans-serif", fontSize: 12, textAlign: "right",
                outline: "none", transition: "border-color 0.15s", flexShrink: 0,
              }}
              onFocus={e => { e.target.style.borderColor = color; }}
              onBlur={e => { e.target.style.borderColor = "#D9CEBC"; }}
            />
            <span style={{ fontSize: 10, color: "#B5AA9A", width: 12, flexShrink: 0 }}>%</span>
          </div>
        ))}
      </div>
    </div>
  );
}

// ─── Chart component ──────────────────────────────────────────────────────────
function ForecastChart({ chartId, metric, chartMetrics, model, values, maxVal, cm, numWeeks, onChangeMetric, onRemove, year, thisYear, thisWeekNo, sourceIds, srcColors }) {
  const [hoveredBar, setHoveredBar] = useState(null);
  const [stacked, setStacked] = useState(false);
  const [chartType, setChartType] = useState("bar"); // "bar" | "line"
  const chartRef = useRef(null);

  // For stacked mode: per-source values from model.bySource
  const stackedMaxVal = useMemo(() => {
    if (!stacked) return maxVal;
    return Math.max(...model.map(r => {
      const src = r.bySource[metric];
      return src ? sourceIds.reduce((a, s) => a + (src[s] || 0), 0) : 0;
    }), 1);
  }, [stacked, model, metric, maxVal]);

  // Toggle pill style helper
  const toggleStyle = (active) => ({
    padding: "3px 10px", borderRadius: 18,
    fontSize: 11, fontWeight: 600, fontFamily: "'Albert Sans', sans-serif",
    background: active ? "#FDFCF9" : "transparent",
    color: active ? "#1C1916" : "#8A857B",
    boxShadow: active ? "0 1px 3px rgba(28,25,22,0.08)" : "none",
    transition: "all 0.15s",
  });

  const toggleWrap = {
    display: "flex", alignItems: "center", gap: 0,
    background: "#EDEAE3", borderRadius: 20, padding: 2,
    cursor: "pointer", userSelect: "none",
    border: "1px solid #DBD8D0",
  };

  // Compute tooltip position for line chart
  const getTooltipPos = (index) => {
    if (!chartRef.current) return { x: 0, y: 0 };
    const rect = chartRef.current.getBoundingClientRect();
    const chartW = rect.width;
    const chartH = 110;
    const effMax = stacked ? stackedMaxVal : maxVal;
    const step = chartW / Math.max(model.length - 1, 1);
    const val = stacked
      ? sourceIds.reduce((a, s) => a + ((model[index]?.bySource[metric]?.[s]) || 0), 0)
      : values[index];
    const x = index * step;
    const y = chartH - (val / effMax) * 90 - 20;
    return { x, y };
  };

  // Render line chart
  const renderLineChart = () => {
    const chartH = 110;
    const effMax = stacked ? stackedMaxVal : maxVal;

    if (stacked) {
      // Build cumulative stacked areas (bottom to top)
      const sourceTotals = model.map((row) => {
        const src = row.bySource[metric] || {};
        return sourceIds.map(s => src[s] || 0);
      });
      // Cumulative per point
      const cumulative = sourceTotals.map(vals => {
        const cum = [];
        vals.reduce((acc, v, i) => { cum[i] = acc + v; return cum[i]; }, 0);
        return cum;
      });

      return (
        <div ref={chartRef} style={{ position: "relative", height: chartH, marginBottom: 18 }}
             onMouseLeave={() => setHoveredBar(null)}>
          <svg width="100%" height={chartH} style={{ display: "block" }} preserveAspectRatio="none" viewBox={`0 0 ${model.length - 1} ${chartH}`}>
            {/* Render areas from top source to bottom so bottom layers appear on top visually */}
            {[...sourceIds].reverse().map((s, si) => {
              const srcIdx = sourceIds.length - 1 - si;
              const points = model.map((_, i) => {
                const x = i;
                const yTop = chartH - (cumulative[i][srcIdx] / effMax) * 90;
                const yBot = srcIdx > 0 ? chartH - (cumulative[i][srcIdx - 1] / effMax) * 90 : chartH;
                return { x, yTop, yBot };
              });
              const pathUp = points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.yTop}`).join(" ");
              const pathDown = [...points].reverse().map((p, i) => `${i === 0 ? "L" : "L"}${p.x},${p.yBot}`).join(" ");
              return (
                <path key={s} d={`${pathUp} ${pathDown} Z`} fill={srcColors[s]} opacity={0.55} />
              );
            })}
            {/* Lines on top */}
            {sourceIds.map((s, srcIdx) => {
              const d = model.map((_, i) => {
                const x = i;
                const y = chartH - (cumulative[i][srcIdx] / effMax) * 90;
                return `${i === 0 ? "M" : "L"}${x},${y}`;
              }).join(" ");
              return <path key={s} d={d} fill="none" stroke={srcColors[s]} strokeWidth={0.12} vectorEffect="non-scaling-stroke" style={{ strokeWidth: 1.5 }} />;
            })}
          </svg>
          {/* Hover zones */}
          <div style={{ position: "absolute", inset: 0, display: "flex" }}>
            {model.map((row, i) => {
              const srcData = row.bySource[metric] || {};
              const total = sourceIds.reduce((a, s) => a + (srcData[s] || 0), 0);
              const isHovered = hoveredBar === i;
              return (
                <div key={i} style={{ flex: 1, position: "relative" }}
                     onMouseEnter={() => setHoveredBar(i)}
                >
                  {isHovered && total > 0 && (
                    <div style={{
                      position: "absolute", bottom: "50%", left: "50%", transform: "translateX(-50%)",
                      background: "#1C1916", color: "#FDFCF9",
                      padding: "6px 10px", borderRadius: 6, fontSize: 11, fontWeight: 600,
                      whiteSpace: "nowrap", pointerEvents: "none", zIndex: 10,
                      boxShadow: "0 2px 8px rgba(28,25,22,0.25)",
                    }}>
                      <div style={{ fontSize: 9, color: "#DBD8D0", marginBottom: 3 }}>W{String(row.week).padStart(2,"0")}</div>
                      {sourceIds.map(s => (
                        <div key={s} style={{ display: "flex", justifyContent: "space-between", gap: 14, fontSize: 10 }}>
                          <span style={{ color: srcColors[s] }}>{s}</span>
                          <span>{cm.format(srcData[s] || 0)}</span>
                        </div>
                      ))}
                      <div style={{ borderTop: "1px solid #8A857B", marginTop: 3, paddingTop: 3, fontSize: 10, textAlign: "right" }}>
                        {cm.format(total)}
                      </div>
                    </div>
                  )}
                </div>
              );
            })}
          </div>
        </div>
      );
    }

    // Non-stacked line
    const points = model.map((row, i) => {
      const x = i;
      const y = chartH - (values[i] / effMax) * 90;
      return { x, y, val: values[i], week: row.week };
    });
    const linePath = points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x},${p.y}`).join(" ");
    const areaPath = `${linePath} L${points.length - 1},${chartH} L0,${chartH} Z`;

    return (
      <div ref={chartRef} style={{ position: "relative", height: chartH, marginBottom: 18 }}
           onMouseLeave={() => setHoveredBar(null)}>
        <svg width="100%" height={chartH} style={{ display: "block" }} preserveAspectRatio="none" viewBox={`0 0 ${model.length - 1} ${chartH}`}>
          <defs>
            <linearGradient id={`lg-${chartId}`} x1="0" y1="0" x2="0" y2="1">
              <stop offset="0%" stopColor={cm.color} stopOpacity="0.25" />
              <stop offset="100%" stopColor={cm.color} stopOpacity="0.02" />
            </linearGradient>
          </defs>
          <path d={areaPath} fill={`url(#lg-${chartId})`} />
          <path d={linePath} fill="none" stroke={cm.color} strokeWidth={0.12} vectorEffect="non-scaling-stroke" style={{ strokeWidth: 2 }} />
        </svg>
        {/* Hover zones + dots */}
        <div style={{ position: "absolute", inset: 0, display: "flex" }}>
          {model.map((row, i) => {
            const isHovered = hoveredBar === i;
            const val = values[i];
            return (
              <div key={i} style={{ flex: 1, position: "relative" }}
                   onMouseEnter={() => setHoveredBar(i)}
              >
                {isHovered && val > 0 && (
                  <div style={{
                    position: "absolute", bottom: "50%", left: "50%", transform: "translateX(-50%)",
                    background: "#1C1916", color: "#FDFCF9",
                    padding: "4px 8px", borderRadius: 6, fontSize: 11, fontWeight: 600,
                    whiteSpace: "nowrap", pointerEvents: "none", zIndex: 10,
                    boxShadow: "0 2px 8px rgba(28,25,22,0.25)",
                  }}>
                    <div style={{ fontSize: 9, color: "#DBD8D0", marginBottom: 1 }}>W{String(row.week).padStart(2,"0")}</div>
                    {cm.format(val)}
                  </div>
                )}
              </div>
            );
          })}
        </div>
      </div>
    );
  };

  return (
    <div style={{ marginBottom: 20, ...CARD_STYLE, padding: "20px 22px" }}>
      {/* Header row: pills left, quarter summary center, toggles + remove right */}
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 14 }}>
        <div style={{ display: "flex", gap: 4, flexWrap: "wrap", alignItems: "center" }}>
          {chartMetrics.map(m => (
            <button
              key={m.key}
              onClick={() => onChangeMetric(chartId, m.key)}
              style={{
                padding: "4px 12px", borderRadius: 20, border: "1px solid",
                borderColor: metric === m.key ? m.color : "#DBD8D0",
                background: metric === m.key ? m.color : "transparent",
                color: metric === m.key ? "#fff" : "#8A857B",
                fontSize: 11, fontWeight: 600, cursor: "pointer",
                fontFamily: "'Albert Sans', sans-serif",
                transition: "all 0.15s",
                letterSpacing: "0.02em",
              }}
            >{m.label}</button>
          ))}
        </div>
        {/* Quarter summary */}
        {(() => {
          const qTotals = [1,2,3,4].map(q => model.filter(r => r.quarter === q).reduce((a, r) => a + r[metric], 0));
          return (
            <div style={{ display: "flex", gap: 12, alignItems: "baseline", fontSize: 11, color: "#B5AA9A" }}>
              {qTotals.map((t, i) => (
                <span key={i}>Q{i + 1} {cm.format(t)}</span>
              ))}
              <span style={{ color: "#8A7158" }}>
                Jaar {cm.format(qTotals.reduce((a, v) => a + v, 0))}
              </span>
            </div>
          );
        })()}
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          {/* Stacked toggle */}
          <div onClick={() => setStacked(s => !s)} style={toggleWrap}>
            <span style={toggleStyle(!stacked)}>Totaal</span>
            <span style={toggleStyle(stacked)}>Gestapeld</span>
          </div>
          {/* Bar / Line toggle */}
          <div onClick={() => setChartType(t => t === "bar" ? "line" : "bar")} style={toggleWrap}>
            <span style={toggleStyle(chartType === "bar")}>Staaf</span>
            <span style={toggleStyle(chartType === "line")}>Lijn</span>
          </div>
          {onRemove && (
            <button
              onClick={() => onRemove(chartId)}
              style={{
                background: "none", border: "none", color: "#DBD8D0", cursor: "pointer",
                fontSize: 16, padding: "2px 6px", borderRadius: 6, lineHeight: 1,
                transition: "color 0.15s, background 0.15s",
              }}
              onMouseEnter={e => { e.target.style.color = "#C43434"; e.target.style.background = "rgba(196,52,52,0.08)"; }}
              onMouseLeave={e => { e.target.style.color = "#DBD8D0"; e.target.style.background = "none"; }}
            >✕</button>
          )}
        </div>
      </div>

      {/* Chart title + legend when stacked */}
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 12 }}>
        <div style={{ fontSize: 11, color: "#8A857B", letterSpacing: "0.08em", textTransform: "uppercase", fontWeight: 600 }}>
          {cm.label} per week — {numWeeks} weken
        </div>
        {stacked && (
          <div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
            {sourceIds.map(s => (
              <div key={s} style={{ display: "flex", alignItems: "center", gap: 4, fontSize: 10, color: "#8A857B" }}>
                <div style={{ width: 7, height: 7, borderRadius: "50%", background: srcColors[s] }} />
                {s}
              </div>
            ))}
          </div>
        )}
      </div>

      {/* Chart: bar or line */}
      {chartType === "line" ? renderLineChart() : (
      <div style={{ display: "flex", alignItems: "flex-end", gap: 2, height: 110, position: "relative" }}>
        {model.map((row, i) => {
          const val = values[i];
          const isHovered = hoveredBar === i;
          const srcData = row.bySource[metric];
          const isQuarterBoundary = fiscalQuarterBoundary(row.week);

          const bar = (() => {
          if (stacked && srcData) {
            // Stacked mode
            const total = sourceIds.reduce((a, s) => a + (srcData[s] || 0), 0);
            const totalH = (total / stackedMaxVal) * 90;
            return (
              <div
                style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 4, cursor: "default", position: "relative" }}
                onMouseEnter={() => setHoveredBar(i)}
                onMouseLeave={() => setHoveredBar(null)}
              >
                {/* Tooltip */}
                {isHovered && total > 0 && (
                  <div style={{
                    position: "absolute", bottom: "100%", marginBottom: 4,
                    background: "#1C1916", color: "#FDFCF9",
                    padding: "6px 10px", borderRadius: 6, fontSize: 11, fontWeight: 600,
                    whiteSpace: "nowrap", pointerEvents: "none", zIndex: 10,
                    boxShadow: "0 2px 8px rgba(28,25,22,0.25)",
                    left: "50%", transform: "translateX(-50%)",
                  }}>
                    <div style={{ fontSize: 9, color: "#DBD8D0", marginBottom: 3 }}>W{String(row.week).padStart(2,"0")}</div>
                    {sourceIds.map(s => (
                      <div key={s} style={{ display: "flex", justifyContent: "space-between", gap: 14, fontSize: 10 }}>
                        <span style={{ color: srcColors[s] }}>{s}</span>
                        <span>{cm.format(srcData[s] || 0)}</span>
                      </div>
                    ))}
                    <div style={{ borderTop: "1px solid #8A857B", marginTop: 3, paddingTop: 3, fontSize: 10, textAlign: "right" }}>
                      {cm.format(total)}
                    </div>
                  </div>
                )}
                <div style={{
                  width: "100%", height: Math.max(totalH, total > 0 ? 2 : 0),
                  display: "flex", flexDirection: "column-reverse",
                  borderRadius: "3px 3px 0 0", overflow: "hidden",
                  transition: "height 0.2s ease",
                  transform: isHovered ? "scaleX(1.15)" : "scaleX(1)",
                }}>
                  {sourceIds.map(s => {
                    const segPct = total > 0 ? ((srcData[s] || 0) / total) * 100 : 0;
                    return (
                      <div key={s} style={{
                        width: "100%", height: segPct + "%",
                        background: srcColors[s],
                        minHeight: segPct > 0 ? 1 : 0,
                      }} />
                    );
                  })}
                </div>
                <div style={{ fontSize: 9, color: isHovered ? "#1C1916" : "#8A857B", fontWeight: isHovered ? 600 : 500, transition: "color 0.1s" }}>
                  {row.week}
                </div>
              </div>
            );
          }

          // Total mode (default)
          const h = (val / maxVal) * 90;
          return (
            <div
              style={{ flex: 1, display: "flex", flexDirection: "column", alignItems: "center", gap: 4, cursor: "default", position: "relative" }}
              onMouseEnter={() => setHoveredBar(i)}
              onMouseLeave={() => setHoveredBar(null)}
            >
              {/* Tooltip */}
              {isHovered && val > 0 && (
                <div style={{
                  position: "absolute", bottom: "100%", marginBottom: 4,
                  background: "#1C1916", color: "#FDFCF9",
                  padding: "4px 8px", borderRadius: 6, fontSize: 11, fontWeight: 600,
                  whiteSpace: "nowrap", pointerEvents: "none",
                  fontFamily: "'Albert Sans', sans-serif",
                  boxShadow: "0 2px 8px rgba(28,25,22,0.25)",
                  zIndex: 10,
                }}>
                  <div style={{ fontSize: 9, color: "#DBD8D0", marginBottom: 1 }}>W{String(row.week).padStart(2,"0")}</div>
                  {cm.format(val)}
                </div>
              )}
              <div style={{
                width: "100%", height: Math.max(h, val > 0 ? 2 : 0),
                background: isHovered
                    ? cm.gradient[0]
                    : `linear-gradient(180deg, ${cm.gradient[0]} 0%, ${cm.gradient[1]} 100%)`,
                borderRadius: "3px 3px 0 0",
                transition: "height 0.2s ease, background 0.1s",
                boxShadow: isHovered ? `0 2px 8px ${cm.color}40` : `0 1px 3px ${cm.color}30`,
                transform: isHovered ? "scaleX(1.15)" : "scaleX(1)",
              }} />
              <div style={{ fontSize: 9, color: isHovered ? "#1C1916" : "#8A857B", fontWeight: isHovered ? 600 : 500, transition: "color 0.1s" }}>
                {row.week}
              </div>
            </div>
          );
          })();

          return (
            <React.Fragment key={i}>
              {isQuarterBoundary && <div style={{ width: 8, flexShrink: 0 }} />}
              {bar}
            </React.Fragment>
          );
        })}
      </div>
      )}
    </div>
  );
}

// ─── Pipeline timeline visualization ──────────────────────────────────────────
function PipelineTimeline({ leadLag, offerteLag, orderLag }) {
  const llFrac = leadLag.map(v => v / (leadLag.reduce((a,x)=>a+x,0)||100));
  const flFrac = offerteLag.map(v => v / (offerteLag.reduce((a,x)=>a+x,0)||100));
  const olFrac = orderLag.map(v => v / (orderLag.reduce((a,x)=>a+x,0)||100));
  const timelineLen = 1 + leadLag.length + offerteLag.length + orderLag.length + 2;

  const afspraken = Array(timelineLen).fill(0);
  llFrac.forEach((frac, d) => { if (d < timelineLen) afspraken[d] = frac * 100; });

  const offertes = Array(timelineLen).fill(0);
  afspraken.forEach((aspVal, aspWeek) => {
    if (aspVal <= 0) return;
    flFrac.forEach((frac, d) => {
      const targetWeek = aspWeek + d;
      if (targetWeek < timelineLen) offertes[targetWeek] += aspVal * frac;
    });
  });

  const orders = Array(timelineLen).fill(0);
  offertes.forEach((offVal, offWeek) => {
    if (offVal <= 0) return;
    olFrac.forEach((frac, d) => {
      const targetWeek = offWeek + d;
      if (targetWeek < timelineLen) orders[targetWeek] += offVal * frac;
    });
  });

  const stages = [
    { key: "leads",     label: "Leads",     color: STAGE_COLORS.leads,     data: Array.from({ length: timelineLen }, (_, w) => w === 0 ? 100 : 0) },
    { key: "afspraken", label: "Afspraken",  color: STAGE_COLORS.afspraken, data: afspraken },
    { key: "offertes",  label: "Offertes",   color: STAGE_COLORS.offertes,  data: offertes },
    { key: "orders",    label: "Orders",     color: STAGE_COLORS.orders,    data: orders },
  ];

  // Trim to the last week where any stage still has a meaningful value
  let lastWeek = 0;
  for (let w = timelineLen - 1; w >= 0; w--) {
    if (stages.some(s => s.data[w] > 1)) { lastWeek = w; break; }
  }
  const visibleLen = lastWeek + 1;

  return (
    <div style={{ marginTop: 28, ...CARD_STYLE, padding: "20px 22px" }}>
      <div style={{ ...SECTION_LABEL, marginBottom: 16 }}>Pipeline tijdlijn (cohort W1)</div>
      <div style={{ display: "grid", gridTemplateColumns: `68px repeat(${visibleLen}, minmax(0, 1fr))`, gap: 0 }}>
        <div />
        {Array.from({ length: visibleLen }, (_, w) => (
          <div key={w} style={{ textAlign: "center", fontSize: 10, color: "#8A7158", fontWeight: 600, paddingBottom: 8, borderBottom: "1px solid #EAE1D1" }}>
            W+{w}
          </div>
        ))}
        {stages.map((stage, si) => (
          <React.Fragment key={stage.key}>
            <div style={{
              display: "flex", alignItems: "center", gap: 6,
              fontSize: 11, fontWeight: 600, color: stage.color,
              paddingRight: 10, height: 32,
              borderBottom: si < stages.length - 1 ? "1px solid #EAE1D1" : "none",
            }}>
              <div style={{ width: 6, height: 6, borderRadius: "50%", background: stage.color, flexShrink: 0 }} />
              {stage.label}
            </div>
            {Array.from({ length: visibleLen }, (_, w) => {
              const val = stage.data[w];
              return (
                <div key={w} style={{
                  display: "flex", alignItems: "center", justifyContent: "center",
                  height: 32, minWidth: 0,
                  borderBottom: si < stages.length - 1 ? "1px solid #EAE1D1" : "none",
                }}>
                  {val > 1 && (
                    <div style={{
                      padding: "3px 7px", borderRadius: 6, fontSize: 10,
                      fontWeight: 600, fontFamily: "'DM Sans', sans-serif",
                      background: stage.color, color: "#fff",
                      opacity: Math.max(0.35, val / 100),
                      whiteSpace: "nowrap",
                      boxShadow: "0 1px 3px rgba(42,26,10,0.12)",
                    }}>
                      {Math.round(val)}%
                    </div>
                  )}
                </div>
              );
            })}
          </React.Fragment>
        ))}
      </div>
    </div>
  );
}

// ─── Drag handle with hover tooltip ───────────────────────────────────────────
function DragHandle({ onMouseDown, onMouseEnter, onMouseUp, isDragging }) {
  const [hovered, setHovered] = useState(false);
  return (
    <div
      onMouseDown={onMouseDown}
      onMouseEnter={e => { setHovered(true); onMouseEnter(e); }}
      onMouseLeave={() => setHovered(false)}
      onMouseUp={onMouseUp}
      style={{
        width: 16, height: 16, borderRadius: 3,
        border: "1px solid " + (hovered && !isDragging ? "#B8622A" : "#D9CEBC"),
        background: hovered && !isDragging ? "rgba(184,98,42,0.06)" : "#FAF7F2",
        cursor: "s-resize", flexShrink: 0,
        display: "flex", alignItems: "center", justifyContent: "center",
        fontSize: 10, color: hovered && !isDragging ? "#B8622A" : "#D9CEBC", lineHeight: 1,
        transition: "border-color 0.15s, color 0.15s, background 0.15s",
        position: "relative",
      }}
    >
      ⠿
      {hovered && !isDragging && (
        <div style={{
          position: "absolute", top: "100%", left: "50%", transform: "translateX(-50%)",
          marginTop: 4, padding: "3px 8px", borderRadius: 5,
          background: "#2A1A0A", color: "#FAF7F2",
          fontSize: 10, fontWeight: 500, whiteSpace: "nowrap",
          pointerEvents: "none", zIndex: 20,
          boxShadow: "0 2px 6px rgba(42,26,10,0.2)",
        }}>Sleep omlaag om te kopiëren</div>
      )}
    </div>
  );
}

// ─── Settings Modal ──────────────────────────────────────────────────────────
function SourceSettingsModal({ sources, onUpdate, onClose }) {
  const [showHidden, setShowHidden] = useState(false);
  const visible = sources.filter(s => !s.hidden);
  const hidden = sources.filter(s => s.hidden);
  const colors = buildSrcColors(visible);

  const rename = (id, name) => {
    onUpdate(sources.map(s => s.id === id ? { ...s, name } : s));
  };
  const remove = (id) => {
    onUpdate(sources.map(s => s.id === id ? { ...s, hidden: true } : s));
  };
  const restore = (id) => {
    onUpdate(sources.map(s => s.id === id ? { ...s, hidden: false } : s));
  };
  const addSource = () => {
    if (sources.filter(s => !s.hidden).length >= MAX_SOURCES) return;
    const id = "src_" + Date.now();
    onUpdate([...sources, { id, name: "Nieuw kanaal", hidden: false }]);
  };

  return (
    <div style={{
      position: "fixed", inset: 0,
      background: "rgba(42,26,10,0.3)", backdropFilter: "blur(3px)",
      zIndex: 200, display: "flex", alignItems: "center", justifyContent: "center",
    }} onClick={onClose}>
      <div onClick={e => e.stopPropagation()} style={{
        background: "#F3EDE1", border: "1px solid #D9CEBC", borderRadius: 14,
        padding: 28, minWidth: 380, maxWidth: 480,
        boxShadow: "0 4px 16px rgba(42,26,10,0.14)",
        maxHeight: "80vh", overflowY: "auto",
      }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 20 }}>
          <div style={{ fontFamily: "'Fraunces', Georgia, serif", fontSize: 18, fontWeight: 600, color: "#2A1A0A" }}>
            Bronnen beheren
          </div>
          <button onClick={onClose} style={{
            background: "none", border: "none", color: "#8A7158", cursor: "pointer",
            fontSize: 18, padding: "2px 6px", borderRadius: 6, lineHeight: 1,
          }}>✕</button>
        </div>

        <div style={{ fontSize: 12, color: "#8A7158", marginBottom: 16 }}>
          Voeg bronnen toe, hernoem of verwijder ze. Verwijderde bronnen worden verborgen maar data blijft bewaard.
        </div>

        {/* Active sources */}
        <div style={{ display: "flex", flexDirection: "column", gap: 8, marginBottom: 16 }}>
          {visible.map((s) => (
            <div key={s.id} style={{
              display: "flex", alignItems: "center", gap: 10,
              padding: "8px 12px", background: "#FAF7F2",
              border: "1px solid #E8E0D4", borderRadius: 8,
            }}>
              <div style={{
                width: 10, height: 10, borderRadius: "50%",
                background: colors[s.id], flexShrink: 0,
              }} />
              <input
                type="text" value={s.name}
                onChange={e => rename(s.id, e.target.value)}
                style={{
                  flex: 1, padding: "4px 8px", border: "1px solid transparent",
                  borderRadius: 6, background: "transparent",
                  fontFamily: "'DM Sans', sans-serif", fontSize: 13, color: "#2A1A0A",
                  outline: "none", transition: "border-color 0.15s",
                }}
                onFocus={e => { e.target.style.borderColor = "#B8622A"; e.target.style.background = "#FFF8F0"; }}
                onBlur={e => { e.target.style.borderColor = "transparent"; e.target.style.background = "transparent"; }}
              />
              {visible.length > 1 && (
                <button onClick={() => remove(s.id)} style={{
                  background: "none", border: "none", color: "#D9CEBC", cursor: "pointer",
                  fontSize: 14, padding: "2px 6px", borderRadius: 6, lineHeight: 1,
                  transition: "color 0.15s",
                }}
                onMouseEnter={e => { e.target.style.color = "#C43434"; }}
                onMouseLeave={e => { e.target.style.color = "#D9CEBC"; }}
                >✕</button>
              )}
            </div>
          ))}
        </div>

        {/* Add button */}
        <button onClick={addSource}
          disabled={visible.length >= MAX_SOURCES}
          style={{
            width: "100%", padding: "10px 16px", borderRadius: 8,
            border: "1.5px dashed #D9CEBC", background: "transparent",
            color: visible.length >= MAX_SOURCES ? "#D9CEBC" : "#8A7158",
            fontSize: 13, fontWeight: 500, cursor: visible.length >= MAX_SOURCES ? "default" : "pointer",
            fontFamily: "'DM Sans', sans-serif",
            display: "flex", alignItems: "center", justifyContent: "center", gap: 6,
            transition: "border-color 0.2s, color 0.2s",
            marginBottom: 16,
          }}
          onMouseEnter={e => { if (visible.length < MAX_SOURCES) { e.currentTarget.style.borderColor = "#B8622A"; e.currentTarget.style.color = "#B8622A"; } }}
          onMouseLeave={e => { e.currentTarget.style.borderColor = "#D9CEBC"; e.currentTarget.style.color = visible.length >= MAX_SOURCES ? "#D9CEBC" : "#8A7158"; }}
        >
          <span style={{ fontSize: 16, lineHeight: 1 }}>+</span> Bron toevoegen
          {visible.length >= MAX_SOURCES && <span style={{ fontSize: 11 }}>(max {MAX_SOURCES})</span>}
        </button>

        {/* Hidden sources */}
        {hidden.length > 0 && (
          <>
            <button onClick={() => setShowHidden(v => !v)} style={{
              background: "none", border: "none", color: "#8A7158", cursor: "pointer",
              fontSize: 12, fontWeight: 500, padding: 0, marginBottom: 8,
              fontFamily: "'DM Sans', sans-serif",
              display: "flex", alignItems: "center", gap: 4,
            }}>
              <span style={{ fontSize: 10, transform: showHidden ? "rotate(90deg)" : "rotate(0deg)", transition: "transform 0.15s", display: "inline-block" }}>▶</span>
              Verborgen bronnen ({hidden.length})
            </button>
            {showHidden && (
              <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
                {hidden.map(s => (
                  <div key={s.id} style={{
                    display: "flex", alignItems: "center", gap: 10,
                    padding: "6px 12px", background: "#EAE1D1",
                    border: "1px solid #E8E0D4", borderRadius: 8,
                    opacity: 0.7,
                  }}>
                    <span style={{ flex: 1, fontSize: 13, color: "#8A7158" }}>{s.name}</span>
                    <button onClick={() => restore(s.id)} style={{
                      padding: "4px 10px", borderRadius: 6, border: "1px solid #D9CEBC",
                      background: "#FAF7F2", color: "#8A7158", fontSize: 11,
                      cursor: "pointer", fontFamily: "'DM Sans', sans-serif",
                      transition: "all 0.15s",
                    }}
                    onMouseEnter={e => { e.target.style.borderColor = "#2A8F6B"; e.target.style.color = "#2A8F6B"; }}
                    onMouseLeave={e => { e.target.style.borderColor = "#D9CEBC"; e.target.style.color = "#8A7158"; }}
                    >Herstellen</button>
                  </div>
                ))}
              </div>
            )}
          </>
        )}
      </div>
    </div>
  );
}

// ─── Main App ─────────────────────────────────────────────────────────────────
function ForecastApp() {
  // Determine current fiscal year: if current week >= 14, FY = this calendar year; else FY = last year
  const [year, setYear] = useState(() => {
    const now = new Date();
    const cy = now.getFullYear();
    return currentISOWeek() >= FISCAL_START ? cy : cy - 1;
  });
  const fiscalWeeks = useMemo(() => buildFiscalWeeks(year), [year]);
  const numWeeks = fiscalWeeks.length;
  const thisWeekNo = currentISOWeek();
  const thisYear = new Date().getFullYear();

  const [sources,   setSources]   = useState(() => JSON.parse(JSON.stringify(DEFAULT_SOURCES)));
  const [defaults,  setDefaults]  = useState(() => JSON.parse(JSON.stringify(INITIAL_DEFAULTS)));
  const [weekly,    setWeekly]    = useState(() => initWeekly(numWeeks, defaults));
  const [leadLag,    setLeadLag]    = useState([...DEFAULT_LEAD_LAG]);
  const [offerteLag, setOfferteLag] = useState([...DEFAULT_OFFERTE_LAG]);
  const [orderLag,   setOrderLag]   = useState([...DEFAULT_ORDER_LAG]);
  const [mainTab,   setMainTab]   = useState("timing");
  const [metricKey, setMetricKey] = useState("leads");
  const [charts,    setCharts]    = useState([{ id: 1, metric: "omzet" }]);
  const [nextChartId, setNextChartId] = useState(2);
  const [showSettings, setShowSettings] = useState(false);
  const [showActuals, setShowActuals] = useState(true);
  const [resetConfirm, setResetConfirm] = useState(false);

  // When sources change, ensure defaults exist for any new source
  const handleSourcesUpdate = useCallback((newSources) => {
    setSources(newSources);
    setDefaults(prev => {
      const updated = { ...prev };
      for (const s of newSources) {
        for (const mk of Object.keys(INITIAL_DEFAULTS)) {
          if (!updated[mk]) updated[mk] = {};
          if (updated[mk][s.id] === undefined) {
            updated[mk] = { ...updated[mk], [s.id]: mk === "orderwaarde" ? 1500 : 50 };
          }
        }
      }
      return updated;
    });
  }, []);

  // Derived from sources
  const visibleSources = useMemo(() => sources.filter(s => !s.hidden), [sources]);
  const visibleSourceIds = useMemo(() => visibleSources.map(s => s.id), [visibleSources]);
  const srcColors = useMemo(() => buildSrcColors(visibleSources), [visibleSources]);
  const sum = useCallback((obj) => sumSources(obj, visibleSourceIds), [visibleSourceIds]);

  const [actuals, setActuals] = useState(() => initWeeklyNull(numWeeks, visibleSourceIds));
  const blended = useMemo(() => blendWeekly(weekly, actuals), [weekly, actuals]);

  const weekHasActuals = useCallback((weekIdx) => {
    const act = actuals[weekIdx];
    if (!act) return false;
    return METRICS.some(m => visibleSourceIds.some(s => act[m.key] && act[m.key][s] !== null && act[m.key][s] !== undefined));
  }, [actuals, visibleSourceIds]);

  const lastActualsWeek = useMemo(() => {
    for (let i = actuals.length - 1; i >= 0; i--) {
      if (weekHasActuals(i)) return i;
    }
    return -1;
  }, [actuals, weekHasActuals]);

  // ── Firestore persistence ──
  const [loaded, setLoaded] = useState(false);
  const [dirty, setDirty] = useState(false);
  const [saveStatus, setSaveStatus] = useState("idle"); // idle | saving | saved
  const skipNextDirty = useRef(false);

  // Load global settings (lag distributions + defaults + sources) once on mount
  useEffect(() => {
    const loadGlobal = async () => {
      const doc = await db.collection('forecast').doc('_settings').get();
      if (doc.exists) {
        const data = doc.data();
        if (data.leadLag) setLeadLag(data.leadLag);
        if (data.offerteLag) setOfferteLag(data.offerteLag);
        if (data.orderLag) setOrderLag(data.orderLag);
        if (data.defaults) setDefaults(data.defaults);
        if (data.sources) {
          setSources(data.sources);
        } else {
          // Migration: populate sources from hardcoded defaults
          const migrated = JSON.parse(JSON.stringify(DEFAULT_SOURCES));
          setSources(migrated);
          db.collection('forecast').doc('_settings').set({ sources: migrated }, { merge: true });
        }
      }
    };
    loadGlobal();
  }, []);

  // Load fiscal year data from Firestore (spans two calendar years)
  useEffect(() => {
    setLoaded(false);
    const loadFiscalYear = async () => {
      const startYear = year;
      const endYear = year + 1;

      // Load chart config from start year
      const yearDoc = await forecastRef(startYear).get();
      if (yearDoc.exists) {
        const data = yearDoc.data();
        if (data.charts) {
          setCharts(data.charts);
          setNextChartId(Math.max(...data.charts.map(c => c.id)) + 1);
        }
      }

      // Load weekly data from both calendar years
      const [weeklySnapStart, weeklySnapEnd] = await Promise.all([
        forecastRef(startYear).collection('weekly').get(),
        forecastRef(endYear).collection('weekly').get(),
      ]);

      // Build lookup: calYear -> isoWeek -> data
      const weeklyLookup = {};
      const processSnap = (snap, calYear) => {
        if (!weeklyLookup[calYear]) weeklyLookup[calYear] = {};
        snap.docs.forEach(doc => {
          weeklyLookup[calYear][parseInt(doc.id)] = doc.data();
        });
      };
      processSnap(weeklySnapStart, startYear);
      processSnap(weeklySnapEnd, endYear);

      // Build fiscal-ordered weekly array
      const fw = fiscalWeeks;
      const freshWeekly = initWeekly(fw.length, defaults);
      fw.forEach((fw_entry, fi) => {
        const saved = weeklyLookup[fw_entry.calYear]?.[fw_entry.isoWeek];
        if (saved) {
          const merged = { ...freshWeekly[fi] };
          for (const metric of Object.keys(merged)) {
            if (saved[metric] && typeof saved[metric] === 'object') {
              merged[metric] = { ...merged[metric], ...saved[metric] };
            }
          }
          freshWeekly[fi] = merged;
        }
      });
      setWeekly(freshWeekly);

      // Load actuals from both calendar years
      const [actualsSnapStart, actualsSnapEnd] = await Promise.all([
        forecastRef(startYear).collection('actuals').get(),
        forecastRef(endYear).collection('actuals').get(),
      ]);

      const actualsLookup = {};
      const processActualsSnap = (snap, calYear) => {
        if (!actualsLookup[calYear]) actualsLookup[calYear] = {};
        snap.docs.forEach(doc => {
          actualsLookup[calYear][parseInt(doc.id)] = doc.data();
        });
      };
      processActualsSnap(actualsSnapStart, startYear);
      processActualsSnap(actualsSnapEnd, endYear);

      const freshActuals = initWeeklyNull(fw.length, visibleSourceIds);
      fw.forEach((fw_entry, fi) => {
        const saved = actualsLookup[fw_entry.calYear]?.[fw_entry.isoWeek];
        if (saved) {
          const merged = { ...freshActuals[fi] };
          for (const metric of Object.keys(merged)) {
            if (saved[metric] && typeof saved[metric] === 'object') {
              for (const src of Object.keys(merged[metric])) {
                if (saved[metric][src] !== undefined) {
                  merged[metric][src] = saved[metric][src];
                }
              }
            }
          }
          freshActuals[fi] = merged;
        }
      });
      skipNextDirty.current = true;
      setActuals(freshActuals);
      setDirty(false);
      setLoaded(true);
    };
    loadFiscalYear();
  }, [year]);

  // Save global settings (lag distributions + defaults + sources)
  useEffect(() => {
    if (!loaded) return;
    db.collection('forecast').doc('_settings').set({ leadLag, offerteLag, orderLag, defaults, sources }, { merge: true });
  }, [leadLag, offerteLag, orderLag, defaults, sources, loaded]);

  // Save chart config per year
  useEffect(() => {
    if (!loaded) return;
    forecastRef(year).set({ charts }, { merge: true });
  }, [charts, loaded]);

  // Mark dirty when weekly/actuals change after load
  useEffect(() => {
    if (!loaded) return;
    if (skipNextDirty.current) { skipNextDirty.current = false; return; }
    setDirty(true);
  }, [weekly, actuals]);

  // Manual save — writes all weeks to their correct calendar year
  const saveAll = useCallback(async () => {
    if (!loaded || saveStatus === "saving") return;
    setSaveStatus("saving");
    try {
      const writes = [];
      weekly.forEach((weekData, idx) => {
        const fw = fiscalWeeks[idx];
        if (fw) writes.push(forecastRef(fw.calYear).collection('weekly').doc(String(fw.isoWeek)).set(weekData));
      });
      actuals.forEach((weekData, idx) => {
        const fw = fiscalWeeks[idx];
        if (fw) writes.push(forecastRef(fw.calYear).collection('actuals').doc(String(fw.isoWeek)).set(weekData));
      });
      await Promise.all(writes);
      setDirty(false);
      setSaveStatus("saved");
      setTimeout(() => setSaveStatus("idle"), 1500);
    } catch (err) {
      console.error("Save failed:", err);
      setSaveStatus("idle");
      alert("Opslaan mislukt: " + err.message);
    }
  }, [loaded, saveStatus, weekly, actuals, fiscalWeeks]);

  // Warn before browser navigation if dirty
  useEffect(() => {
    if (!dirty) return;
    const handler = (e) => { e.preventDefault(); e.returnValue = ""; };
    window.addEventListener("beforeunload", handler);
    return () => window.removeEventListener("beforeunload", handler);
  }, [dirty]);

  // Cmd/Ctrl+S keyboard shortcut
  useEffect(() => {
    const handler = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key === "s") {
        e.preventDefault();
        if (dirty) saveAll();
      }
    };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [dirty, saveAll]);

  // Year change handler
  const changeYear = useCallback((newYear) => {
    if (dirty && !window.confirm("Niet-opgeslagen wijzigingen gaan verloren. Doorgaan?")) return;
    setYear(newYear);
    const fw = buildFiscalWeeks(newYear);
    skipNextDirty.current = true;
    setWeekly(initWeekly(fw.length, defaults));
    setActuals(initWeeklyNull(fw.length, visibleSourceIds));
    setDirty(false);
    setLoaded(false);
  }, [defaults, visibleSourceIds, dirty]);

  const updateCell = useCallback((week, metric, source, value) => {
    setWeekly(prev =>
      prev.map((w, i) => i === week ? { ...w, [metric]: { ...w[metric], [source]: value } } : w)
    );
  }, []);

  const updateActualCell = useCallback((week, metric, source, value) => {
    setActuals(prev =>
      prev.map((w, i) => i === week ? { ...w, [metric]: { ...w[metric], [source]: value } } : w)
    );
  }, []);

  // Paste a grid of values starting at (startRow, startCol) for the current metric
  const handleGridPaste = useCallback((startRow, startCol, grid) => {
    setWeekly(prev => {
      const next = prev.map((w, wi) => {
        const gridRow = wi - startRow;
        if (gridRow < 0 || gridRow >= grid.length) return w;
        const row = grid[gridRow];
        const updated = { ...w[metricKey] };
        row.forEach((val, ci) => {
          const colIdx = startCol + ci;
          if (colIdx < visibleSourceIds.length && val !== null) {
            updated[visibleSourceIds[colIdx]] = val;
          }
        });
        return { ...w, [metricKey]: updated };
      });
      return next;
    });
  }, [metricKey]);

  const resetMetric = useCallback((metric) => {
    setWeekly(prev => prev.map(w => ({ ...w, [metric]: { ...defaults[metric] } })));
  }, []);

  // Drag-to-copy state: { source, fromWeek, value, toWeek }
  const [dragCopy, setDragCopy] = useState(null);
  const [copyConfirm, setCopyConfirm] = useState(null); // { source, value, fromWeek, toWeek, metric }

  const startDragCopy = useCallback((weekIdx, source, value) => {
    setDragCopy({ source, fromWeek: weekIdx, value, toWeek: weekIdx });
  }, []);

  const extendDragCopy = useCallback((weekIdx) => {
    setDragCopy(prev => prev && weekIdx > prev.fromWeek ? { ...prev, toWeek: weekIdx } : prev);
  }, []);

  const endDragCopy = useCallback(() => {
    if (dragCopy && dragCopy.toWeek > dragCopy.fromWeek) {
      setCopyConfirm({ ...dragCopy, metric: metricKey });
    }
    setDragCopy(null);
  }, [dragCopy, metricKey]);

  // Auto-scroll page while dragging near viewport edges
  const scrollRafRef = useRef(null);
  const mouseYRef = useRef(0);

  useEffect(() => {
    if (!dragCopy) {
      if (scrollRafRef.current) { cancelAnimationFrame(scrollRafRef.current); scrollRafRef.current = null; }
      return;
    }
    const onMouseMove = (e) => { mouseYRef.current = e.clientY; };
    const onMouseUp = () => { endDragCopy(); };
    const scrollLoop = () => {
      const y = mouseYRef.current;
      const edge = 60;
      const vh = window.innerHeight;
      if (y > vh - edge) {
        window.scrollBy(0, Math.min(12, (y - (vh - edge)) / 3));
      } else if (y < edge) {
        window.scrollBy(0, -Math.min(12, (edge - y) / 3));
      }
      scrollRafRef.current = requestAnimationFrame(scrollLoop);
    };
    window.addEventListener("mousemove", onMouseMove);
    window.addEventListener("mouseup", onMouseUp);
    scrollRafRef.current = requestAnimationFrame(scrollLoop);
    return () => {
      window.removeEventListener("mousemove", onMouseMove);
      window.removeEventListener("mouseup", onMouseUp);
      if (scrollRafRef.current) cancelAnimationFrame(scrollRafRef.current);
    };
  // Coerce to boolean so the effect only fires on drag start/stop, not every toWeek update
  }, [!!dragCopy, endDragCopy]);

  const confirmCopy = useCallback(() => {
    if (!copyConfirm) return;
    const { source, value, fromWeek, toWeek, metric } = copyConfirm;
    setWeekly(prev => prev.map((w, i) =>
      i > fromWeek && i <= toWeek
        ? { ...w, [metric]: { ...w[metric], [source]: value } }
        : w
    ));
    setCopyConfirm(null);
  }, [copyConfirm]);

  // ── Previous fiscal year tail (for cross-year lag spillover) ──
  const [prevYearTail, setPrevYearTail] = useState(null);

  useEffect(() => {
    const maxLag = Math.max(leadLag.length, offerteLag.length, orderLag.length);
    const prevFiscalWeeks = buildFiscalWeeks(year - 1);
    const tailStart = Math.max(0, prevFiscalWeeks.length - maxLag);

    const loadTail = async () => {
      // Determine which calendar years the previous FY spans
      const prevStartYear = year - 1;
      const prevEndYear = year;

      // Load weekly data from both calendar years of previous FY
      const [weeklySnapStart, weeklySnapEnd] = await Promise.all([
        forecastRef(prevStartYear).collection('weekly').get(),
        forecastRef(prevEndYear).collection('weekly').get(),
      ]);

      const weeklyLookup = {};
      const processSnap = (snap, calYear) => {
        if (!weeklyLookup[calYear]) weeklyLookup[calYear] = {};
        snap.docs.forEach(doc => {
          weeklyLookup[calYear][parseInt(doc.id)] = doc.data();
        });
      };
      processSnap(weeklySnapStart, prevStartYear);
      processSnap(weeklySnapEnd, prevEndYear);

      const forecastTail = initWeekly(prevFiscalWeeks.length, defaults);
      prevFiscalWeeks.forEach((fw_entry, fi) => {
        if (fi < tailStart) return;
        const saved = weeklyLookup[fw_entry.calYear]?.[fw_entry.isoWeek];
        if (saved) {
          const merged = { ...forecastTail[fi] };
          for (const metric of Object.keys(merged)) {
            if (saved[metric] && typeof saved[metric] === 'object') {
              merged[metric] = { ...merged[metric], ...saved[metric] };
            }
          }
          forecastTail[fi] = merged;
        }
      });

      // Load actuals from both calendar years of previous FY
      const [actualsSnapStart, actualsSnapEnd] = await Promise.all([
        forecastRef(prevStartYear).collection('actuals').get(),
        forecastRef(prevEndYear).collection('actuals').get(),
      ]);

      const actualsLookup = {};
      const processActualsSnap = (snap, calYear) => {
        if (!actualsLookup[calYear]) actualsLookup[calYear] = {};
        snap.docs.forEach(doc => {
          actualsLookup[calYear][parseInt(doc.id)] = doc.data();
        });
      };
      processActualsSnap(actualsSnapStart, prevStartYear);
      processActualsSnap(actualsSnapEnd, prevEndYear);

      const actualsTail = initWeeklyNull(prevFiscalWeeks.length, visibleSourceIds);
      prevFiscalWeeks.forEach((fw_entry, fi) => {
        if (fi < tailStart) return;
        const saved = actualsLookup[fw_entry.calYear]?.[fw_entry.isoWeek];
        if (saved) {
          const merged = { ...actualsTail[fi] };
          for (const metric of Object.keys(merged)) {
            if (saved[metric] && typeof saved[metric] === 'object') {
              for (const src of Object.keys(merged[metric])) {
                if (saved[metric][src] !== undefined) merged[metric][src] = saved[metric][src];
              }
            }
          }
          actualsTail[fi] = merged;
        }
      });

      // Blend and slice
      const blendedTail = blendWeekly(forecastTail, actualsTail);
      setPrevYearTail(blendedTail.slice(tailStart));
    };
    loadTail();
  }, [year, leadLag.length, offerteLag.length, orderLag.length, visibleSourceIds]);

  // ── Model ──────────────────────────────────────────────────────────────────
  const model = useMemo(() => {
    const safeLen = Math.min(numWeeks, blended.length);
    const W = Array.from({ length: safeLen }, (_, i) => i);
    const maxLag = Math.max(leadLag.length, offerteLag.length, orderLag.length);

    const llTotal = leadLag.reduce((a, v) => a + v, 0) || 100;
    const flTotal = offerteLag.reduce((a, v) => a + v, 0) || 100;
    const olTotal = orderLag.reduce((a, v) => a + v, 0) || 100;
    const ll = leadLag.map(v => v / llTotal);
    const fl = offerteLag.map(v => v / flTotal);
    const ol = orderLag.map(v => v / olTotal);

    // Build extended leads array: [prevTail..., thisYear...]
    // Index 0 = first prev tail week, index maxLag = W1 of this year
    const offset = prevYearTail ? prevYearTail.length : maxLag;
    const extLen = offset + safeLen;

    // Zeroed week for warmup slots when no prev-year data exists.
    // (Avoids phantom lead history from default values.)
    const zeroWeek = Object.fromEntries(
      METRICS.map(m => [m.key, Object.fromEntries(visibleSourceIds.map(s => [s, 0]))])
    );

    const extWeekly = [];
    for (let i = 0; i < offset; i++) {
      extWeekly.push(prevYearTail ? prevYearTail[i] : zeroWeek);
    }
    for (let i = 0; i < safeLen; i++) {
      extWeekly.push(blended[i]);
    }

    // Safe accessor: return 0 for undefined/NaN/negative
    const safe = v => (v > 0 ? v : 0);

    const extLeads = extWeekly.map(w => w.leads);

    const extAfspraken = extWeekly.map((w, wi) =>
      Object.fromEntries(visibleSourceIds.map(s => {
        let val = 0;
        ll.forEach((frac, d) => {
          const src = wi - d;
          if (src >= 0) val += safe(extLeads[src][s]) * frac * (safe(extWeekly[src].leadAfspraak[s]) / 100);
        });
        return [s, val];
      }))
    );

    const extOffertes = extWeekly.map((w, wi) =>
      Object.fromEntries(visibleSourceIds.map(s => {
        let val = 0;
        fl.forEach((frac, d) => {
          const src = wi - d;
          if (src >= 0) val += safe(extAfspraken[src][s]) * frac * (safe(extWeekly[src].afspraakOfferte[s]) / 100);
        });
        return [s, val];
      }))
    );

    const extOrders = extWeekly.map((w, wi) =>
      Object.fromEntries(visibleSourceIds.map(s => {
        let val = 0;
        ol.forEach((frac, d) => {
          const src = wi - d;
          if (src >= 0) val += safe(extOffertes[src][s]) * frac * (safe(extWeekly[src].offerteOrder[s]) / 100);
        });
        return [s, val];
      }))
    );

    const extOmzet = extWeekly.map((w, wi) =>
      Object.fromEntries(visibleSourceIds.map(s => [s, safe(extOrders[wi][s]) * safe(w.orderwaarde[s])]))
    );

    // Slice back to just this year's weeks
    return W.map(w => {
      const ei = w + offset;
      const fw = fiscalWeeks[w];
      return {
        week: fw ? fw.isoWeek : w + 1,
        calYear: fw ? fw.calYear : year,
        index: w,
        quarter: fiscalQuarter(fw ? fw.isoWeek : w + 1),
        leads:     sum(extLeads[ei]),
        afspraken: sum(extAfspraken[ei]),
        offertes:  sum(extOffertes[ei]),
        orders:    sum(extOrders[ei]),
        omzet:     sum(extOmzet[ei]),
        bySource: {
          leads:     extLeads[ei],
          afspraken: extAfspraken[ei],
          offertes:  extOffertes[ei],
          orders:    extOrders[ei],
          omzet:     extOmzet[ei],
        },
      };
    });
  }, [blended, leadLag, offerteLag, orderLag, numWeeks, fiscalWeeks, prevYearTail, visibleSourceIds, sum]);

  const metricConfig = METRICS.find(m => m.key === metricKey);

  const addChart = useCallback(() => {
    // pick the first metric not already shown
    const usedMetrics = new Set(charts.map(c => c.metric));
    const available = CHART_METRICS.find(m => !usedMetrics.has(m.key));
    const metricToAdd = available ? available.key : "omzet";
    setCharts(prev => [...prev, { id: nextChartId, metric: metricToAdd }]);
    setNextChartId(prev => prev + 1);
  }, [charts, nextChartId]);

  const removeChart = useCallback((id) => {
    setCharts(prev => prev.filter(c => c.id !== id));
  }, []);

  const setChartMetric = useCallback((id, newMetric) => {
    setCharts(prev => prev.map(c => c.id === id ? { ...c, metric: newMetric } : c));
  }, []);

  const tabs = [
    { key: "timing", label: "Timing & Spreiding" },
    { key: "invoer", label: "Invoer" },
    { key: "prognose", label: "Prognose" },
  ];

  return (
    <>
      <style>{`
        #forecast-app input[type=number] { -moz-appearance: textfield; }
        #forecast-app input[type=number]::-webkit-inner-spin-button,
        #forecast-app input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; }

        #forecast-app .fc-tab {
          padding: 7px 16px;
          border: none;
          border-radius: 7px;
          background: transparent;
          color: #8A7158;
          cursor: pointer;
          font-size: 13px;
          font-weight: 500;
          font-family: 'DM Sans', sans-serif;
          transition: background 0.15s, color 0.15s;
        }
        #forecast-app .fc-tab:hover { color: #2A1A0A; }
        #forecast-app .fc-tab.active {
          background: #FAF7F2;
          color: #2A1A0A;
          box-shadow: 0 1px 4px rgba(42, 26, 10, 0.08);
        }

        #forecast-app .fc-metric-btn {
          padding: 6px 14px; border-radius: 8px; border: 1px solid #D9CEBC;
          background: none; color: #8A7158;
          font-family: 'DM Sans', sans-serif; font-size: 12px; font-weight: 500;
          cursor: pointer; transition: all .15s;
        }
        #forecast-app .fc-metric-btn:hover { border-color: #B8622A; color: #2A1A0A; }
        #forecast-app .fc-metric-btn.active {
          border-color: #B8622A; background: rgba(184,98,42,0.08); color: #B8622A; font-weight: 600;
        }

        #forecast-app tr.fc-data-row:hover td { background: rgba(184,98,42,0.04) !important; }

      `}</style>

      <div style={{ background: "#FAF7F2", minHeight: "100vh", fontFamily: "'DM Sans', -apple-system, sans-serif", color: "#2A1A0A" }}>

        {/* ── Header ── */}
        <div style={{
          padding: "16px 28px",
          display: "flex", alignItems: "center", justifyContent: "space-between",
        }}>
          {/* Tabs (left) */}
          <div style={{ display: "flex", gap: 2, background: "#EAE1D1", borderRadius: 9, padding: 3 }}>
            {tabs.map(t => (
              <button key={t.key} className={`fc-tab${mainTab === t.key ? " active" : ""}`}
                onClick={() => setMainTab(t.key)}>{t.label}</button>
            ))}
          </div>

          {/* Controls (right) */}
          <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
            {/* Save button */}
            <button
              onClick={saveAll}
              disabled={!dirty || saveStatus === "saving"}
              style={{
                padding: "7px 16px", borderRadius: 8,
                border: "1px solid",
                borderColor: saveStatus === "saved" ? "#2E7D5A" : dirty ? "#B8622A" : "#D9CEBC",
                background: saveStatus === "saved" ? "rgba(46,125,90,0.08)" : dirty ? "#B8622A" : "transparent",
                color: saveStatus === "saved" ? "#2E7D5A" : dirty ? "#FFF8F0" : "#B5AA9A",
                fontSize: 12, fontWeight: 600,
                cursor: dirty && saveStatus !== "saving" ? "pointer" : "default",
                fontFamily: "'DM Sans', sans-serif",
                transition: "all 0.15s",
                boxShadow: dirty && saveStatus !== "saved" ? "0 2px 6px rgba(184,98,42,0.25)" : "none",
              }}
              title={dirty ? "Wijzigingen opslaan (Cmd+S)" : "Geen wijzigingen"}
            >
              {saveStatus === "saving" ? "Bezig..." : saveStatus === "saved" ? "✓ Opgeslagen" : dirty ? "● Opslaan" : "Opgeslagen"}
            </button>

            <button onClick={() => setShowSettings(true)} style={{
              background: "none", border: "1px solid #D9CEBC", color: "#8A7158", cursor: "pointer",
              padding: "7px 9px", fontSize: 15, lineHeight: 1, borderRadius: 8,
              transition: "color 0.15s, border-color 0.15s, background 0.15s",
            }}
            onMouseEnter={e => { e.target.style.color = "#B8622A"; e.target.style.borderColor = "#B8622A"; e.target.style.background = "rgba(184,98,42,0.06)"; }}
            onMouseLeave={e => { e.target.style.color = "#8A7158"; e.target.style.borderColor = "#D9CEBC"; e.target.style.background = "none"; }}
            title="Bronnen beheren"
            >⚙</button>

            <div style={{
              display: "flex", alignItems: "center", gap: 0,
              background: "#EAE1D1", borderRadius: 10, border: "1px solid #D9CEBC",
              padding: 2,
            }}>
              <button onClick={() => changeYear(year - 1)} style={{
                background: "none", border: "none", color: "#8A7158", cursor: "pointer",
                padding: "6px 10px", fontSize: 14, lineHeight: 1, borderRadius: 8,
                transition: "color 0.15s, background 0.15s",
              }}
              onMouseEnter={e => { e.target.style.color = "#B8622A"; e.target.style.background = "rgba(184,98,42,0.06)"; }}
              onMouseLeave={e => { e.target.style.color = "#8A7158"; e.target.style.background = "none"; }}
              >◀</button>
              <span style={{
                fontFamily: "'Fraunces', Georgia, serif", fontSize: 14, fontWeight: 600,
                color: "#2A1A0A", padding: "0 8px", minWidth: 64, textAlign: "center",
              }}>{year}–{String(year + 1).slice(2)}</span>
              <button onClick={() => changeYear(year + 1)} style={{
                background: "none", border: "none", color: "#8A7158", cursor: "pointer",
                padding: "6px 10px", fontSize: 14, lineHeight: 1, borderRadius: 8,
                transition: "color 0.15s, background 0.15s",
              }}
              onMouseEnter={e => { e.target.style.color = "#B8622A"; e.target.style.background = "rgba(184,98,42,0.06)"; }}
              onMouseLeave={e => { e.target.style.color = "#8A7158"; e.target.style.background = "none"; }}
              >▶</button>
            </div>
          </div>
        </div>

        {/* ── TIMING TAB ── */}
        {mainTab === "timing" && (
          <div style={{ padding: "28px 28px", maxWidth: 1280, margin: "0 auto" }}>
            <p style={{ color: "#8A7158", fontSize: 13, marginBottom: 28, lineHeight: 1.7 }}>
              Definieer hoe leads en offertes uitsmeren over de tijd. De percentages geven aan welk aandeel van conversies in welke week terechtkomt na het triggerpunt.
            </p>

            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 24 }}>

              {/* Lead → Afspraak lag */}
              <div style={{ ...CARD_STYLE, padding: "20px 20px 16px" }}>
                <div style={{ fontSize: 13, fontWeight: 600, color: "#2A1A0A", marginBottom: 4, fontFamily: "'Fraunces', Georgia, serif" }}>Lead → Afspraak spreiding</div>
                <div style={{ fontSize: 12, color: "#8A7158", marginBottom: 18 }}>
                  Na binnenkomen lead: hoeveel % afspraken valt in week +0 t/m +{leadLag.length - 1}?
                </div>
                <LagDistEditor label="" values={leadLag} onChange={setLeadLag} color="#2563EB" />
              </div>

              {/* Afspraak → Offerte lag */}
              <div style={{ ...CARD_STYLE, padding: "20px 20px 16px" }}>
                <div style={{ fontSize: 13, fontWeight: 600, color: "#2A1A0A", marginBottom: 4, fontFamily: "'Fraunces', Georgia, serif" }}>Afspraak → Offerte spreiding</div>
                <div style={{ fontSize: 12, color: "#8A7158", marginBottom: 18 }}>
                  Na afspraak: hoeveel % offertes valt in week +0 t/m +{offerteLag.length - 1}?
                </div>
                <LagDistEditor label="" values={offerteLag} onChange={setOfferteLag} color="#2A8F6B" />
              </div>

              {/* Offerte → Order lag */}
              <div style={{ ...CARD_STYLE, padding: "20px 20px 16px" }}>
                <div style={{ fontSize: 13, fontWeight: 600, color: "#2A1A0A", marginBottom: 4, fontFamily: "'Fraunces', Georgia, serif" }}>Offerte → Order spreiding</div>
                <div style={{ fontSize: 12, color: "#8A7158", marginBottom: 18 }}>
                  Na versturen offerte: hoeveel % orders valt in week +0 t/m +{orderLag.length - 1}?
                </div>
                <LagDistEditor label="" values={orderLag} onChange={setOrderLag} color="#D97706" />
              </div>
            </div>

            <PipelineTimeline leadLag={leadLag} offerteLag={offerteLag} orderLag={orderLag} />

            {/* Defaults editor */}
            <div style={{ marginTop: 28, ...CARD_STYLE, padding: "20px 22px" }}>
              <div style={{ fontSize: 13, fontWeight: 600, color: "#2A1A0A", marginBottom: 4, fontFamily: "'Fraunces', Georgia, serif" }}>Standaardwaarden</div>
              <div style={{ fontSize: 12, color: "#8A7158", marginBottom: 18 }}>
                Beginwaarden voor nieuwe weken. Wijzigingen gelden alleen voor nieuw aangemaakte jaren.
              </div>
              <div style={{ overflowX: "auto" }}>
                <table style={{ borderCollapse: "collapse", width: "100%", fontSize: 12 }}>
                  <thead>
                    <tr>
                      <th style={{ textAlign: "left", padding: "6px 8px", color: "#8A7158", fontWeight: 500, borderBottom: "1px solid #E8E0D4" }}></th>
                      {visibleSources.map(s => (
                        <th key={s.id} style={{ textAlign: "right", padding: "6px 8px", color: "#8A7158", fontWeight: 500, borderBottom: "1px solid #E8E0D4", whiteSpace: "nowrap" }}>{s.name}</th>
                      ))}
                    </tr>
                  </thead>
                  <tbody>
                    {METRICS.map(m => (
                      <tr key={m.key}>
                        <td style={{ padding: "5px 8px", fontWeight: 500, color: "#2A1A0A", borderBottom: "1px solid #F0EBE3", whiteSpace: "nowrap" }}>{m.label}</td>
                        {visibleSources.map(s => (
                          <td key={s.id} style={{ padding: "3px 4px", borderBottom: "1px solid #F0EBE3", textAlign: "right" }}>
                            <input type="number" value={defaults[m.key][s.id] || 0} step={m.step}
                              onChange={e => {
                                const val = Number(e.target.value);
                                setDefaults(prev => ({
                                  ...prev,
                                  [m.key]: { ...prev[m.key], [s.id]: val }
                                }));
                              }}
                              style={{
                                width: 70, textAlign: "right", padding: "4px 6px",
                                border: "1px solid #E8E0D4", borderRadius: 5,
                                fontSize: 12, fontFamily: "'DM Sans', sans-serif",
                                background: "#FAF7F2", color: "#2A1A0A",
                                outline: "none",
                              }}
                            />
                          </td>
                        ))}
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            </div>
          </div>
        )}

        {/* ── INVOER TAB ── */}
        {mainTab === "invoer" && (
          <div style={{ padding: "20px 24px" }}>
            {/* Metric selector */}
            <div style={{ display: "flex", gap: 6, marginBottom: 18, flexWrap: "wrap", alignItems: "center" }}>
              {METRICS.map(m => (
                <button key={m.key} className={`fc-metric-btn${metricKey === m.key ? " active" : ""}`}
                  onClick={() => setMetricKey(m.key)}>{m.label}</button>
              ))}
              <button onClick={() => setResetConfirm(true)} style={{
                marginLeft: "auto", padding: "6px 14px", borderRadius: 8,
                border: "1px solid #D9CEBC", background: "none",
                color: "#8A7158", fontSize: 12, cursor: "pointer",
                fontFamily: "'DM Sans', sans-serif", fontWeight: 500,
                transition: "all 0.15s",
              }}
              onMouseEnter={e => { e.target.style.borderColor = "#B8622A"; e.target.style.color = "#B8622A"; }}
              onMouseLeave={e => { e.target.style.borderColor = "#D9CEBC"; e.target.style.color = "#8A7158"; }}
              >↺ Herstel standaard</button>
              <div onClick={() => setShowActuals(v => !v)} style={{
                display: "flex", alignItems: "center", gap: 0,
                background: "#EDEAE3", borderRadius: 20, padding: 2,
                cursor: "pointer", userSelect: "none",
                border: "1px solid #DBD8D0",
              }}>
                <span style={{
                  padding: "3px 10px", borderRadius: 18,
                  fontSize: 11, fontWeight: 600, fontFamily: "'DM Sans', sans-serif",
                  background: !showActuals ? "#FDFCF9" : "transparent",
                  color: !showActuals ? "#1C1916" : "#8A857B",
                  boxShadow: !showActuals ? "0 1px 3px rgba(28,25,22,0.08)" : "none",
                  transition: "all 0.15s",
                }}>Prognose</span>
                <span style={{
                  padding: "3px 10px", borderRadius: 18,
                  fontSize: 11, fontWeight: 600, fontFamily: "'DM Sans', sans-serif",
                  background: showActuals ? "#FDFCF9" : "transparent",
                  color: showActuals ? "#2E7D5A" : "#8A857B",
                  boxShadow: showActuals ? "0 1px 3px rgba(46,125,90,0.12)" : "none",
                  transition: "all 0.15s",
                }}>+ Actuals</span>
              </div>
            </div>

            {/* Quarter tables */}
            {[1,2,3,4].map(q => {
              const qWeeks = fiscalWeeks.map((fw, idx) => ({ ...fw, idx })).filter(fw => fiscalQuarter(fw.isoWeek) === q);
              if (qWeeks.length === 0) return null;
              return (
                <div key={q} style={{ marginBottom: 20 }}>
                  <div style={{ fontSize: 10, fontWeight: 600, color: "#B5AA9A", textTransform: "uppercase", letterSpacing: "0.1em", marginBottom: 6, paddingLeft: 2 }}>
                    {fiscalQuarterLabel(q, year)}
                  </div>
                  <div
                    onMouseUp={() => { if (dragCopy) endDragCopy(); }}
                    style={{ overflowX: "auto", borderRadius: 10, border: "1px solid #D9CEBC", overflow: "hidden", boxShadow: "0 1px 4px rgba(42,26,10,0.08)", userSelect: dragCopy ? "none" : "auto" }}>
                    <table style={{ borderCollapse: "collapse", fontSize: 13, width: "100%" }}>
                      <thead>
                        <tr style={{ background: "#F3EDE1", borderBottom: "1px solid #D9CEBC" }}>
                          <th style={{ ...TH, width: 60, color: "#8A7158" }}>Week</th>
                          {visibleSources.map(s => (
                            <th key={s.id} style={{ ...TH, color: srcColors[s.id] }}>
                              <div style={{ display: "flex", alignItems: "center", gap: 6, justifyContent: "center" }}>
                                <div style={{ width: 8, height: 8, borderRadius: "50%", background: srcColors[s.id], flexShrink: 0 }} />
                                {s.name}
                              </div>
                            </th>
                          ))}
                          <th style={{ ...TH, color: "#8A7158", textAlign: "right", width: 70 }}>Totaal</th>
                        </tr>
                      </thead>
                      <tbody>
                        {qWeeks.map((fw, ri) => {
                          const w = fw.idx;
                          const isoWk = fw.isoWeek;
                          const isCurrentWeek = fw.calYear === thisYear && isoWk === thisWeekNo;
                          const mon = mondayOfISOWeek(fw.calYear, isoWk);
                          const vals = weekly[w][metricKey];
                          const total = visibleSourceIds.reduce((a, s) => a + (vals[s] || 0), 0);
                          return (
                            <tr key={w} className="fc-data-row" style={{
                              background: isCurrentWeek ? "rgba(184,98,42,0.06)" : ri % 2 === 0 ? "#FAF7F2" : "#FFFFFF",
                              borderBottom: "1px solid #EAE1D1",
                            }}>
                              <td style={{ ...TD, textAlign: "center", minWidth: 80 }}>
                                <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 1 }}>
                                  <span style={{ color: isCurrentWeek ? "#B8622A" : "#8A7158", fontSize: 12, fontWeight: isCurrentWeek ? 700 : 500 }}>
                                    W{String(isoWk).padStart(2, "0")}
                                  </span>
                                  <span style={{ fontSize: 9, color: isCurrentWeek ? "#B8622A" : "#D9CEBC", fontWeight: 500 }}>
                                    {fmtDate(mon)}
                                  </span>
                                </div>
                              </td>
                              {visibleSources.map((src, si) => {
                                const s = src.id;
                                const isDragSource = dragCopy && dragCopy.source === s && dragCopy.fromWeek === w;
                                const isInDragRange = dragCopy && dragCopy.source === s && w > dragCopy.fromWeek && w <= dragCopy.toWeek;
                                return (
                                  <td key={s}
                                    onMouseEnter={() => { if (dragCopy && dragCopy.source === s) extendDragCopy(w); }}
                                    style={{
                                    ...TD, padding: "5px 8px", position: "relative",
                                    background: isDragSource ? "rgba(184,98,42,0.12)" : isInDragRange ? "rgba(184,98,42,0.06)" : undefined,
                                    outline: isDragSource ? "2px solid #B8622A" : isInDragRange ? "1px dashed #B8622A" : "none",
                                    outlineOffset: -1,
                                  }}>
                                    <div style={{ display: "flex", alignItems: "center", gap: 4, justifyContent: "center" }}>
                                      <CellInput
                                        value={vals[s] || 0}
                                        onChange={v => updateCell(w, metricKey, s, v)}
                                        step={metricConfig.step}
                                        row={w}
                                        col={si}
                                        layer="forecast"
                                        onGridPaste={handleGridPaste}
                                      />
                                      {showActuals && (fw.calYear < thisYear || (fw.calYear === thisYear && isoWk <= thisWeekNo)) && (() => {
                                        const actVal = actuals[w] && actuals[w][metricKey] && actuals[w][metricKey][s];
                                        const hasVal = actVal !== null && actVal !== undefined;
                                        return (
                                          <ActualCellInput
                                            value={actVal}
                                            hasVal={hasVal}
                                            step={metricConfig.step}
                                            row={w}
                                            col={si}
                                            onChange={val => updateActualCell(w, metricKey, s, val)}
                                          />
                                        );
                                      })()}
                                      <DragHandle
                                        onMouseDown={e => { e.preventDefault(); startDragCopy(w, s, vals[s] || 0); }}
                                        onMouseEnter={() => { if (dragCopy && dragCopy.source === s) extendDragCopy(w); }}
                                        onMouseUp={() => { if (dragCopy) endDragCopy(); }}
                                        isDragging={!!dragCopy}
                                      />
                                    </div>
                                  </td>
                                );
                              })}
                              <td style={{ ...TD, color: "#8A7158", textAlign: "right", paddingRight: 16, fontSize: 12, fontWeight: 600 }}>
                                {["orderwaarde","leadAfspraak","afspraakOfferte","offerteOrder"].includes(metricKey) ? "—" : fmt(total)}
                              </td>
                            </tr>
                          );
                        })}
                      </tbody>
                    </table>
                  </div>
                </div>
              );
            })}

            {/* Reset confirm modal */}
            {resetConfirm && (
              <div style={{
                position: "fixed", inset: 0,
                background: "rgba(42,26,10,0.3)", backdropFilter: "blur(3px)",
                zIndex: 200, display: "flex", alignItems: "center", justifyContent: "center",
              }} onClick={() => setResetConfirm(false)}>
                <div onClick={e => e.stopPropagation()} style={{
                  background: "#F3EDE1", border: "1px solid #D9CEBC", borderRadius: 14,
                  padding: 24, minWidth: 320, maxWidth: 400,
                  boxShadow: "0 4px 16px rgba(42,26,10,0.14)",
                }}>
                  <div style={{ fontFamily: "'Fraunces', Georgia, serif", fontSize: 18, fontWeight: 600, marginBottom: 16 }}>
                    Standaardwaarden herstellen
                  </div>
                  <div style={{ fontSize: 13, color: "#2A1A0A", lineHeight: 1.6, marginBottom: 20 }}>
                    Alle <strong>{METRICS.find(m => m.key === metricKey)?.label}</strong> waarden worden teruggezet naar de standaardwaarden. Dit overschrijft alle handmatige invoer voor deze metric.
                  </div>
                  <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
                    <button onClick={() => setResetConfirm(false)} style={{
                      padding: "8px 16px", borderRadius: 8, border: "1px solid #D9CEBC",
                      background: "#EAE1D1", color: "#2A1A0A", fontSize: 13, fontWeight: 500,
                      cursor: "pointer", fontFamily: "'DM Sans', sans-serif",
                    }}>Annuleren</button>
                    <button onClick={() => { resetMetric(metricKey); setResetConfirm(false); }} style={{
                      padding: "8px 16px", borderRadius: 8, border: "none",
                      background: "#B8622A", color: "#FFF8F0", fontSize: 13, fontWeight: 500,
                      cursor: "pointer", fontFamily: "'DM Sans', sans-serif",
                      boxShadow: "0 2px 8px rgba(184,98,42,0.3)",
                    }}>Herstellen</button>
                  </div>
                </div>
              </div>
            )}

            {/* Copy confirm modal */}
            {copyConfirm && (
              <div style={{
                position: "fixed", inset: 0,
                background: "rgba(42,26,10,0.3)", backdropFilter: "blur(3px)",
                zIndex: 200, display: "flex", alignItems: "center", justifyContent: "center",
              }} onClick={() => setCopyConfirm(null)}>
                <div onClick={e => e.stopPropagation()} style={{
                  background: "#F3EDE1", border: "1px solid #D9CEBC", borderRadius: 14,
                  padding: 24, minWidth: 320, maxWidth: 400,
                  boxShadow: "0 4px 16px rgba(42,26,10,0.14)",
                }}>
                  <div style={{ fontFamily: "'Fraunces', Georgia, serif", fontSize: 18, fontWeight: 600, marginBottom: 16 }}>
                    Waarde omlaag kopiëren
                  </div>
                  <div style={{ fontSize: 13, color: "#2A1A0A", lineHeight: 1.6, marginBottom: 20 }}>
                    Kopieer <strong style={{ color: srcColors[copyConfirm.source] }}>{copyConfirm.source}</strong> waarde{" "}
                    <strong>{copyConfirm.value}</strong> van{" "}
                    <strong>W{String(fiscalWeeks[copyConfirm.fromWeek]?.isoWeek || copyConfirm.fromWeek + 1).padStart(2,"0")}</strong> naar{" "}
                    <strong>W{String(fiscalWeeks[copyConfirm.toWeek]?.isoWeek || copyConfirm.toWeek + 1).padStart(2,"0")}</strong>?
                    <div style={{ fontSize: 11, color: "#8A7158", marginTop: 4 }}>
                      {copyConfirm.toWeek - copyConfirm.fromWeek} {copyConfirm.toWeek - copyConfirm.fromWeek > 1 ? "weken" : "week"} worden bijgewerkt
                    </div>
                  </div>
                  <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
                    <button onClick={() => setCopyConfirm(null)} style={{
                      padding: "8px 16px", borderRadius: 8, border: "1px solid #D9CEBC",
                      background: "#EAE1D1", color: "#2A1A0A", fontSize: 13, fontWeight: 500,
                      cursor: "pointer", fontFamily: "'DM Sans', sans-serif",
                    }}>Annuleren</button>
                    <button onClick={confirmCopy} style={{
                      padding: "8px 16px", borderRadius: 8, border: "none",
                      background: "#B8622A", color: "#FFF8F0", fontSize: 13, fontWeight: 500,
                      cursor: "pointer", fontFamily: "'DM Sans', sans-serif",
                      boxShadow: "0 2px 8px rgba(184,98,42,0.3)",
                    }}>Kopiëren</button>
                  </div>
                </div>
              </div>
            )}
          </div>
        )}

        {/* ── PROGNOSE TAB ── */}
        {mainTab === "prognose" && (
          <div style={{ padding: "24px 28px" }}>

            {/* Dynamic charts */}
            {charts.map(chart => {
              const cm = CHART_METRICS.find(m => m.key === chart.metric);
              const values = model.map(r => r[chart.metric]);
              const maxVal = Math.max(...values, 1);
              return (
                <ForecastChart
                  key={chart.id}
                  chartId={chart.id}
                  metric={chart.metric}
                  chartMetrics={CHART_METRICS}
                  model={model}
                  values={values}
                  maxVal={maxVal}
                  cm={cm}
                  numWeeks={numWeeks}
                  onChangeMetric={setChartMetric}
                  onRemove={charts.length > 1 ? removeChart : null}
                  year={year}
                  thisYear={thisYear}
                  thisWeekNo={thisWeekNo}
                  sourceIds={visibleSourceIds}
                  srcColors={srcColors}
                />
              );
            })}

            {/* Add chart button */}
            <div
              onClick={addChart}
              style={{
                marginBottom: 28, background: "transparent", border: "1.5px dashed #D9CEBC",
                borderRadius: 10, padding: "16px 22px", cursor: "pointer",
                display: "flex", alignItems: "center", justifyContent: "center", gap: 8,
                color: "#8A7158", fontSize: 13, fontWeight: 500,
                transition: "border-color 0.2s, color 0.2s, background 0.2s",
              }}
              onMouseEnter={e => { e.currentTarget.style.borderColor = "#B8622A"; e.currentTarget.style.color = "#B8622A"; e.currentTarget.style.background = "rgba(184,98,42,0.04)"; }}
              onMouseLeave={e => { e.currentTarget.style.borderColor = "#D9CEBC"; e.currentTarget.style.color = "#8A7158"; e.currentTarget.style.background = "transparent"; }}
            >
              <span style={{ fontSize: 18, lineHeight: 1 }}>+</span> Grafiek toevoegen
            </div>

            {/* Quarter tables */}
            {[1,2,3,4].map(q => {
              const qRows = model.map((row, i) => ({ ...row, modelIdx: i })).filter(row => row.quarter === q);
              if (qRows.length === 0) return null;
              return (
                <div key={q} style={{ marginBottom: 20 }}>
                  <div style={{ fontSize: 10, fontWeight: 600, color: "#B5AA9A", textTransform: "uppercase", letterSpacing: "0.1em", marginBottom: 6, paddingLeft: 2 }}>
                    {fiscalQuarterLabel(q, year)}
                  </div>
                  <div style={{ overflowX: "auto", borderRadius: 10, border: "1px solid #D9CEBC", overflow: "hidden", boxShadow: "0 1px 4px rgba(42,26,10,0.08)" }}>
                    <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
                      <thead>
                        <tr style={{ background: "#F3EDE1", borderBottom: "1px solid #D9CEBC" }}>
                          {[["Week","#8A7158"],["Leads","#2A1A0A"],["Afspraken",STAGE_COLORS.afspraken],["Offertes",STAGE_COLORS.offertes],["Orders",STAGE_COLORS.orders],["Omzet",STAGE_COLORS.omzet]].map(([h,c]) => (
                            <th key={h} style={{ padding: "10px 14px", textAlign: h === "Omzet" ? "right" : "left",
                              fontSize: 11, letterSpacing: "0.06em",
                              textTransform: "uppercase", color: c, fontWeight: 600 }}>{h}</th>
                          ))}
                        </tr>
                      </thead>
                      <tbody>
                        {qRows.map((row, ri) => {
                          const i = row.modelIdx;
                          const isCurrentWeek = row.calYear === thisYear && row.week === thisWeekNo;
                          const mon = mondayOfISOWeek(row.calYear, row.week);
                          return (
                            <React.Fragment key={i}>
                              {i === lastActualsWeek + 1 && lastActualsWeek >= 0 && (
                                <tr><td colSpan={6} style={{
                                  padding: "4px 14px", fontSize: 10, fontWeight: 600,
                                  color: "#8A7158", textAlign: "center",
                                  background: "linear-gradient(90deg, rgba(46,125,90,0.08) 0%, rgba(46,125,90,0.02) 40%, rgba(184,98,42,0.02) 60%, rgba(184,98,42,0.08) 100%)",
                                  borderBottom: "1px solid #D9CEBC", borderTop: "1px solid #D9CEBC",
                                  letterSpacing: "0.06em",
                                }}>
                                  <span style={{ color: "#2E7D5A" }}>↑ Actueel</span>
                                  <span style={{ color: "#D9CEBC", padding: "0 8px" }}>|</span>
                                  <span style={{ color: "#B8622A" }}>Prognose ↓</span>
                                </td></tr>
                              )}
                              <tr className="fc-data-row" style={{
                                borderBottom: "1px solid #EAE1D1",
                                background: weekHasActuals(i)
                                  ? (isCurrentWeek ? "rgba(46,125,90,0.10)" : "rgba(46,125,90,0.04)")
                                  : (isCurrentWeek ? "rgba(184,98,42,0.06)" : ri % 2 === 0 ? "#FAF7F2" : "#FFFFFF"),
                              }}>
                                <td style={{ ...TD2, color: isCurrentWeek ? "#B8622A" : "#8A7158", minWidth: 90 }}>
                                  <span style={{ fontWeight: isCurrentWeek ? 700 : 400 }}>W{String(row.week).padStart(2,"00")}</span>
                                  <span style={{ marginLeft: 6, fontSize: 10, color: isCurrentWeek ? "#B8622A" : "#D9CEBC", fontWeight: 500 }}>{fmtDate(mon)}</span>
                                </td>
                                <td style={TD2}>{fmt(row.leads)}</td>
                                <td style={{ ...TD2, color: STAGE_COLORS.afspraken }}>{fmt(row.afspraken)}</td>
                                <td style={{ ...TD2, color: STAGE_COLORS.offertes }}>{fmt(row.offertes)}</td>
                                <td style={{ ...TD2, color: STAGE_COLORS.orders }}>{fmt(row.orders)}</td>
                                <td style={{ ...TD2, color: STAGE_COLORS.omzet, textAlign: "right", fontWeight: 600 }}>{fmtEur(row.omzet)}</td>
                              </tr>
                            </React.Fragment>
                          );
                        })}
                      </tbody>
                      <tfoot>
                        <tr style={{ background: "#F3EDE1", borderTop: "1px solid #D9CEBC" }}>
                          <td style={{ ...TD2, color: "#8A7158", fontWeight: 600, fontSize: 10, textTransform: "uppercase", letterSpacing: "0.06em" }}>Subtotaal</td>
                          {[
                            [qRows.reduce((a,r)=>a+r.leads,0),     "#2A1A0A"],
                            [qRows.reduce((a,r)=>a+r.afspraken,0), STAGE_COLORS.afspraken],
                            [qRows.reduce((a,r)=>a+r.offertes,0),  STAGE_COLORS.offertes],
                            [qRows.reduce((a,r)=>a+r.orders,0),    STAGE_COLORS.orders],
                          ].map(([v,c], j) => (
                            <td key={j} style={{ ...TD2, color: c, fontWeight: 600 }}>{fmt(v)}</td>
                          ))}
                          <td style={{ ...TD2, color: STAGE_COLORS.omzet, textAlign: "right", fontWeight: 600 }}>
                            {fmtEur(qRows.reduce((a,r)=>a+r.omzet,0))}
                          </td>
                        </tr>
                      </tfoot>
                    </table>
                  </div>
                </div>
              );
            })}

            {/* Year total */}
            <div style={{ ...CARD_STYLE, padding: "14px 18px", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
              <span style={{ fontSize: 11, fontWeight: 700, color: "#8A7158", textTransform: "uppercase", letterSpacing: "0.06em" }}>Jaartotaal</span>
              <div style={{ display: "flex", gap: 20, alignItems: "baseline" }}>
                <span style={{ fontSize: 13, fontWeight: 600 }}>{fmt(model.reduce((a,r)=>a+r.leads,0))} leads</span>
                <span style={{ fontSize: 13, fontWeight: 600, color: STAGE_COLORS.orders }}>{fmt(model.reduce((a,r)=>a+r.orders,0))} orders</span>
                <span style={{ fontSize: 16, fontWeight: 700, fontFamily: "'Fraunces', Georgia, serif", color: STAGE_COLORS.omzet }}>
                  {fmtEur(model.reduce((a,r)=>a+r.omzet,0))}
                </span>
              </div>
            </div>
          </div>
        )}
      </div>

      {/* Settings modal */}
      {showSettings && (
        <SourceSettingsModal
          sources={sources}
          onUpdate={handleSourcesUpdate}
          onClose={() => setShowSettings(false)}
        />
      )}
    </>
  );
}

window.ForecastApp = ForecastApp;
