// ════════════ PX DATA · shared domain stores ════════════
// Generalizes the PXBank pattern (px-bank-store.jsx): tiny reactive
// module-level stores so records survive navigation/remounts and every
// surface reads the SAME data — no more per-screen hardcoded islands.
// Resets on full page reload — clean slate each session.

window.makeStore = function (seed) {
  let state = JSON.parse(JSON.stringify(seed));
  const subs = new Set();
  return {
    get: () => state,
    subscribe: (fn) => { subs.add(fn); return () => subs.delete(fn); },
    set: (updater) => {
      state = typeof updater === 'function' ? updater(state) : { ...state, ...updater };
      subs.forEach(f => f());
    },
  };
};

window.useStore = function (store) {
  const [, force] = React.useReducer(x => x + 1, 0);
  React.useEffect(() => store.subscribe(force), []);
  return store.get();
};

// ────────────────────────────────────────────────────────────────
// Invoices — the PILOT thread's spine. Editor writes here, list and
// detail read here. Statuses: draft · sent · paid · overdue · void.
// ────────────────────────────────────────────────────────────────
const INVOICE_SEED = {
  invoices: [
    { id: '1028', who: 'Lila Events', contact: 'lila', email: 'lila@lilaevents.nyc', addr: '410 Kent Ave, Brooklyn',
      amt: 2014.19, status: 'draft', date: 'Mar 12', due: 'Apr 11', terms: 'net30',
      items: [{ desc: 'Event catering · pastry spread', qty: 1, rate: 1864.19 }, { desc: 'Delivery · Manhattan', qty: 1, rate: 150 }],
      subtotal: 2014.19, discount: 0, taxRate: 0, tax: 0,
      timeline: [{ t: 'Created', d: 'Mar 12, 8:40am', icon: 'check' }] },
    { id: '1027', who: 'BK Food Co-op', contact: 'bkcoop', email: 'orders@bkfood.coop', addr: '620 Manhattan Ave, Brooklyn',
      amt: 460.00, status: 'sent', date: 'Mar 9', due: 'Apr 2', terms: 'net30',
      items: [{ desc: 'Rye & caraway · 12 ct', qty: 5, rate: 48 }, { desc: 'Pain de mie · 8 loaves', qty: 5, rate: 32 }, { desc: 'Delivery · local', qty: 2, rate: 30 }],
      subtotal: 460, discount: 0, taxRate: 0, tax: 0,
      timeline: [{ t: 'Created', d: 'Mar 9, 7:15am', icon: 'check' }, { t: 'Sent to BK Food Co-op', d: 'Mar 9, 7:22am', icon: 'send' }] },
    { id: '1026', who: 'Greenpoint Market', contact: 'green', email: 'ap@greenpointmkt.com', addr: '1102 Manhattan Ave, Brooklyn',
      amt: 2400.00, status: 'sent', date: 'Mar 6', due: 'Mar 16', terms: 'net15',
      items: [{ desc: 'Wholesale bread program · March', qty: 1, rate: 2400 }],
      subtotal: 2400, discount: 0, taxRate: 0, tax: 0,
      timeline: [{ t: 'Created', d: 'Mar 6, 9:00am', icon: 'check' }, { t: 'Sent to Greenpoint Market', d: 'Mar 6, 9:04am', icon: 'send' }, { t: 'Viewed', d: 'Mar 7', icon: 'eye' }] },
    { id: '1025', who: 'Lila Events', contact: 'lila', email: 'lila@lilaevents.nyc', addr: '410 Kent Ave, Brooklyn',
      amt: 1850.00, status: 'paid', date: 'Mar 2', due: 'Mar 16', terms: 'net15',
      items: [{ desc: 'Event catering · winter menu', qty: 1, rate: 1850 }],
      subtotal: 1850, discount: 0, taxRate: 0, tax: 0,
      timeline: [{ t: 'Created', d: 'Mar 2', icon: 'check' }, { t: 'Sent to Lila Events', d: 'Mar 2', icon: 'send' }, { t: 'Paid · card', d: 'Mar 8', icon: 'check' }] },
    { id: '1024', who: 'Café Bleu', contact: 'marc', email: 'marc@cafebleu.nyc', addr: '88 Bedford Ave, Brooklyn',
      amt: 1420.00, status: 'overdue', date: 'Mar 1', due: 'Mar 1', terms: 'receipt',
      items: [{ desc: 'Country bâtards', qty: 20, rate: 42 }, { desc: 'Baguettes', qty: 10, rate: 38 }, { desc: 'Delivery', qty: 1, rate: 200 }],
      subtotal: 1420, discount: 0, taxRate: 0, tax: 0,
      note: "Marc replied he'll pay Friday. Payment auto-confirms when it lands, and reminders stop automatically.",
      timeline: [{ t: 'Created', d: 'Mar 1, 9:02am', icon: 'check' }, { t: 'Sent to Marc', d: 'Mar 1, 9:05am', icon: 'send' }, { t: 'Viewed 3×', d: 'last Mar 6', icon: 'eye' }, { t: 'Reminder sent', d: 'Today 7:12am', icon: 'send' }, { t: 'Marc replied', d: 'Today 7:41am · paying Fri', icon: 'reply' }] },
    { id: '1023', who: 'Park Bros. Hotel', contact: 'park', email: 'ap@parkbros.com', addr: '25 Kent Ave, Brooklyn',
      amt: 12000.00, status: 'paid', date: 'Feb 24', due: 'Mar 10', terms: 'net15',
      items: [{ desc: 'Hotel breakfast program · Feb', qty: 1, rate: 12000 }],
      subtotal: 12000, discount: 0, taxRate: 0, tax: 0,
      timeline: [{ t: 'Created', d: 'Feb 24', icon: 'check' }, { t: 'Sent to Park Bros. Hotel', d: 'Feb 24', icon: 'send' }, { t: 'Paid · ACH', d: 'Mar 4', icon: 'check' }] },
    { id: '1022', who: 'Slate & Stone', contact: 'slate', email: 'hello@slateandstone.nyc', addr: '77 N 6th St, Brooklyn',
      amt: 800.00, status: 'paid', date: 'Feb 20', due: 'Mar 6', terms: 'net15',
      items: [{ desc: 'Sourdough class · private', qty: 10, rate: 80 }],
      subtotal: 800, discount: 0, taxRate: 0, tax: 0,
      timeline: [{ t: 'Created', d: 'Feb 20', icon: 'check' }, { t: 'Sent to Slate & Stone', d: 'Feb 20', icon: 'send' }, { t: 'Paid · card', d: 'Feb 27', icon: 'check' }] },
  ],
};

window.PXInvoices = (function () {
  const store = window.makeStore(INVOICE_SEED);
  const list = () => store.get().invoices;
  return Object.assign(store, {
    list,
    byId: (id) => list().find(i => i.id === String(id)),
    nextNumber: () => String(list().reduce((m, i) => Math.max(m, parseInt(i.id, 10) || 0), 0) + 1),
    add: (inv) => store.set(s => ({ ...s, invoices: [inv, ...s.invoices] })),
    update: (id, patch) => store.set(s => ({
      ...s,
      invoices: s.invoices.map(i => i.id === String(id) ? { ...i, ...(typeof patch === 'function' ? patch(i) : patch) } : i),
    })),
    // prepend a timeline event on a record
    logEvent: (id, t, d, icon) => store.set(s => ({
      ...s,
      invoices: s.invoices.map(i => i.id === String(id) ? { ...i, timeline: [...(i.timeline || []), { t, d, icon }] } : i),
    })),
  });
})();

// ────────────────────────────────────────────────────────────────
// Monotonic id helper (Date.now/Math.random are avoided elsewhere, but
// fine in the live browser page; a counter keeps ids stable per session).
// ────────────────────────────────────────────────────────────────
let __pxid = 1000;
window.pxid = (prefix) => (prefix || 'id') + '-' + (++__pxid);

// ────────────────────────────────────────────────────────────────
// Pex — the agent loop's shared state. `draftPrompt` carries an Ask-Pex
// request from the modal into the Pex screen; `runs` is the execution
// surface: each approved proposal becomes a run with step-by-step progress.
// ────────────────────────────────────────────────────────────────
window.PXPex = (function () {
  const store = window.makeStore({ draftPrompt: '', runs: [] });
  return Object.assign(store, {
    setDraftPrompt: (t) => store.set(s => ({ ...s, draftPrompt: t })),
    takeDraftPrompt: () => { const t = store.get().draftPrompt; store.set(s => ({ ...s, draftPrompt: '' })); return t; },
    // start a run from an approved proposal; returns the run id
    startRun: (run) => { store.set(s => ({ ...s, runs: [run, ...s.runs] })); return run.id; },
    advance: (id) => store.set(s => ({
      ...s,
      runs: s.runs.map(r => {
        if (r.id !== id || r.status === 'done') return r;
        const next = r.steps.findIndex(st => st.state !== 'done');
        if (next === -1) return { ...r, status: 'done' };
        const steps = r.steps.map((st, i) => i === next ? { ...st, state: 'done' } : st);
        return { ...r, steps, status: steps.every(st => st.state === 'done') ? 'done' : 'running' };
      }),
    })),
  });
})();

// Start a tracked run from an approved proposal and auto-advance its
// pipeline so you can watch Pex execute. Called from actProposal + the
// Pex screen; global so any surface can kick one off.
window.startPexRun = function (p) {
  p = p || {};
  const runId = window.PXPex.startRun({
    id: window.pxid('run'),
    title: p.title || 'Pex task',
    cost: p.cost || 0,
    tone: p.tone || 'spark',
    status: 'running',
    startedAt: 'Just now',
    steps: [
      { label: 'Understanding request', state: 'pending' },
      { label: 'Drafting', state: 'pending' },
      { label: 'Reviewing', state: 'pending' },
      { label: 'Ready', state: 'pending' },
    ],
  });
  let n = 0;
  const tick = () => { window.PXPex.advance(runId); if (++n < 4) setTimeout(tick, 950); };
  setTimeout(tick, 500);
  return runId;
};

// Derive a display view of a run (progress %, current step, one-liner).
window.pexRunView = function (r) {
  const total = r.steps.length;
  const done = r.steps.filter(s => s.state === 'done').length;
  const p = total ? Math.round(done / total * 100) : 0;
  const current = r.steps.find(s => s.state !== 'done');
  const d = r.status === 'done' ? `Completed · ${r.cost} ⚡ spent` : `${done} of ${total} steps · ${current ? current.label : 'Ready'}`;
  return { title: r.title, p, d, done: r.status === 'done', steps: r.steps };
};
