/* ============================================================
   BUDGET BUILDER — line-item budget vs. target
   Categories → lines (qty × rate = estimate) vs. actuals.
   Topsheet rolls up per category; grand total vs. brief target.
   Persisted per-project to localStorage. Real, editable, additive.
   ============================================================ */

const BUDGET_KEY = 'silverlines.budget.' + (window.PROJ_ID || 'crimson') + '.v1';

/* target pulled live from the brief so the budget tracks the headline number.
   A real (blank) production starts at 0 — the producer sets it. Only the demo
   carries the illustrative 85k headline. */
function budgetTarget() {
  try {
    const b = window.SLStore.get('silverlines.brief.v1', {});
    if (+b.budget) return +b.budget;
  } catch (e) {}
  return window.PROJ_BLANK ? 0 : 85000;
}
function setBudgetTargetValue(v) {
  v = Math.max(0, +v || 0);
  try {
    const b = window.SLStore.get('silverlines.brief.v1', {});
    b.budget = v;
    window.SLStore.set('silverlines.brief.v1', b);
  } catch (e) {}
  return v;
}

const BUDGET_SEED = [
  { id:'atl', name:'Above the Line', lines:[
    { id:'a1', desc:'Director', qty:1, unit:'flat', rate:9000, actual:9000 },
    { id:'a2', desc:'Producer', qty:1, unit:'flat', rate:6500, actual:6500 },
    { id:'a3', desc:'Lead Talent — Lena Frost', qty:1, unit:'flat', rate:7500, actual:7500 },
    { id:'a4', desc:'Featured Talent ×4', qty:4, unit:'day', rate:650, actual:0 },
  ]},
  { id:'prod', name:'Production', lines:[
    { id:'p1', desc:'Director of Photography', qty:4, unit:'day', rate:1100, actual:2200 },
    { id:'p2', desc:'Camera package + lenses', qty:4, unit:'day', rate:1450, actual:0 },
    { id:'p3', desc:'Grip & electric package', qty:4, unit:'day', rate:1200, actual:0 },
    { id:'p4', desc:'Crew — gaffer, AC, sound, grip', qty:4, unit:'day', rate:2600, actual:0 },
    { id:'p5', desc:'Art department & set build', qty:1, unit:'flat', rate:12000, actual:8400 },
    { id:'p6', desc:'Locations & permits', qty:1, unit:'flat', rate:5500, actual:5500 },
    { id:'p7', desc:'Catering & craft', qty:4, unit:'day', rate:780, actual:1560 },
    { id:'p8', desc:'Transport & logistics', qty:1, unit:'flat', rate:3200, actual:0 },
    { id:'p9', desc:'Production insurance', qty:1, unit:'flat', rate:2400, actual:2400 },
  ]},
  { id:'post', name:'Post-Production', lines:[
    { id:'o1', desc:'Editorial', qty:10, unit:'day', rate:550, actual:0 },
    { id:'o2', desc:'Color grade', qty:2, unit:'day', rate:1200, actual:0 },
    { id:'o3', desc:'VFX — ascent plate', qty:1, unit:'flat', rate:6500, actual:0 },
    { id:'o4', desc:'Sound design & mix', qty:1, unit:'flat', rate:3200, actual:0 },
    { id:'o5', desc:'Music license', qty:1, unit:'flat', rate:4000, actual:0 },
  ]},
  { id:'other', name:'Contingency & Other', lines:[
    { id:'c1', desc:'Contingency (10%)', qty:1, unit:'flat', rate:7800, actual:0 },
  ]},
];

function budgetLoad() {
  const s = window.SLStore.get(BUDGET_KEY); if (Array.isArray(s) && s.length) return s;
  return window.PROJ_BLANK ? [] : BUDGET_SEED;
}

const lineEst = l => (+l.qty || 0) * (+l.rate || 0);
const catEst = c => c.lines.reduce((a, l) => a + lineEst(l), 0);
const catActual = c => c.lines.reduce((a, l) => a + (+l.actual || 0), 0);

/* managed option lists — units & owners, editable + persisted per project */
const BUDGET_UNITS_KEY = 'silverlines.budgetUnits.' + (window.PROJ_ID || 'crimson') + '.v1';
const BUDGET_OWNERS_KEY = 'silverlines.budgetOwners.' + (window.PROJ_ID || 'crimson') + '.v1';
const UNITS_DEFAULT = ['flat', 'day', 'week', 'hour', 'each', '%'];
const OWNERS_DEFAULT = ['Producer', 'Director', 'DP', 'Art Dept', 'Post'];
const listLoad = (key, def) => { const s = window.SLStore.get(key); return Array.isArray(s) ? s : def.slice(); };
const listSave = (key, arr) => { window.SLStore.set(key, arr); };
const ownerColor = name => { let h = 0; for (let i = 0; i < (name || '').length; i++) h = (h * 31 + name.charCodeAt(i)) % 360; return `hsl(${h} 45% 55%)`; };
const initials = name => (name || '').split(/\s+/).map(w => w[0]).join('').slice(0, 2).toUpperCase();
const curSym = () => (CURRENCIES[curCode()] || CURRENCIES.USD).sym;

/* a value-picker whose option list the user can extend or prune inline */
function ManagedSelect({ value, options, onChange, onAddOption, onDeleteOption, placeholder = '—', allowEmpty = false, align = 'left', trigger }) {
  const [open, setOpen] = useState(false);
  const [draft, setDraft] = useState('');
  const ref = useRef(null);
  useEffect(() => {
    if (!open) return;
    const h = e => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', h);
    return () => document.removeEventListener('mousedown', h);
  }, [open]);
  const add = () => { const v = draft.trim(); if (v && !options.includes(v)) { onAddOption(v); onChange(v); } setDraft(''); };
  return (
    <div ref={ref} className="relative inline-block">
      <button onClick={() => setOpen(o => !o)}>{trigger}</button>
      {open && (
        <div className={cx('absolute z-30 mt-1 w-44 rounded-xl border border-line bg-ink-850 p-1.5 shadow-xl shadow-black/40', align === 'right' ? 'right-0' : 'left-0')}>
          <div className="max-h-52 overflow-y-auto">
            {allowEmpty && (
              <button onClick={() => { onChange(''); setOpen(false); }}
                className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left text-[12px] text-zinc-400 hover:bg-ink-800">Unassigned</button>
            )}
            {options.map(o => (
              <div key={o} className="group/op flex items-center gap-1 rounded-lg hover:bg-ink-800">
                <button onClick={() => { onChange(o); setOpen(false); }}
                  className={cx('flex-1 truncate px-2 py-1.5 text-left text-[12px]', value === o ? 'font-700 text-electric-soft' : 'text-zinc-200')}>{o}</button>
                {onDeleteOption && (
                  <button onClick={() => { onDeleteOption(o); if (value === o) onChange(allowEmpty ? '' : (options.find(x => x !== o) || '')); }}
                    title="Remove option" className="mr-1 flex h-5 w-5 shrink-0 items-center justify-center rounded text-zinc-600 opacity-0 transition-all hover:text-[#FF4D6D] group-hover/op:opacity-100"><Icon name="X" size={11} /></button>
                )}
              </div>
            ))}
          </div>
          <div className="mt-1 flex items-center gap-1 border-t border-line pt-1.5">
            <input value={draft} onChange={e => setDraft(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') add(); }}
              placeholder="Add…" className="min-w-0 flex-1 rounded-md border border-line bg-ink-900 px-2 py-1 text-[12px] text-zinc-200 outline-none focus:border-electric/60" />
            <button onClick={add} className="flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-cta text-cta-ink hover:bg-cta-hover"><Icon name="Plus" size={12} /></button>
          </div>
        </div>
      )}
    </div>
  );
}

function BudgetNum({ value, prefix = '', onChange, className = '', placeholder = '0' }) {
  return (
    <span className={className}>
      {prefix}<Edit value={value ? String(value) : ''} placeholder={placeholder} className={className}
        onChange={v => { const n = parseFloat(String(v).replace(/[^0-9.]/g, '')); onChange(isNaN(n) ? 0 : n); }} />
    </span>
  );
}

function BudgetRow({ l, units, owners, onPatch, onRemove, onAddUnit, onDeleteUnit, onAddOwner, onDeleteOwner, readOnly }) {
  /* derived, non-editable line fed live from the Purchasing module — never stored,
     so it can't drift or double-count. est = committed, actual = purchased. */
  if (l._derived) {
    return (
      <div className="flex items-center gap-3 border-t border-line/70 bg-electric/[0.04] px-3.5 py-2.5">
        <div className="min-w-0 flex-1">
          <div className="flex items-center gap-2">
            <span className="text-[13px] font-600 text-zinc-100">{l.desc}</span>
            <button onClick={() => window.__sl && window.__sl.setModule && window.__sl.setModule('purchase')}
              title="Open Purchasing" className="inline-flex items-center gap-1 rounded-md border border-line px-1.5 py-0.5 text-[10px] font-600 text-electric-soft hover:border-electric/50">
              <Icon name="ExternalLink" size={10} /> Purchasing</button>
          </div>
          {l._committed > 0 && <div className="mt-1 text-[11px] text-zinc-500">Committed {money(l._committed)} · awaiting purchase</div>}
        </div>
        <div className="w-[92px] shrink-0 text-right font-display text-[14px] font-700 text-head">{money(lineEst(l))}</div>
        <div className="w-[92px] shrink-0 text-right font-display text-[14px] font-700 text-mint">{l.actual ? money(l.actual) : '—'}</div>
        {!readOnly && <div className="w-7 shrink-0" />}
      </div>
    );
  }
  const est = lineEst(l);
  const act = +l.actual || 0;
  const variance = act === 0 ? 0 : act - est;
  const owner = l.owner || '';
  return (
    <div className="group/br flex items-center gap-3 border-t border-line/70 px-3.5 py-2.5 hover:bg-ink-800">
      <div className="min-w-0 flex-1">
        <Edit value={l.desc} onChange={v => onPatch({ desc: v })} placeholder="Line item…" className="text-[13px] font-600 text-zinc-100" />
        <div className="mt-1 flex items-center gap-2">
          <ManagedSelect value={owner} options={owners} allowEmpty align="left"
            onChange={v => onPatch({ owner: v })}
            onAddOption={readOnly ? null : onAddOwner} onDeleteOption={readOnly ? null : onDeleteOwner}
            trigger={owner
              ? <span className="inline-flex items-center gap-1.5 rounded-full border border-line bg-ink-850 py-0.5 pl-0.5 pr-2 text-[10.5px] font-600 text-zinc-300 hover:border-ink-600">
                  <span className="flex h-4 w-4 items-center justify-center rounded-full text-[8px] font-700 text-white" style={{ background: ownerColor(owner) }}>{initials(owner)}</span>{owner}
                </span>
              : <span className="inline-flex items-center gap-1 rounded-full border border-dashed border-ink-600 px-2 py-0.5 text-[10.5px] font-600 text-zinc-600 hover:text-zinc-400"><Icon name="UserPlus" size={11} /> Assign</span>} />
        </div>
      </div>
      <div className="hidden shrink-0 items-center gap-1 font-mono text-[11.5px] text-zinc-500 sm:flex">
        <BudgetNum value={l.qty} onChange={v => onPatch({ qty: v })} className="text-zinc-300" /> ×
        <ManagedSelect value={l.unit} options={units} align="left"
          onChange={v => onPatch({ unit: v })}
          onAddOption={readOnly ? null : onAddUnit} onDeleteOption={readOnly ? null : onDeleteUnit}
          trigger={<span className="inline-flex items-center gap-0.5 rounded-md border border-line bg-ink-850 px-1.5 py-0.5 text-[11px] text-zinc-300 hover:border-ink-600">{l.unit || 'unit'}<Icon name="ChevronDown" size={10} className="text-zinc-600" /></span>} />
        × <BudgetNum value={l.rate} prefix={curSym()} onChange={v => onPatch({ rate: v })} className="text-zinc-300" />
      </div>
      <div className="w-[92px] shrink-0 text-right font-display text-[14px] font-700 text-head">{money(est)}</div>
      <div className="w-[92px] shrink-0 text-right">
        <BudgetNum value={l.actual} prefix={curSym()} onChange={v => onPatch({ actual: v })} placeholder="—"
          className={cx('font-display text-[14px] font-700', act === 0 ? 'text-zinc-600' : variance > 0 ? 'text-[#FF4D6D]' : 'text-mint')} />
      </div>
      {!readOnly && (
        <button onClick={onRemove} title="Delete line"
          className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-zinc-700 opacity-0 transition-all hover:bg-[#FF4D6D]/12 hover:text-[#FF4D6D] group-hover/br:opacity-100">
          <Icon name="Trash2" size={13} />
        </button>
      )}
    </div>
  );
}

function BudgetModule({ mobile, readOnly = false }) {
  const [cats, setCatsState] = useState(budgetLoad);
  const setCats = next => {
    setCatsState(prev => {
      const v = typeof next === 'function' ? next(prev) : next;
      window.SLStore.set(BUDGET_KEY, v);
      return v;
    });
  };
  const [target, setTargetState] = useState(budgetTarget);
  const saveTarget = v => setTargetState(setBudgetTargetValue(v));

  const [units, setUnits] = useState(() => listLoad(BUDGET_UNITS_KEY, UNITS_DEFAULT));
  const [owners, setOwners] = useState(() => listLoad(BUDGET_OWNERS_KEY, OWNERS_DEFAULT));
  const addUnit = u => setUnits(us => { const n = us.includes(u) ? us : [...us, u]; listSave(BUDGET_UNITS_KEY, n); return n; });
  const deleteUnit = u => setUnits(us => { const n = us.filter(x => x !== u); listSave(BUDGET_UNITS_KEY, n); return n; });
  const addOwner = o => setOwners(os => { const n = os.includes(o) ? os : [...os, o]; listSave(BUDGET_OWNERS_KEY, n); return n; });
  const deleteOwner = o => setOwners(os => { const n = os.filter(x => x !== o); listSave(BUDGET_OWNERS_KEY, n); return n; });

  /* live link from Purchasing: fold a derived (non-editable, non-stored) line into
     the Art/Production category so the topsheet reflects real art-dept spend. */
  const pur = window.purchaseRollup ? window.purchaseRollup() : null;
  const purLine = (pur && pur.count > 0)
    ? { id: 'auto-purchase', desc: 'Art purchasing — from Purchasing', _derived: true, qty: 1, unit: 'flat', rate: pur.committed, actual: pur.purchased, _committed: pur.approved }
    : null;
  const displayCats = (() => {
    if (!purLine || !cats.length) return cats;
    let idx = cats.findIndex(c => /art/i.test(c.name));
    if (idx < 0) idx = cats.findIndex(c => /production/i.test(c.name));
    if (idx < 0) idx = 0;
    return cats.map((c, i) => i !== idx ? c : { ...c, lines: [...c.lines, purLine] });
  })();

  const totalEst = displayCats.reduce((a, c) => a + catEst(c), 0);
  const totalAct = displayCats.reduce((a, c) => a + catActual(c), 0);
  const overEst = totalEst > target;
  const overAct = totalAct > target;
  /* you can't un-spend actuals — the honest headline is the worse of estimate vs
     actuals-to-date, so an overspent production never reads as "under target" */
  const projected = Math.max(totalEst, totalAct);
  const remaining = target - projected;
  const over = projected > target;
  const pct = v => target > 0 ? Math.min(100, (v / target) * 100) : 0;

  const patchLine = (ci, id, p) => setCats(cs => cs.map((c, i) => i !== ci ? c : { ...c, lines: c.lines.map(l => l.id === id ? { ...l, ...p } : l) }));
  const removeLine = (ci, id) => {
    const prev = cats; const line = (cats[ci] && cats[ci].lines.find(l => l.id === id)) || {};
    setCats(cs => cs.map((c, i) => i !== ci ? c : { ...c, lines: c.lines.filter(l => l.id !== id) }));
    window.pushUndo && window.pushUndo(`Removed "${line.desc || 'line item'}"`, () => setCats(prev));
  };
  const addLine = ci => setCats(cs => cs.map((c, i) => i !== ci ? c : { ...c, lines: [...c.lines, { id:'l' + Date.now(), desc:'', qty:1, unit:'flat', rate:0, actual:0 }] }));
  const patchCat = (ci, p) => setCats(cs => cs.map((c, i) => i !== ci ? { ...c, ...{} } : { ...c, ...p }));
  const removeCat = ci => {
    const prev = cats; const cat = cats[ci] || {};
    setCats(cs => cs.filter((_, i) => i !== ci));
    window.pushUndo && window.pushUndo(`Removed category "${cat.name || ''}"`, () => setCats(prev));
  };
  const addCat = () => setCats(cs => [...cs, { id:'cat' + Date.now(), name:'New Category', lines:[] }]);

  return (
    <div className="fade-up">
      {!readOnly && <SectionTitle kicker="Producer · Finance" icon="Calculator" title="Budget Builder"
        action={<button onClick={addCat}
          className="flex items-center gap-2 rounded-xl bg-cta px-3.5 py-2.5 text-[12.5px] font-700 text-cta-ink transition-colors hover:bg-cta-hover">
          <Icon name="Plus" size={15} /> Add category
        </button>} />}

      {/* topsheet header */}
      <Panel className="mb-5">
        <div className="flex flex-wrap items-end justify-between gap-4">
          <div>
            <div className="text-[11px] uppercase tracking-[0.12em] text-zinc-500">Budgeted (estimate)</div>
            <div className="font-display text-[34px] font-700 leading-none text-head">{money(totalEst)}</div>
            <div className="mt-1 flex flex-wrap items-center gap-x-1 text-[12px] text-zinc-500">
              <span>Target</span>
              {readOnly
                ? <span>{money(target)}</span>
                : <span className="text-zinc-300"><BudgetNum value={target} onChange={saveTarget} placeholder="set target" className="text-[12px] text-zinc-300" /></span>}
              <span>· Actuals to date {money(totalAct)}</span>
            </div>
          </div>
          <div className="text-right">
            <div className="text-[11px] uppercase tracking-[0.12em] text-zinc-500">{over ? 'Over target' : 'Under target'}</div>
            <div className={cx('font-display text-[34px] font-700 leading-none', over ? 'text-[#FF4D6D]' : 'text-mint')}>{money(Math.abs(remaining))}</div>
          </div>
        </div>
        {/* estimate vs target bar */}
        <div className="mt-4 h-3 overflow-hidden rounded-full bg-ink-700">
          <div style={{ width: pct(projected) + '%' }} className={cx('h-full rounded-full transition-all', over ? 'bg-[#FF4D6D]' : 'bg-electric')} />
        </div>
        {over && (
          <div className="mt-3 flex items-center gap-2 rounded-lg border border-[#FF4D6D]/30 bg-[#FF4D6D]/8 px-3 py-2 text-[12px] font-600 text-[#FF4D6D]">
            <Icon name="TriangleAlert" size={13} /> {overAct
              ? `Actual spend is ${money(totalAct - target)} over the target budget — you're spending past the plan.`
              : `Estimate exceeds the target budget by ${money(totalEst - target)} — trim lines or raise the target above.`}
          </div>
        )}

        {/* per-category topsheet */}
        <div className="mt-4 grid grid-cols-2 gap-2.5 sm:grid-cols-4">
          {displayCats.map(c => {
            const e = catEst(c);
            return (
              <div key={c.id} className="rounded-xl border border-line bg-ink-900 p-3">
                <div className="truncate text-[11px] font-600 text-zinc-400">{c.name}</div>
                <div className="mt-1 font-display text-[20px] font-700 leading-none text-head">{money(e)}</div>
                <div className="mt-1 text-[10.5px] text-zinc-500">{totalEst > 0 ? Math.round((e / totalEst) * 100) : 0}% of budget</div>
              </div>
            );
          })}
        </div>
      </Panel>

      {/* category tables */}
      {cats.length === 0 ? (
        <EmptyState icon="Calculator" title="No budget yet"
          sub="Add a category — Above the Line, Production, Post — then break it into line items with qty × rate."
          action={!readOnly && <button onClick={addCat} className="flex items-center gap-2 rounded-xl bg-cta px-3.5 py-2.5 text-[12.5px] font-700 text-cta-ink transition-colors hover:bg-cta-hover"><Icon name="Plus" size={15} /> Add category</button>} />
      ) : (
      <div className="space-y-4">
        {displayCats.map((c, ci) => {
          const e = catEst(c), a = catActual(c);
          return (
            <Panel key={c.id} pad={false} className="overflow-hidden">
              <div className="flex items-center justify-between gap-3 border-b border-line p-3.5">
                <div className="flex items-center gap-2.5">
                  <span className="flex h-8 w-8 items-center justify-center rounded-lg bg-ink-700 text-zinc-300"><Icon name="Layers" size={16} /></span>
                  <div className="min-w-0">
                    <Edit value={c.name} onChange={v => patchCat(ci, { name: v })} className="text-[14px] font-700 text-zinc-100" />
                    <div className="text-[11px] text-zinc-500">{c.lines.length} line{c.lines.length === 1 ? '' : 's'}</div>
                  </div>
                </div>
                <div className="flex items-center gap-4">
                  <div className="text-right">
                    <div className="text-[10px] uppercase tracking-[0.1em] text-zinc-500">Estimate</div>
                    <div className="font-display text-[20px] font-700 leading-none text-head">{money(e)}</div>
                  </div>
                  <div className="hidden text-right sm:block">
                    <div className="text-[10px] uppercase tracking-[0.1em] text-zinc-500">Actual</div>
                    <div className={cx('font-display text-[20px] font-700 leading-none', a > e ? 'text-[#FF4D6D]' : 'text-mint')}>{a ? money(a) : '—'}</div>
                  </div>
                  {!readOnly && cats.length > 1 && (
                    <button onClick={() => removeCat(ci)} title="Remove category"
                      className="flex h-7 w-7 items-center justify-center rounded-md text-zinc-700 transition-colors hover:bg-[#FF4D6D]/12 hover:text-[#FF4D6D]"><Icon name="Trash2" size={13} /></button>
                  )}
                </div>
              </div>

              {/* column headers */}
              <div className="flex items-center gap-3 px-3.5 pb-1.5 pt-2.5 text-[10px] font-600 uppercase tracking-[0.1em] text-zinc-600">
                <div className="flex-1">Item</div>
                <div className="hidden w-auto sm:block">Qty × rate</div>
                <div className="w-[92px] text-right">Estimate</div>
                <div className="w-[92px] text-right">Actual</div>
                {!readOnly && <div className="w-7" />}
              </div>

              {c.lines.map(l => (
                <BudgetRow key={l.id} l={l} readOnly={readOnly} units={units} owners={owners}
                  onPatch={p => patchLine(ci, l.id, p)} onRemove={() => removeLine(ci, l.id)}
                  onAddUnit={addUnit} onDeleteUnit={deleteUnit} onAddOwner={addOwner} onDeleteOwner={deleteOwner} />
              ))}

              {!readOnly && (
                <button onClick={() => addLine(ci)}
                  className="flex w-full items-center justify-center gap-2 border-t border-line py-2.5 text-[12.5px] font-600 text-zinc-500 transition-colors hover:bg-ink-800 hover:text-electric-soft">
                  <Icon name="Plus" size={14} /> Add line item
                </button>
              )}
            </Panel>
          );
        })}
      </div>
      )}
    </div>
  );
}

window.BudgetModule = BudgetModule;
window.budgetLoad = budgetLoad;
