/* ============================================================
   lib.jsx — shared hooks, helpers, icons, chart primitives
   ============================================================ */
const { useState, useEffect, useRef, useMemo, useCallback } = React;

/* ---------- persistence ---------- */
function useLocalState(key, initial) {
  const k = "roan_" + key;
  const BASE = (typeof API !== "undefined" && API.baseUrl) || "/api";

  // Laad initieel uit localStorage (snelle start)
  const [val, setValRaw] = useState(() => {
    try {
      const s = localStorage.getItem(k);
      return s !== null ? JSON.parse(s) : initial;
    } catch (e) { return initial; }
  });

  // Haal bij opstarten de waarde op uit de database
  useEffect(() => {
    if (typeof API === "undefined" || API.useMock) return;
    fetch(`${BASE}/data/${encodeURIComponent(key)}`, { credentials: "include" })
      .then(r => r.json())
      .then(({ found, value }) => {
        if (found && value !== null) {
          setValRaw(value);
          try { localStorage.setItem(k, JSON.stringify(value)); } catch (e) {}
        }
      })
      .catch(() => {}); // stil falen — localStorage blijft als fallback
  }, [key]); // eslint-disable-line

  // Sla op in localStorage én database bij elke wijziging
  const setVal = (updater) => {
    setValRaw(prev => {
      const next = typeof updater === "function" ? updater(prev) : updater;
      try { localStorage.setItem(k, JSON.stringify(next)); } catch (e) {}
      if (typeof API !== "undefined" && !API.useMock) {
        fetch(`${BASE}/data/${encodeURIComponent(key)}`, {
          method: "POST", credentials: "include",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ value: next }),
        }).catch(() => {});
      }
      return next;
    });
  };

  return [val, setVal];
}

/* ---------- formatting ---------- */
const fmtEur = (n, dec = 0) =>
  "€" + Number(n).toLocaleString("nl-NL", { minimumFractionDigits: dec, maximumFractionDigits: dec });
const fmtNum = (n) => Number(n).toLocaleString("nl-NL");
const clamp = (n, a, b) => Math.max(a, Math.min(b, n));

const NL_DAYS = ["zo", "ma", "di", "wo", "do", "vr", "za"];
const NL_DAYS_FULL = ["zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag"];
const NL_MONTHS = ["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"];

function fmtDateNL(d) {
  return `${NL_DAYS_FULL[d.getDay()]} ${d.getDate()} ${NL_MONTHS[d.getMonth()]}`;
}
function greetingNL(d) {
  const h = d.getHours();
  if (h < 6) return "Goedenacht";
  if (h < 12) return "Goedemorgen";
  if (h < 18) return "Goedemiddag";
  return "Goedenavond";
}
// monday-based start of week
function startOfWeek(d) {
  const x = new Date(d);
  const day = (x.getDay() + 6) % 7;
  x.setDate(x.getDate() - day);
  x.setHours(0, 0, 0, 0);
  return x;
}
function addDays(d, n) { const x = new Date(d); x.setDate(x.getDate() + n); return x; }
function sameDay(a, b) { return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate(); }
function isoKey(d) { return d.toISOString().slice(0, 10); }

/* ---------- icons (stroke, lucide-ish, original) ---------- */
function Icon({ name, ...p }) {
  const paths = {
    home:      <><path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.5V20h14V9.5"/></>,
    wallet:    <><rect x="3" y="6" width="18" height="13" rx="2.5"/><path d="M3 9h18"/><circle cx="17" cy="13" r="1.3" fill="currentColor" stroke="none"/></>,
    chart:     <><path d="M4 19V5"/><path d="M4 19h16"/><path d="M8 16l3-4 3 2 4-6"/></>,
    target:    <><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="4"/><circle cx="12" cy="12" r="1" fill="currentColor" stroke="none"/></>,
    calendar:  <><rect x="3.5" y="5" width="17" height="16" rx="2.5"/><path d="M3.5 9.5h17M8 3v4M16 3v4"/></>,
    check:     <><path d="M4 12.5l5 5L20 6.5"/></>,
    checkCircle: <><circle cx="12" cy="12" r="9"/><path d="M8.5 12.5l2.5 2.5 4.5-5"/></>,
    flame:     <><path d="M12 3c1 3-2 4-2 7a2 2 0 1 0 4 0c0-1 0-1 .5-2 1.5 1.5 2.5 3 2.5 5a5 5 0 0 1-10 0c0-3.5 3-5 5-10z"/></>,
    smile:     <><circle cx="12" cy="12" r="9"/><path d="M8.5 14.5s1.3 1.8 3.5 1.8 3.5-1.8 3.5-1.8"/><circle cx="9" cy="10" r="1" fill="currentColor" stroke="none"/><circle cx="15" cy="10" r="1" fill="currentColor" stroke="none"/></>,
    droplet:   <><path d="M12 3.5c3.5 4 6 6.6 6 9.5a6 6 0 1 1-12 0c0-2.9 2.5-5.5 6-9.5z"/></>,
    dumbbell:  <><path d="M6.5 8v8M3.5 10v4M17.5 8v8M20.5 10v4M6.5 12h11"/></>,
    book:      <><path d="M4 5.5A2.5 2.5 0 0 1 6.5 3H19v15H6.5A2.5 2.5 0 0 0 4 20.5z"/><path d="M4 20.5A2.5 2.5 0 0 1 6.5 18H19v3H6.5"/></>,
    heart:     <><path d="M12 20s-7-4.4-9.3-8.5C1.2 8.6 2.6 5 6 5c2 0 3.2 1.2 4 2.4C10.8 6.2 12 5 14 5c3.4 0 4.8 3.6 3.3 6.5C19 15.6 12 20 12 20z"/></>,
    sparkle:   <><path d="M12 3l1.8 5.2L19 10l-5.2 1.8L12 17l-1.8-5.2L5 10l5.2-1.8z"/></>,
    compass:   <><circle cx="12" cy="12" r="9"/><path d="M15.5 8.5l-2 5-5 2 2-5z"/></>,
    sun:       <><circle cx="12" cy="12" r="4"/><path d="M12 2v2.5M12 19.5V22M2 12h2.5M19.5 12H22M4.9 4.9l1.8 1.8M17.3 17.3l1.8 1.8M19.1 4.9l-1.8 1.8M6.7 17.3l-1.8 1.8"/></>,
    moon:      <><path d="M20 14.5A8 8 0 1 1 9.5 4a6.5 6.5 0 0 0 10.5 10.5z"/></>,
    settings:  <><circle cx="12" cy="12" r="3"/><path d="M12 2.5l1.3 2.4 2.7-.5.9 2.6 2.4 1.3-1 2.5 1 2.5-2.4 1.3-.9 2.6-2.7-.5L12 21.5l-1.3-2.4-2.7.5-.9-2.6L4.7 15.7l1-2.5-1-2.5 2.4-1.3.9-2.6 2.7.5z"/></>,
    plus:      <><path d="M12 5v14M5 12h14"/></>,
    minus:     <><path d="M5 12h14"/></>,
    chevL:     <><path d="M15 5l-7 7 7 7"/></>,
    chevR:     <><path d="M9 5l7 7-7 7"/></>,
    arrowUp:   <><path d="M12 19V5M6 11l6-6 6 6"/></>,
    arrowDn:   <><path d="M12 5v14M6 13l6 6 6-6"/></>,
    trend:     <><path d="M3 17l6-6 4 4 8-8"/><path d="M21 7v5h-5"/></>,
    dots:      <><circle cx="5" cy="12" r="1.6" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.6" fill="currentColor" stroke="none"/><circle cx="19" cy="12" r="1.6" fill="currentColor" stroke="none"/></>,
    bell:      <><path d="M6 9a6 6 0 0 1 12 0c0 5 2 6 2 6H4s2-1 2-6z"/><path d="M10 19a2 2 0 0 0 4 0"/></>,
    search:    <><circle cx="11" cy="11" r="6.5"/><path d="M20 20l-3.6-3.6"/></>,
    clock:     <><circle cx="12" cy="12" r="9"/><path d="M12 7.5V12l3 2"/></>,
    coffee:    <><path d="M4 8h13v5a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4z"/><path d="M17 9h2.5a2.5 2.5 0 0 1 0 5H17"/><path d="M7 3.5v1.5M11 3.5v1.5"/></>,
    leaf:      <><path d="M5 19c0-8 6-13 14-13 0 8-6 13-14 13z"/><path d="M5 19c3-4 6-6 9-7.5"/></>,
  };
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.85"
         strokeLinecap="round" strokeLinejoin="round" {...p}>
      {paths[name]}
    </svg>
  );
}

/* ---------- widget header ---------- */
function WHead({ icon, title, sub, action, onAction, accent }) {
  return (
    <div className="wh">
      <div className="wh-l">
        {icon && <div className="wh-ico"><Icon name={icon} /></div>}
        <div style={{ minWidth: 0, flex: 1 }}>
          <div className="wh-title">{title}</div>
          {sub && <div className="wh-sub">{sub}</div>}
        </div>
      </div>
      {action && (
        <button className="wh-action" onClick={onAction}>{action}</button>
      )}
    </div>
  );
}

/* ---------- circular progress ring ---------- */
function Ring({ value, max = 100, size = 84, stroke = 9, color = "var(--accent)", track = "var(--surface-3)", children, rounded = true }) {
  const r = (size - stroke) / 2;
  const c = 2 * Math.PI * r;
  const pct = clamp(value / max, 0, 1);
  return (
    <div style={{ position: "relative", width: size, height: size, flexShrink: 0 }}>
      <svg width={size} height={size} style={{ transform: "rotate(-90deg)" }}>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={track} strokeWidth={stroke} />
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={color} strokeWidth={stroke}
                strokeDasharray={c} strokeDashoffset={c * (1 - pct)} strokeLinecap={rounded ? "round" : "butt"}
                style={{ transition: "stroke-dashoffset 0.6s cubic-bezier(.4,0,.2,1)" }} />
      </svg>
      <div style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center", textAlign: "center" }}>
        {children}
      </div>
    </div>
  );
}

/* ---------- smooth line / area chart ---------- */
function smoothPath(pts) {
  if (pts.length < 2) return "";
  let d = `M ${pts[0][0]} ${pts[0][1]}`;
  for (let i = 0; i < pts.length - 1; i++) {
    const [x0, y0] = pts[i], [x1, y1] = pts[i + 1];
    const cx = (x0 + x1) / 2;
    d += ` C ${cx} ${y0}, ${cx} ${y1}, ${x1} ${y1}`;
  }
  return d;
}
function LineChart({ data, height = 150, color = "var(--accent)", fill = true, pad = 6, showDot = true, gridY = 3 }) {
  const ref = useRef(null);
  const [w, setW] = useState(400);
  useEffect(() => {
    if (!ref.current) return;
    const ro = new ResizeObserver(es => setW(es[0].contentRect.width));
    ro.observe(ref.current);
    return () => ro.disconnect();
  }, []);
  const min = Math.min(...data), max = Math.max(...data);
  const span = max - min || 1;
  const H = height, topPad = 10, botPad = 10;
  const innerH = H - topPad - botPad;
  const pts = data.map((v, i) => [
    pad + (i / (data.length - 1)) * (w - pad * 2),
    topPad + innerH - ((v - min) / span) * innerH,
  ]);
  const line = smoothPath(pts);
  const area = line + ` L ${pts[pts.length-1][0]} ${H} L ${pts[0][0]} ${H} Z`;
  const last = pts[pts.length - 1];
  const gid = useMemo(() => "g" + Math.random().toString(36).slice(2, 8), []);
  return (
    <div ref={ref} style={{ width: "100%" }}>
      <svg width={w} height={H} style={{ display: "block", overflow: "visible" }}>
        <defs>
          <linearGradient id={gid} x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stopColor={color} stopOpacity="0.20" />
            <stop offset="100%" stopColor={color} stopOpacity="0" />
          </linearGradient>
        </defs>
        {Array.from({ length: gridY }).map((_, i) => {
          const y = topPad + (innerH / (gridY - 1)) * i;
          return <line key={i} x1="0" x2={w} y1={y} y2={y} stroke="var(--border)" strokeWidth="1" strokeDasharray="2 4" />;
        })}
        {fill && <path d={area} fill={`url(#${gid})`} />}
        <path d={line} fill="none" stroke={color} strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" />
        {showDot && <>
          <circle cx={last[0]} cy={last[1]} r="5.5" fill="var(--surface)" stroke={color} strokeWidth="2.6" />
        </>}
      </svg>
    </div>
  );
}

/* ---------- mini sparkline bars ---------- */
function Bars({ data, height = 46, color = "var(--accent)", gap = 4, highlightLast = false }) {
  const max = Math.max(...data, 1);
  return (
    <div style={{ display: "flex", alignItems: "flex-end", gap, height }}>
      {data.map((v, i) => (
        <div key={i} style={{
          flex: 1, height: `${clamp((v / max) * 100, 6, 100)}%`,
          background: (highlightLast && i === data.length - 1) ? color : "var(--surface-3)",
          borderRadius: 4, transition: "height 0.4s",
        }} />
      ))}
    </div>
  );
}

Object.assign(window, {
  useLocalState, fmtEur, fmtNum, clamp,
  NL_DAYS, NL_DAYS_FULL, NL_MONTHS, fmtDateNL, greetingNL,
  startOfWeek, addDays, sameDay, isoKey,
  Icon, WHead, Ring, LineChart, smoothPath, Bars,
  useState, useEffect, useRef, useMemo, useCallback,
});
