// VISUAL EDITOR v2 — Mutable state, pub/sub, mutation APIs
(function () {
  const clone = (x) => JSON.parse(JSON.stringify(x));
  const seed = window.VISUAL_EDITOR_DATA || {};

  const state = {
    validationIssues:     clone(seed.validationIssues || []),
    versions:             clone(seed.versions || []),
    subflows:             clone(seed.subflows || []),
    primitives:           clone(seed.primitives || []),
    swimlanes:            clone(seed.swimlanes || []),
    variables:            clone(seed.variables || []),
    schemas:              clone(seed.schemas || []),
    testFixtures:         clone(seed.testFixtures || []),
    copilotHistory:       clone(seed.copilotHistory || []),
    optimizerSuggestions: clone(seed.optimizerSuggestions || []),
    complianceRules:      clone(seed.complianceRules || []),
    collaborators:        clone(seed.collaborators || []),
    comments:             clone(seed.comments || []),
    approvalChain:        clone(seed.approvalChain || []),
    canary:               clone(seed.canary || {}),
    variants:             clone(seed.variants || []),
    heatmap:              clone(seed.heatmap || []),
    incidents:            clone(seed.incidents || []),
    costLedger:           clone(seed.costLedger || {}),
    marketplaceDropIns:   clone(seed.marketplaceDropIns || []),
    keyboardShortcuts:    clone(seed.keyboardShortcuts || []),
    a11y:                 clone(seed.a11y || {}),
    auditLog:             [],
  };

  const bus = {
    _subs: {},
    on(topic, fn) {
      (this._subs[topic] = this._subs[topic] || []).push(fn);
      return () => { this._subs[topic] = (this._subs[topic] || []).filter(f => f !== fn); };
    },
    emit(topic, data) {
      (this._subs[topic] || []).forEach(fn => { try { fn(data); } catch (e) { console.error(e); } });
      (this._subs['*'] || []).forEach(fn => { try { fn({ topic, data }); } catch (e) { console.error(e); } });
      try { window.dispatchEvent(new CustomEvent('arbiter:ve.' + topic, { detail: data })); } catch {}
    },
  };

  const audit = (action, user, detail, ref) => {
    const entry = { ts: new Date().toISOString(), user: user || 'Author', action, detail: detail || '', ref: ref || null };
    state.auditLog.unshift(entry);
    bus.emit('audit.logged', entry);
    return entry;
  };

  // ── VE1. Apply validation autofix ──
  const applyAutofix = (id, user) => {
    const i = state.validationIssues.find(x => x.id === id);
    if (!i || !i.autofix) return null;
    state.validationIssues = state.validationIssues.filter(x => x.id !== id);
    audit('validation.autofix', user, `Fixed: ${i.title}`, id);
    bus.emit('validation.autofix', i);
    return i;
  };

  // ── VE2. Version time-travel ──
  const revertToVersion = (id, user) => {
    const v = state.versions.find(x => x.id === id);
    if (!v) return null;
    audit('version.revert', user, `Reverted to ${v.label}`, id);
    bus.emit('version.revert', v);
    return v;
  };

  // ── VE3. Collapse to subflow ──
  const collapseSubflow = (name, nodeCount, user) => {
    const id = 'SF-' + name.toLowerCase().replace(/\s+/g, '-').slice(0, 20);
    const sf = { id, name, nodes: nodeCount, runs30d: 0, ownedBy: user || 'Author' };
    state.subflows.unshift(sf);
    audit('subflow.collapse', user, `Collapsed ${nodeCount} nodes → ${name}`, id);
    bus.emit('subflow.collapse', sf);
    return sf;
  };

  // ── VE4. Insert primitive wrapper ──
  const insertPrimitive = (primId, targetStep, user) => {
    const p = state.primitives.find(x => x.id === primId);
    if (!p) return null;
    p.usedIn += 1;
    audit('primitive.insert', user, `${p.name} wrapped ${targetStep}`, primId);
    bus.emit('primitive.insert', p);
    return p;
  };

  // ── VE5. Re-layout by actor swimlane ──
  const relayoutBySwimlane = (user) => {
    audit('layout.swimlane', user, 'Re-flowed canvas into actor swimlanes');
    bus.emit('layout.swimlane', state.swimlanes);
    return state.swimlanes;
  };

  // ── VE6. Prune unused variable ──
  const pruneVariable = (id, user) => {
    const v = state.variables.find(x => x.id === id);
    if (!v) return null;
    state.variables = state.variables.filter(x => x.id !== id);
    audit('variable.prune', user, `Pruned unused ${id}`, id);
    bus.emit('variable.prune', v);
    return v;
  };

  // ── VE7. Bind schema to step ──
  const bindSchema = (id, user) => {
    const s = state.schemas.find(x => x.id === id);
    if (!s) return null;
    s.bound = true;
    audit('schema.bind', user, `Bound ${s.event}@${s.version}`, id);
    bus.emit('schema.bind', s);
    return s;
  };

  // ── VE8. Run test fixture ──
  const runFixture = (id, user) => {
    const f = state.testFixtures.find(x => x.id === id);
    if (!f) return null;
    f.lastRun = new Date().toISOString().slice(0, 16).replace('T', ' ');
    // Re-run — toss a coin but favor existing result
    const pass = Math.random() > 0.15 ? f.passed : !f.passed;
    f.passed = pass;
    f.failed = pass ? 0 : Math.max(1, Math.round(f.asserts * 0.1));
    audit('fixture.run', user, `${f.name}: ${pass ? 'PASS' : 'FAIL'} in ${(f.durMs/1000).toFixed(1)}s`, id);
    bus.emit('fixture.run', f);
    return f;
  };

  // ── VE9. Apply copilot suggestion ──
  const applyCopilot = (id, user) => {
    const c = state.copilotHistory.find(x => x.id === id);
    if (!c) return null;
    c.status = 'applied';
    audit('copilot.apply', user, `Applied: ${c.prompt.slice(0, 48)}`, id);
    bus.emit('copilot.apply', c);
    return c;
  };
  const submitCopilot = (prompt, user) => {
    const id = 'CH-' + (state.copilotHistory.length + 13);
    const entry = {
      id, ts: new Date().toISOString().slice(0, 16).replace('T', ' '),
      user: user || 'Author', prompt, status: 'pending', nodesChanged: 0,
    };
    state.copilotHistory.unshift(entry);
    audit('copilot.submit', user, `Asked: ${prompt.slice(0, 48)}`, id);
    bus.emit('copilot.submit', entry);
    return entry;
  };

  // ── VE10. Apply optimizer suggestion ──
  const applyOptimizer = (id, user) => {
    const o = state.optimizerSuggestions.find(x => x.id === id);
    if (!o) return null;
    state.optimizerSuggestions = state.optimizerSuggestions.filter(x => x.id !== id);
    audit('optimizer.apply', user, `Applied: ${o.rationale.slice(0, 48)}`, id);
    bus.emit('optimizer.apply', o);
    return o;
  };

  // ── VE11. Bind compliance rule ──
  const bindComplianceRule = (id, stepId, user) => {
    const r = state.complianceRules.find(x => x.id === id);
    if (!r) return null;
    if (!r.bindings.includes(stepId)) r.bindings.push(stepId);
    r.status = 'satisfied';
    audit('compliance.bind', user, `${r.id} → ${stepId}`, id);
    bus.emit('compliance.bind', r);
    pushToRulebook({ ruleId: id, stepId });
    return r;
  };

  // ── VE12. Resolve comment ──
  const resolveComment = (id, user) => {
    const c = state.comments.find(x => x.id === id);
    if (!c) return null;
    c.resolved = true;
    audit('comment.resolve', user, `Resolved comment on ${c.step}`, id);
    bus.emit('comment.resolve', c);
    return c;
  };
  const addComment = (step, text, user) => {
    const id = 'C-' + (state.comments.length + 1);
    const c = { id, step, author: user || 'Author', ts: new Date().toISOString().slice(0, 16).replace('T', ' '), resolved: false, text };
    state.comments.unshift(c);
    audit('comment.add', user, `On ${step}: ${text.slice(0, 40)}`, id);
    bus.emit('comment.add', c);
    return c;
  };

  // ── VE13. Approve pipeline stage ──
  const approveStage = (id, user) => {
    const s = state.approvalChain.find(x => x.id === id);
    if (!s || s.status !== 'pending') return null;
    s.status = 'approved';
    s.approver = user || s.approver;
    s.ts = new Date().toISOString().slice(0, 16).replace('T', ' ');
    // Unblock next
    const idx = state.approvalChain.findIndex(x => x.id === id);
    const next = state.approvalChain[idx + 1];
    if (next && next.status === 'blocked') next.status = 'pending';
    audit('pipeline.approve', user, `${s.stage} approved`, id);
    bus.emit('pipeline.approve', s);
    pushToCalendar({ stage: s.stage, ts: s.ts });
    return s;
  };

  // ── VE14. Advance canary ──
  const advanceCanary = (user) => {
    const c = state.canary;
    const map = { 'canary-5': { stage: 'canary-10', pct: 10 }, 'canary-10': { stage: 'canary-25', pct: 25 }, 'canary-25': { stage: 'canary-50', pct: 50 }, 'canary-50': { stage: 'promoted', pct: 100 } };
    const nxt = map[c.stage];
    if (!nxt) return c;
    c.stage = nxt.stage;
    c.pctVariant = nxt.pct;
    audit('canary.advance', user, `Canary → ${nxt.stage} (${nxt.pct}%)`);
    bus.emit('canary.advance', c);
    return c;
  };
  const rollbackCanary = (user) => {
    state.canary.stage = 'rolled-back';
    state.canary.pctVariant = 0;
    audit('canary.rollback', user, 'Canary rolled back to baseline');
    bus.emit('canary.rollback', state.canary);
    return state.canary;
  };

  // ── VE15. Promote variant ──
  const promoteVariant = (id, user) => {
    const v = state.variants.find(x => x.id === id);
    if (!v) return null;
    v.status = 'Promoted';
    v.allocation = 100;
    audit('variant.promote', user, `Promoted ${v.name}`, id);
    bus.emit('variant.promote', v);
    return v;
  };

  // ── VE17. Replay incident ──
  const replayIncident = (id, user) => {
    const i = state.incidents.find(x => x.id === id);
    if (!i) return null;
    audit('incident.replay', user, `Replayed ${i.id} (${i.kind})`, id);
    bus.emit('incident.replay', i);
    return i;
  };

  // ── VE18. Push cost to Billing ──
  const reportCostPeriod = (user) => {
    audit('cost.report', user, `Reported ${state.costLedger.period} cost to Billing`);
    bus.emit('cost.report', state.costLedger);
    pushToBilling({ period: state.costLedger.period, totalUsd: state.costLedger.totalUsd });
    return state.costLedger;
  };

  // ── VE19. Install marketplace drop-in ──
  const installDropIn = (id, user) => {
    const d = state.marketplaceDropIns.find(x => x.id === id);
    if (!d) return null;
    d.installs += 1;
    audit('dropin.install', user, `Installed ${d.name}`, id);
    bus.emit('dropin.install', d);
    return d;
  };

  // ── Cross-platform bridges ──
  const pushToRulebook = (payload) => {
    const entry = { source: 'visualEditor', ...payload, ts: new Date().toISOString() };
    try { window.dispatchEvent(new CustomEvent('arbiter:ve.rulebook.push', { detail: entry })); } catch {}
    bus.emit('rulebook.push', entry);
    return entry;
  };
  const pushToCalendar = (payload) => {
    const entry = { source: 'visualEditor', ...payload, ts: new Date().toISOString() };
    try { window.dispatchEvent(new CustomEvent('arbiter:ve.calendar.push', { detail: entry })); } catch {}
    bus.emit('calendar.push', entry);
    return entry;
  };
  const pushToBilling = (payload) => {
    const entry = { source: 'visualEditor', ...payload, ts: new Date().toISOString() };
    try { window.dispatchEvent(new CustomEvent('arbiter:ve.billing.push', { detail: entry })); } catch {}
    bus.emit('billing.push', entry);
    return entry;
  };

  // ── Listeners (inbound from other platforms) ──
  try {
    window.addEventListener('arbiter:rulebook.updated', (ev) => {
      audit('rulebook.inbound', 'System', `Rulebook updated: ${ev.detail?.ruleId || '?'}`);
    });
    window.addEventListener('arbiter:calendar.deadline.changed', (ev) => {
      audit('calendar.inbound', 'System', `Calendar shifted deadline: ${ev.detail?.eventId || '?'}`);
    });
  } catch {}

  // ── Aggregations ──
  const openIssues      = () => state.validationIssues.length;
  const errorIssues     = () => state.validationIssues.filter(i => i.severity === 'error').length;
  const unusedVars      = () => state.variables.filter(v => v.unused).length;
  const unboundSchemas  = () => state.schemas.filter(s => !s.bound).length;
  const testPassRate    = () => {
    if (!state.testFixtures.length) return 1;
    return state.testFixtures.filter(f => f.passed).length / state.testFixtures.length;
  };
  const pendingApprovals = () => state.approvalChain.filter(s => s.status === 'pending').length;
  const unresolvedComments = () => state.comments.filter(c => !c.resolved).length;
  const complianceCoverage = () => {
    if (!state.complianceRules.length) return 1;
    return state.complianceRules.filter(r => r.status === 'satisfied').length / state.complianceRules.length;
  };
  const costBurnPct     = () => state.costLedger.burnPct || 0;

  const API = {
    state, bus, audit,
    applyAutofix,
    revertToVersion,
    collapseSubflow,
    insertPrimitive,
    relayoutBySwimlane,
    pruneVariable,
    bindSchema,
    runFixture,
    applyCopilot, submitCopilot,
    applyOptimizer,
    bindComplianceRule,
    resolveComment, addComment,
    approveStage,
    advanceCanary, rollbackCanary,
    promoteVariant,
    replayIncident,
    reportCostPeriod,
    installDropIn,
    pushToRulebook, pushToCalendar, pushToBilling,
    openIssues, errorIssues, unusedVars, unboundSchemas, testPassRate,
    pendingApprovals, unresolvedComments, complianceCoverage, costBurnPct,
  };

  window.VisualEditorStore = API;

  window.useVisualEditorStore = (topics) => {
    const topicList = Array.isArray(topics) ? topics : [topics || '*'];
    const [, force] = React.useReducer(x => x + 1, 0);
    React.useEffect(() => {
      const offs = topicList.map(t => bus.on(t, () => force()));
      return () => offs.forEach(off => off());
    }, []);
    return state;
  };
})();
