/* Driftwood — shared UI primitives: icons, Donut, Spark, AreaChart, helpers.
   Exports to window for use by all screen files. */

/* ---- Icon set (simple line icons) ---- */
const ICONS = {
  dashboard: "M3 10.5 12 3l9 7.5M5 9.5V20h5v-6h4v6h5V9.5",
  transactions: "M4 7h16M4 12h16M4 17h10",
  budgets: "M3 12a9 9 0 1 0 9-9v9z",
  income: "M12 3v18M8.5 7.5h5.5a2.5 2.5 0 0 1 0 5H10a2.5 2.5 0 0 0 0 5h6",
  bills: "M5 3h14v18l-2.5-1.6L14 21l-2-1.6L10 21l-2.5-1.6L5 21zM9 8h6M9 12h6",
  debt: "M2 7h20v10H2zM2 11h20M6 15h4",
  goals: "M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0-18 0M12 12m-5 0a5 5 0 1 0 10 0a5 5 0 1 0-10 0M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0-2 0",
  reports: "M4 20V10M10 20V4M16 20v-7M22 20H2",
  plus: "M12 5v14M5 12h14",
  arrowUp: "M12 19V5M6 11l6-6 6 6",
  arrowDown: "M12 5v14M6 13l6 6 6-6",
  logo: "M4 14c3 0 3-5 6-5s3 5 6 5 4-5 4-5M4 18c3 0 3-3 6-3s3 3 6 3 4-3 4-3",
  wallet: "M3 7h15a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4a1 1 0 0 1-1-1zM3 7l2.5-3.5L16 6M17 13h.01",
  chevron: "M9 6l6 6-6 6",
};
function Icon({ name, style }) {
  return (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"
      strokeLinecap="round" strokeLinejoin="round" style={style}>
      <path d={ICONS[name] || ""} />
    </svg>
  );
}

/* ---- Donut (multi-segment) ---- */
function Donut({ segments, size = 188, stroke = 26 }) {
  const r = (size - stroke) / 2;
  const C = 2 * Math.PI * r;
  const total = segments.reduce((s, x) => s + x.value, 0) || 1;
  let acc = 0;
  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
      <g transform={`rotate(-90 ${size / 2} ${size / 2})`}>
        <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke="oklch(1 0 0 / 0.06)" strokeWidth={stroke} />
        {segments.map((s, i) => {
          const len = (s.value / total) * C;
          const el = (
            <circle key={i} cx={size / 2} cy={size / 2} r={r} fill="none"
              stroke={s.color} strokeWidth={stroke}
              strokeDasharray={`${Math.max(0, len - 2.5)} ${C - len + 2.5}`} strokeDashoffset={-acc}
              style={{ transition: "stroke-dasharray .5s, stroke-dashoffset .5s" }} />
          );
          acc += len;
          return el;
        })}
      </g>
    </svg>
  );
}

/* ---- Sparkline ---- */
function Spark({ data, w = 92, h = 30, color = "var(--blue-soft)", fill = false }) {
  const min = Math.min(...data), max = Math.max(...data);
  const span = max - min || 1;
  const pts = data.map((v, i) => {
    const x = (i / (data.length - 1)) * w;
    const y = h - ((v - min) / span) * (h - 5) - 2.5;
    return [x, y];
  });
  const line = pts.map((p) => `${p[0].toFixed(1)},${p[1].toFixed(1)}`).join(" ");
  const area = `0,${h} ${line} ${w},${h}`;
  const id = "sg" + Math.random().toString(36).slice(2, 7);
  return (
    <svg width={w} height={h}>
      {fill && (
        <>
          <defs><linearGradient id={id} x1="0" y1="0" x2="0" y2="1">
            <stop offset="0" stopColor={color} stopOpacity="0.28" /><stop offset="1" stopColor={color} stopOpacity="0" />
          </linearGradient></defs>
          <polygon points={area} fill={`url(#${id})`} />
        </>
      )}
      <polyline points={line} fill="none" stroke={color} strokeWidth="1.8" strokeLinejoin="round" strokeLinecap="round" />
    </svg>
  );
}

/* ---- Area chart: income vs expenses ---- */
function AreaChart({ data, w = 760, h = 300, pad = { t: 16, r: 12, b: 30, l: 46 } }) {
  const maxV = Math.max(...data.flatMap((d) => [d.income, d.expenses]));
  const top = Math.ceil(maxV / 2000) * 2000;
  const iw = w - pad.l - pad.r, ih = h - pad.t - pad.b;
  const x = (i) => pad.l + (i / (data.length - 1)) * iw;
  const y = (v) => pad.t + ih - (v / top) * ih;
  const path = (key) => {
    const pts = data.map((d, i) => `${x(i).toFixed(1)},${y(d[key]).toFixed(1)}`);
    return "M" + pts.join(" L");
  };
  const areaPath = (key) => `${path(key)} L${x(data.length - 1).toFixed(1)},${(pad.t + ih).toFixed(1)} L${pad.l},${(pad.t + ih).toFixed(1)} Z`;
  const ticks = [0, 0.25, 0.5, 0.75, 1].map((f) => Math.round(top * f));
  return (
    <svg width="100%" viewBox={`0 0 ${w} ${h}`} style={{ display: "block" }}>
      <defs>
        <linearGradient id="incG" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor="var(--green)" stopOpacity="0.26" /><stop offset="1" stopColor="var(--green)" stopOpacity="0" /></linearGradient>
        <linearGradient id="expG" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stopColor="var(--blue)" stopOpacity="0.22" /><stop offset="1" stopColor="var(--blue)" stopOpacity="0" /></linearGradient>
      </defs>
      {ticks.map((t, i) => (
        <g key={i}>
          <line x1={pad.l} x2={w - pad.r} y1={y(t)} y2={y(t)} stroke="oklch(1 0 0 / 0.06)" strokeWidth="1" />
          <text x={pad.l - 10} y={y(t) + 4} textAnchor="end" fill="var(--dim)" fontSize="11" fontFamily="Space Grotesk">${(t / 1000).toFixed(0)}k</text>
        </g>
      ))}
      <path d={areaPath("income")} fill="url(#incG)" />
      <path d={areaPath("expenses")} fill="url(#expG)" />
      <path d={path("income")} fill="none" stroke="var(--green)" strokeWidth="2.6" strokeLinejoin="round" strokeLinecap="round" />
      <path d={path("expenses")} fill="none" stroke="var(--blue-soft)" strokeWidth="2.6" strokeLinejoin="round" strokeLinecap="round" />
      {data.map((d, i) => (
        <g key={i}>
          <circle cx={x(i)} cy={y(d.income)} r="4" fill="var(--green)" stroke="var(--bg)" strokeWidth="2" />
          <circle cx={x(i)} cy={y(d.expenses)} r="4" fill="var(--blue-soft)" stroke="var(--bg)" strokeWidth="2" />
          <text x={x(i)} y={h - 8} textAnchor="middle" fill="var(--dim)" fontSize="11.5" fontFamily="Space Grotesk">{d.m}</text>
        </g>
      ))}
    </svg>
  );
}

const PALETTE = {
  blue: "var(--blue)", "blue-soft": "var(--blue-soft)", violet: "var(--violet)", teal: "var(--teal)",
  amber: "var(--amber)", pink: "var(--pink)", sky: "var(--sky)", green: "var(--green)", rose: "var(--rose)",
};
const SWATCHES = ["blue", "teal", "violet", "amber", "sky", "pink", "green", "rose"];

/* ---- Reusable modal + form fields ---- */
function Modal({ title, sub, children, onClose, onSave, saveLabel = "Save", onDelete, deleteLabel = "Delete" }) {
  return (
    <div className="modal-bg" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <h2>{title}</h2>
        {sub && <p className="sub">{sub}</p>}
        {children}
        <div className="modal-foot" style={{ justifyContent: onDelete ? "space-between" : "flex-end" }}>
          {onDelete && <button className="btn btn-ghost" style={{ color: "var(--neg)" }} onClick={onDelete}>{deleteLabel}</button>}
          <div style={{ display: "flex", gap: 10 }}>
            <button className="btn btn-ghost" onClick={onClose}>Cancel</button>
            <button className="btn btn-primary" onClick={onSave}>{saveLabel}</button>
          </div>
        </div>
      </div>
    </div>
  );
}

function TextField({ label, value, onChange, placeholder, autoFocus }) {
  return (
    <div className="field" style={{ marginBottom: 14 }}>
      <label>{label}</label>
      <div className="input"><input value={value} onChange={(e) => onChange(e.target.value)} placeholder={placeholder} autoFocus={autoFocus} style={{ fontSize: 15, fontWeight: 500 }} /></div>
    </div>
  );
}

function MoneyField({ label, value, onChange, placeholder = "0" }) {
  return (
    <div className="field" style={{ marginBottom: 14 }}>
      <label>{label}</label>
      <div className="input"><span className="pre">$</span><input value={value} onChange={(e) => onChange(e.target.value)} placeholder={placeholder} inputMode="decimal" /></div>
    </div>
  );
}

function SelectField({ label, value, onChange, options }) {
  return (
    <div className="field" style={{ marginBottom: 14 }}>
      <label>{label}</label>
      <div className="input"><select value={value} onChange={(e) => onChange(e.target.value)} style={{ width: "100%", fontSize: 15 }}>
        {options.map((o) => <option key={o} value={o}>{o}</option>)}
      </select></div>
    </div>
  );
}

function ColorPicker({ value, onChange, colors = SWATCHES }) {
  return (
    <div className="field" style={{ marginBottom: 4 }}>
      <label>Color</label>
      <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
        {colors.map((c) => (
          <button key={c} onClick={() => onChange(c)} aria-label={c}
            style={{ width: 30, height: 30, borderRadius: 9, background: PALETTE[c], cursor: "pointer",
              border: value === c ? "2px solid var(--hi)" : "2px solid transparent",
              boxShadow: value === c ? "0 0 0 2px var(--bg) inset" : "none", padding: 0 }} />
        ))}
      </div>
    </div>
  );
}

/* ---- Centralized household members (single source of truth: the Income page) ----
   Every screen that shows or selects a person reads from this one store, so a
   rename on Income instantly flows to the Dashboard, Transactions, Reports, etc. */
const MEMBERS_KEY = "driftwood.v1.income.people";
function defaultMembers() {
  const inc = (window.FP && window.FP.income && window.FP.income.people) || [];
  return [
    { name: "Maya", role: "Parent", amount: String(inc[0] ? inc[0].biweekly : 2400), freq: "biweekly", tax: 22 },
    { name: "Devin", role: "Parent", amount: String(inc[1] ? inc[1].biweekly : 3100), freq: "biweekly", tax: 24 },
  ];
}
const memberStore = (() => {
  const listeners = new Set();
  const K = MEMBERS_KEY;
  const read = () => {
    try { const raw = localStorage.getItem(_localKey(K)); if (raw != null) return JSON.parse(raw); } catch (e) {}
    return defaultMembers();
  };
  let state = read();
  const emit = () => listeners.forEach((l) => l());
  // Load from API on startup — overwrites local if present
  _apiGet(K).then((remote) => {
    if (remote !== null && remote !== undefined) {
      state = remote;
      try { localStorage.setItem(_localKey(K), JSON.stringify(remote)); } catch (e) {}
      emit();
    }
  });
  return {
    get: () => state,
    set: (next) => {
      state = typeof next === "function" ? next(state) : next;
      try { localStorage.setItem(_localKey(K), JSON.stringify(state)); } catch (e) {}
      _apiPut(K, state);
      emit();
    },
    subscribe: (l) => { listeners.add(l); return () => listeners.delete(l); },
    refresh: () => { state = read(); emit(); },
  };
})();
// Cross-tab storage sync removed — it caused households in different tabs to bleed into each other.
// Member data now syncs via the API (namespaced by household code) on page load.
/* Shared state hook — all consumers stay in sync. Same API as useState. */
function useMembers() {
  const [, force] = React.useReducer((x) => x + 1, 0);
  React.useEffect(() => memberStore.subscribe(force), []);
  return [memberStore.get(), memberStore.set];
}
/* Just the (trimmed, non-empty) names — for selects, filters & labels. */
function useMemberNames() {
  const [people] = useMembers();
  const names = people.map((p) => (p.name || "").trim()).filter(Boolean);
  return names.length ? names : ["Me"];
}
function householdLabel(names) {
  const list = (names || []).filter(Boolean);
  if (list.length === 0) return "My household";
  if (list.length <= 2) return list.join(" & ");
  return list.slice(0, -1).join(", ") + " & " + list[list.length - 1];
}
function initialsOf(name) {
  return ((name || "").trim()[0] || "?").toUpperCase();
}

/* ---- API sync helpers ─────────────────────────────────────────────────────
   All reads/writes mirror localStorage AND the Cloudflare Worker (if configured).
   Set window.DRIFTWOOD_API in the HTML config block to enable shared sync.
   Falls back gracefully to localStorage-only if the API is not configured.    */

function _apiHeaders() {
  const h = { "Content-Type": "application/json" };
  const tok = sessionStorage.getItem("driftwood.household") || window.DRIFTWOOD_TOKEN || "";
  if (tok) h["X-App-Token"] = tok;
  return h;
}
async function _apiGet(K) {
  const base = window.DRIFTWOOD_API;
  if (!base) return undefined;
  try {
    const r = await fetch(`${base}/store/${encodeURIComponent(K)}`, { headers: _apiHeaders() });
    if (!r.ok) return undefined;
    const { value } = await r.json();
    return value; // null means key exists but is null; undefined means request failed
  } catch (e) { return undefined; }
}
function _apiPut(K, value) {
  const base = window.DRIFTWOOD_API;
  if (!base) return;
  fetch(`${base}/store/${encodeURIComponent(K)}`, {
    method: "PUT",
    headers: _apiHeaders(),
    body: JSON.stringify({ value }),
  }).catch(() => {});
}
function _apiClear() {
  const base = window.DRIFTWOOD_API;
  if (!base) return;
  fetch(`${base}/store`, { method: "DELETE", headers: _apiHeaders() }).catch(() => {});
}

function _localKey(K) {
  const hh = sessionStorage.getItem("driftwood.household") || "";
  return hh ? hh + ":" + K : K;
}

/* ---- Persistence: localStorage (instant) + API sync (shared) ---- */
function usePersist(key, initial) {
  const K = "driftwood.v1." + key;
  // Read from localStorage immediately so the UI renders without a flash
  const [val, setValInner] = React.useState(() => {
    try {
      const raw = localStorage.getItem(_localKey(K));
      if (raw != null) return JSON.parse(raw);
    } catch (e) {}
    return typeof initial === "function" ? initial() : initial;
  });

  // On mount, fetch from the shared API — overwrites local if a newer value exists
  React.useEffect(() => {
    _apiGet(K).then((remote) => {
      if (remote !== null && remote !== undefined) {
        setValInner(remote);
        try { localStorage.setItem(_localKey(K), JSON.stringify(remote)); } catch (e) {}
      }
    });
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [K]);

  const setVal = React.useCallback((next) => {
    setValInner((prev) => {
      const newVal = typeof next === "function" ? next(prev) : next;
      // Write to localStorage for instant reactivity
      try { localStorage.setItem(_localKey(K), JSON.stringify(newVal)); } catch (e) {}
      // Write to shared API (fire-and-forget)
      _apiPut(K, newVal);
      return newVal;
    });
  }, [K]);

  return [val, setVal];
}

function clearStored() {
  const hh = sessionStorage.getItem("driftwood.household") || "";
  const prefix = hh ? hh + ":driftwood.v1." : "driftwood.v1.";
  Object.keys(localStorage).filter((k) => k.startsWith(prefix)).forEach((k) => localStorage.removeItem(k));
  _apiClear();
}

Object.assign(window, { Icon, Donut, Spark, AreaChart, PALETTE, SWATCHES, Modal, TextField, MoneyField, SelectField, ColorPicker, usePersist, clearStored, useMembers, useMemberNames, householdLabel, initialsOf });
