// DISCOVERY PLATFORM — pub/sub store + API + cross-platform bridges
(function(){

const clone = (x) => JSON.parse(JSON.stringify(x));
const seed = window.DISCOVERY_DATA;

const state = {
  kpis:                       clone(seed.kpis),
  requests:                   clone(seed.requests),
  custodians:                 clone(seed.custodians),
  holds:                      clone(seed.holds),
  collections:                clone(seed.collections),
  processing:                 clone(seed.processing),
  reviewBatches:              clone(seed.reviewBatches),
  reviewers:                  clone(seed.reviewers),
  privilegeLog:               clone(seed.privilegeLog),
  tarModels:                  clone(seed.tarModels),
  productions:                clone(seed.productions),
  depositions:                clone(seed.depositions),
  disputes:                   clone(seed.disputes),
  protocols:                  clone(seed.protocols),
  activity:                   clone(seed.activity),
  // New arrays for the 18 opportunities
  objectionsLibrary:          clone(seed.objectionsLibrary || []),
  requestResponses:           [], // per-request response drafts
  meetConferSessions:         clone(seed.meetConferSessions || []),
  proportionalityAssessments: clone(seed.proportionalityAssessments || []),
  holdTemplates:              clone(seed.holdTemplates || []),
  holdAcknowledgments:        clone(seed.holdAcknowledgments || []),
  custodianInterviews:        clone(seed.custodianInterviews || []),
  chainOfCustody:             clone(seed.chainOfCustody || []),
  processingExceptions:       clone(seed.processingExceptions || []),
  searchTerms:                clone(seed.searchTerms || []),
  ecaSummaries:               clone(seed.ecaSummaries || []),
  reviewDocuments:            clone(seed.reviewDocuments || []),
  privilegeEntries:           clone(seed.privilegeEntries || []),
  tarRounds:                  clone(seed.tarRounds || []),
  tarControlSets:             clone(seed.tarControlSets || []),
  redactionReasons:           clone(seed.redactionReasons || []),
  piiDetections:              clone(seed.piiDetections || []),
  batesSchemes:               clone(seed.batesSchemes || []),
  depoBinders:                clone(seed.depoBinders || []),
  budgetPhases:               clone(seed.budgetPhases || []),
  vendors:                    clone(seed.vendors || []),
  defensibilityEvents:        clone(seed.defensibilityEvents || []),
  auditLog: [],
  savedViews: [
    { id: 'DCV-1', name: 'Overdue requests',              entity: 'requests',   filter: { status: 'Overdue' }, system: true },
    { id: 'DCV-2', name: 'Pending hold acknowledgements', entity: 'holds',      filter: { pendingAcks: true }, system: true },
    { id: 'DCV-3', name: 'Hot docs — Redstone',           entity: 'reviewDocs', filter: { hot: true, matterId: 'M-2024-0312' }, system: true },
    { id: 'DCV-4', name: 'Productions in QC',             entity: 'productions', filter: { status: 'In QC' }, system: true },
  ],
};

const bus = {
  _subs: {},
  on(topic, fn) {
    if (!this._subs[topic]) this._subs[topic] = new Set();
    this._subs[topic].add(fn);
    return () => this._subs[topic].delete(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:dc.' + topic, { detail: data })); } catch (e) {}
  },
};

const recompute = () => {
  const openRequests = state.requests.filter(r => r.status !== 'Responded' && r.status !== 'Supplemented').length;
  const overdue = state.requests.filter(r => r.status === 'Overdue').length;
  const pendingAcks = state.holds.reduce((s, h) => s + Math.max(0, (h.custodianCount || 0) - (h.acknowledged || 0)), 0);
  const collectionJobsActive = state.collections.filter(c => c.status === 'In Progress' || c.status === 'Queued').length;
  const productionsPending = state.productions.filter(p => p.status !== 'Sent').length;
  const productionsShipped = state.productions.filter(p => p.status === 'Sent').length;
  const upcomingDepositions = state.depositions.filter(d => (d.status || '').startsWith('Scheduled')).length;
  const takenDepositions = state.depositions.filter(d => d.status === 'Taken').length;
  const openDisputes = state.disputes.filter(d => d.status !== 'Resolved').length;
  state.kpis = {
    ...state.kpis,
    openRequests,
    overdueResponses: overdue,
    pendingAcks,
    collectionJobsActive,
    productionsPending,
    productionsShipped,
    upcomingDepositions,
    takenDepositions,
    openDisputes,
  };
};

const today = () => new Date().toISOString().slice(0, 10);
const uid = (prefix) => prefix + '-' + Date.now() + '-' + Math.floor(Math.random() * 1000);

const audit = (action, entity, id, meta) => {
  const entry = {
    id: 'AUD-' + Date.now() + '-' + Math.floor(Math.random() * 1000),
    action, entity, id, meta: meta || {},
    at: new Date().toISOString(),
    actor: 'M. Kirkland',
  };
  state.auditLog.unshift(entry);
  if (state.auditLog.length > 500) state.auditLog.length = 500;
  // Also add visible defensibility event for key entity classes
  if (['request','hold','collection','production','privilegeEntry','tarRound','redaction','interview'].includes(entity)) {
    state.defensibilityEvents.unshift({
      id: uid('DEF'),
      matterId: (meta && meta.matterId) || null,
      matter: (meta && meta.matter) || null,
      phase: (meta && meta.phase) || 'Process',
      event: action + ' ' + entity + (id ? ' ' + id : ''),
      at: new Date().toISOString(),
      actor: entry.actor,
      evidence: meta && meta.evidence,
    });
    if (state.defensibilityEvents.length > 300) state.defensibilityEvents.length = 300;
  }
  bus.emit('audit.logged', entry);
};

const addActivity = ({ actor, action, target, severity = 'info' }) => {
  state.activity.unshift({
    id: uid('DC-A'),
    time: 'Just now',
    actor, action, target, severity,
  });
  if (state.activity.length > 100) state.activity.length = 100;
  bus.emit('activity.added', state.activity[0]);
};

const API = {
  // ── OP #1 Request response drawer + objections ──
  addRequest(payload) {
    const id = uid('DR');
    const rec = { id, status: 'Draft', direction: 'Outbound', served: today(), objections: 0, meetConfer: 'Not yet', custodiansScoped: 0, ...payload };
    state.requests.unshift(rec);
    recompute();
    audit('create', 'request', id, { matter: rec.matter, matterId: rec.matterId });
    addActivity({ actor: 'M. Kirkland', action: 'Created request', target: `${rec.type} — ${rec.matter}`, severity: 'info' });
    bus.emit('request.created', rec);
    return rec;
  },
  draftResponse(requestId, payload) {
    const existing = state.requestResponses.find(r => r.requestId === requestId);
    const base = existing || { id: uid('RR'), requestId, createdAt: today(), items: [], status: 'Draft' };
    Object.assign(base, payload);
    if (!existing) state.requestResponses.push(base);
    audit('draft', 'requestResponse', base.id, { requestId });
    bus.emit('requestResponse.drafted', base);
    return base;
  },
  addObjection(requestId, objCode, customText) {
    const rr = state.requestResponses.find(r => r.requestId === requestId)
      || API.draftResponse(requestId, {});
    const obj = state.objectionsLibrary.find(o => o.code === objCode || o.id === objCode);
    const item = { id: uid('OBJ-ENT'), code: obj ? obj.code : objCode, text: (customText || (obj && obj.text) || ''), basis: obj && obj.basis };
    rr.items.push(item);
    const r = state.requests.find(x => x.id === requestId);
    if (r) r.objections = (r.objections || 0) + 1;
    audit('objection.add', 'requestResponse', rr.id, { requestId, code: item.code });
    bus.emit('requestResponse.updated', rr);
    return rr;
  },
  markRequestResponded(requestId, actor) {
    const r = state.requests.find(x => x.id === requestId);
    if (!r) return null;
    r.status = 'Responded';
    r.responsesPreparedBy = actor || r.responsesPreparedBy;
    recompute();
    audit('status', 'request', requestId, { status: 'Responded' });
    bus.emit('request.statusChanged', r);
    return r;
  },

  // ── OP #2 Meet-and-confer planner ──
  scheduleMeetConfer(payload) {
    const id = uid('MC');
    const rec = { id, status: 'Scheduled', attendees: [], agenda: [], outcomes: [], ...payload };
    state.meetConferSessions.unshift(rec);
    audit('create', 'meetConfer', id, { matter: rec.matter, matterId: rec.matterId });
    addActivity({ actor: 'M. Kirkland', action: 'Scheduled meet-and-confer', target: `${rec.matter} — ${rec.scheduled || ''}`, severity: 'info' });
    bus.emit('meetConfer.scheduled', rec);
    return rec;
  },
  closeMeetConfer(id, outcomes) {
    const rec = state.meetConferSessions.find(x => x.id === id);
    if (!rec) return null;
    rec.status = 'Complete';
    rec.outcomes = outcomes || rec.outcomes;
    rec.completedAt = today();
    audit('close', 'meetConfer', id, { matter: rec.matter });
    bus.emit('meetConfer.closed', rec);
    return rec;
  },

  // ── OP #3 Proportionality scoring (FRCP 26(b)(1) 6-factor) ──
  scoreProportionality(payload) {
    const id = uid('PROP');
    const factors = payload.factors || {}; // { importance, amount, access, resources, benefit, burden }  1–5
    const vals = Object.values(factors).filter(v => typeof v === 'number');
    const avg = vals.length ? (vals.reduce((s, v) => s + v, 0) / vals.length) : 0;
    const verdict = avg >= 3.5 ? 'Proportional' : (avg >= 2.5 ? 'Mixed — narrow scope' : 'Disproportionate — object');
    const rec = { id, factors, avg: Math.round(avg * 10) / 10, verdict, createdAt: today(), ...payload };
    state.proportionalityAssessments.unshift(rec);
    audit('create', 'proportionality', id, { requestId: rec.requestId, verdict });
    bus.emit('proportionality.scored', rec);
    return rec;
  },

  // ── OP #4 Hold composer + acknowledgment wall ──
  issueHold(payload) {
    const id = uid('HOLD');
    const rec = {
      id, status: 'Active', issuedDate: today(), custodianCount: 0, acknowledged: 0, acknowledgedPct: 0,
      sources: [], lastAudit: today(), nextAudit: null, issuedBy: 'M. Kirkland',
      ...payload,
    };
    state.holds.unshift(rec);
    recompute();
    audit('create', 'hold', id, { matter: rec.matter, matterId: rec.matterId, custodians: rec.custodianCount });
    addActivity({ actor: rec.issuedBy, action: 'Issued hold', target: `${id} — ${rec.matter}`, severity: 'info' });
    bus.emit('hold.issued', rec);
    return rec;
  },
  ackHold(holdId, custodianId, actor) {
    const h = state.holds.find(x => x.id === holdId);
    if (!h) return null;
    const already = state.holdAcknowledgments.find(a => a.holdId === holdId && a.custodianId === custodianId);
    if (already) return already;
    const rec = { id: uid('ACK'), holdId, custodianId, actor: actor || 'Custodian', ackAt: new Date().toISOString(), method: 'Click-through' };
    state.holdAcknowledgments.push(rec);
    h.acknowledged = (h.acknowledged || 0) + 1;
    h.acknowledgedPct = h.custodianCount ? Math.round((h.acknowledged / h.custodianCount) * 1000) / 10 : 100;
    const c = state.custodians.find(x => x.id === custodianId);
    if (c) c.holdAcked = true;
    recompute();
    audit('ack', 'hold', holdId, { custodianId });
    bus.emit('hold.acknowledged', rec);
    return rec;
  },
  nudgeHold(holdId, custodianIds) {
    audit('nudge', 'hold', holdId, { custodianIds: (custodianIds || []).length });
    addActivity({ actor: 'System', action: 'Sent hold reminder', target: `${holdId} — ${(custodianIds || []).length} custodians`, severity: 'warn' });
    bus.emit('hold.nudged', { holdId, custodianIds });
    return true;
  },

  // ── OP #5 Custodian interview kit ──
  logInterview(payload) {
    const id = uid('INT');
    const rec = { id, completedAt: today(), sources: [], devicesInUse: [], potentialSources: [], ...payload };
    state.custodianInterviews.unshift(rec);
    const c = state.custodians.find(x => x.id === rec.custodianId);
    if (c && !/Interviewed/.test(c.status)) c.status = 'Interviewed · ' + (c.status || '');
    audit('create', 'interview', id, { custodianId: rec.custodianId });
    addActivity({ actor: rec.interviewer || 'M. Kirkland', action: 'Logged custodian interview', target: `${rec.custodianName || rec.custodianId}`, severity: 'info' });
    bus.emit('interview.logged', rec);
    return rec;
  },

  // ── OP #6 Collection wizard + OP #7 Chain of custody ──
  startCollection(payload) {
    const id = uid('COL');
    const rec = { id, status: 'Queued', started: null, eta: payload.eta || null, chainOfCustody: 'Pending', ...payload };
    state.collections.unshift(rec);
    recompute();
    audit('create', 'collection', id, { matter: rec.matter, custodian: rec.custodian });
    addActivity({ actor: 'Collection Ops', action: 'Started collection job', target: `${id} — ${rec.matter}`, severity: 'info' });
    bus.emit('collection.started', rec);
    return rec;
  },
  setCollectionStatus(id, status) {
    const c = state.collections.find(x => x.id === id);
    if (!c) return null;
    c.status = status;
    if (status === 'In Progress' && !c.started) c.started = today();
    if (status === 'Complete') c.chainOfCustody = 'Complete';
    recompute();
    audit('status', 'collection', id, { status });
    bus.emit('collection.statusChanged', c);
    return c;
  },
  addChainEvent(payload) {
    const id = uid('COC');
    const rec = { id, at: new Date().toISOString(), ...payload };
    state.chainOfCustody.unshift(rec);
    audit('create', 'chainOfCustody', id, { collectionId: rec.collectionId, event: rec.event });
    bus.emit('chainOfCustody.added', rec);
    return rec;
  },
  chainForCollection(collectionId) {
    return state.chainOfCustody.filter(c => c.collectionId === collectionId);
  },

  // ── OP #8 ECA hub — search-term builder + elusion estimate ──
  addSearchTerm(payload) {
    const id = uid('ST');
    const rec = { id, hits: payload.hits || 0, families: payload.families || 0, falsePositivePct: null, ...payload };
    state.searchTerms.unshift(rec);
    audit('create', 'searchTerm', id, { term: rec.term, matterId: rec.matterId });
    bus.emit('searchTerm.added', rec);
    return rec;
  },
  runECA(matterId) {
    const terms = state.searchTerms.filter(t => t.matterId === matterId);
    const totalHits = terms.reduce((s, t) => s + (t.hits || 0), 0);
    const totalFamilies = terms.reduce((s, t) => s + (t.families || 0), 0);
    const existing = state.ecaSummaries.find(s => s.matterId === matterId);
    const rec = existing || { id: uid('ECA'), matterId, runs: [] };
    rec.runs.unshift({ at: new Date().toISOString(), termCount: terms.length, hits: totalHits, families: totalFamilies });
    if (!existing) state.ecaSummaries.unshift(rec);
    audit('run', 'eca', rec.id, { matterId, terms: terms.length });
    bus.emit('eca.ran', rec);
    return rec;
  },

  // ── OP #9 Processing exception queue ──
  resolveException(excId, resolution) {
    const e = state.processingExceptions.find(x => x.id === excId);
    if (!e) return null;
    e.status = 'Resolved';
    e.resolution = resolution;
    e.resolvedAt = today();
    if (state.processing && state.processing.errorQueue != null) {
      state.processing.errorQueue = Math.max(0, state.processing.errorQueue - 1);
    }
    audit('resolve', 'processingException', excId, { resolution });
    bus.emit('processingException.resolved', e);
    return e;
  },
  reprocessException(excId) {
    const e = state.processingExceptions.find(x => x.id === excId);
    if (!e) return null;
    e.status = 'Reprocessing';
    e.attempts = (e.attempts || 0) + 1;
    audit('reprocess', 'processingException', excId);
    bus.emit('processingException.reprocessing', e);
    return e;
  },

  // ── OP #10 Review pane — single-document coding ──
  codeDocument(docId, coding, actor) {
    const d = state.reviewDocuments.find(x => x.id === docId);
    if (!d) return null;
    d.codes = { ...(d.codes || {}), ...coding };
    d.codedAt = new Date().toISOString();
    d.codedBy = actor || 'M. Kirkland';
    audit('code', 'reviewDocument', docId, { coding });
    bus.emit('reviewDocument.coded', d);
    return d;
  },

  // ── OP #11 Privilege log builder ──
  addPrivEntry(payload) {
    const id = uid('PRV');
    const rec = { id, createdAt: today(), status: 'Draft', ...payload };
    state.privilegeEntries.unshift(rec);
    audit('create', 'privilegeEntry', id, { matterId: rec.matterId, bates: rec.bates });
    bus.emit('privilegeEntry.added', rec);
    return rec;
  },
  setPrivStatus(id, status) {
    const p = state.privilegeEntries.find(x => x.id === id);
    if (!p) return null;
    p.status = status;
    audit('status', 'privilegeEntry', id, { status });
    bus.emit('privilegeEntry.statusChanged', p);
    return p;
  },

  // ── OP #12 TAR validation ──
  runTARRound(payload) {
    const id = uid('TARR');
    const rec = { id, runAt: new Date().toISOString(), ...payload };
    state.tarRounds.unshift(rec);
    const model = state.tarModels.find(m => m.id === rec.modelId);
    if (model) {
      if (rec.precision != null) model.precision = rec.precision;
      if (rec.recall != null) model.recall = rec.recall;
      if (rec.f1 != null) model.f1 = rec.f1;
      model.trainingRounds = (model.trainingRounds || 0) + 1;
      model.lastRun = today();
    }
    audit('run', 'tarRound', id, { modelId: rec.modelId });
    addActivity({ actor: 'System', action: 'TAR round complete', target: `${rec.modelId} round ${rec.roundNumber || ''} — recall ${rec.recall || '-'}%`, severity: 'info' });
    bus.emit('tarRound.ran', rec);
    return rec;
  },
  addControlSet(payload) {
    const id = uid('CSET');
    const rec = { id, createdAt: today(), ...payload };
    state.tarControlSets.unshift(rec);
    audit('create', 'tarControlSet', id, { modelId: rec.modelId });
    bus.emit('tarControlSet.added', rec);
    return rec;
  },

  // ── OP #13 Redaction studio ──
  addRedaction(payload) {
    const { docId, reasonCode } = payload;
    const d = state.reviewDocuments.find(x => x.id === docId);
    if (!d) return null;
    d.redactions = d.redactions || [];
    const id = uid('RED');
    const reason = state.redactionReasons.find(r => r.code === reasonCode);
    const rec = { id, reasonCode, reasonLabel: reason && reason.label, at: new Date().toISOString(), actor: payload.actor || 'M. Kirkland', box: payload.box || null };
    d.redactions.push(rec);
    audit('redact', 'reviewDocument', docId, { reasonCode });
    bus.emit('redaction.added', { doc: d, redaction: rec });
    return rec;
  },
  acceptPiiDetection(detId) {
    const det = state.piiDetections.find(x => x.id === detId);
    if (!det) return null;
    det.status = 'Accepted';
    det.acceptedAt = today();
    if (det.docId) API.addRedaction({ docId: det.docId, reasonCode: det.reasonCode, actor: 'Auto-PII' });
    audit('accept', 'piiDetection', detId);
    bus.emit('piiDetection.accepted', det);
    return det;
  },

  // ── OP #14 Production builder ──
  buildProduction(payload) {
    const id = uid('PROD');
    const scheme = state.batesSchemes.find(s => s.matterId === payload.matterId);
    const startNum = scheme ? scheme.nextNumber : 1;
    const count = payload.docCount || 0;
    const endNum = startNum + count - 1;
    const prefix = scheme ? scheme.prefix : 'DOC';
    const pad = scheme ? scheme.pad : 6;
    const batesRange = `${prefix}-${String(startNum).padStart(pad, '0')} — ${prefix}-${String(endNum).padStart(pad, '0')}`;
    const rec = {
      id,
      matter: payload.matter,
      matterId: payload.matterId,
      batesRange,
      docCount: count,
      volumeGB: payload.volumeGB || 0,
      loadFormat: payload.loadFormat || 'Relativity (OPT + DAT)',
      redactions: payload.redactions || 0,
      privLog: payload.privLog !== false,
      status: 'In Prep',
      clientApproval: 'Not started',
      sentDate: null,
      reviewedBy: payload.reviewedBy || 'M. Kirkland',
      confidentialityStamps: payload.confidentialityStamps || (scheme && scheme.confidentialityStamps) || [],
      preparedAt: today(),
    };
    state.productions.unshift(rec);
    if (scheme) scheme.nextNumber = endNum + 1;
    recompute();
    audit('build', 'production', id, { matterId: rec.matterId, docs: count });
    addActivity({ actor: rec.reviewedBy, action: 'Built production', target: `${id} — ${rec.matter} (${count.toLocaleString()} docs)`, severity: 'info' });
    bus.emit('production.built', rec);
    return rec;
  },
  shipProduction(id, sentDate) {
    const p = state.productions.find(x => x.id === id);
    if (!p) return null;
    p.status = 'Sent';
    p.sentDate = sentDate || today();
    p.clientApproval = 'Approved';
    recompute();
    audit('ship', 'production', id, { sentDate: p.sentDate });
    addActivity({ actor: 'System', action: 'Production shipped', target: `${id} — ${p.matter}`, severity: 'info' });
    bus.emit('production.shipped', p);
    return p;
  },

  // ── OP #15 Deposition binder ──
  getDepoBinder(depoId) {
    let b = state.depoBinders.find(x => x.depoId === depoId);
    if (!b) {
      b = { id: uid('DB'), depoId, outline: [], hotDocs: [], priorStatements: [], proposedExhibitStart: 1, createdAt: today() };
      state.depoBinders.unshift(b);
    }
    return b;
  },
  addDepoExhibit(depoId, doc) {
    const b = API.getDepoBinder(depoId);
    const exhNumber = (b.proposedExhibitStart || 1) + (b.hotDocs || []).length;
    b.hotDocs.push({ ...doc, exhibitNumber: exhNumber, addedAt: new Date().toISOString() });
    audit('add', 'depoExhibit', b.id, { depoId, docId: doc.id });
    bus.emit('depoBinder.updated', b);
    return b;
  },
  addDepoOutlineItem(depoId, item) {
    const b = API.getDepoBinder(depoId);
    b.outline.push({ id: uid('OL'), ...item });
    audit('outline', 'depoBinder', b.id, { depoId });
    bus.emit('depoBinder.updated', b);
    return b;
  },

  // ── OP #16 Budget hub ──
  logBudget(phase, delta, meta) {
    const p = state.budgetPhases.find(x => x.phase === phase);
    if (!p) return null;
    p.spent = (p.spent || 0) + (delta || 0);
    p.variance = (p.allocated || 0) - (p.forecast || p.spent);
    audit('budget.log', 'phase', phase, { delta, meta });
    bus.emit('budget.logged', { phase: p, delta });
    return p;
  },
  budgetSummary() {
    const total = state.budgetPhases.reduce((a, p) => ({
      allocated: a.allocated + (p.allocated || 0),
      spent: a.spent + (p.spent || 0),
      forecast: a.forecast + (p.forecast || 0),
    }), { allocated: 0, spent: 0, forecast: 0 });
    total.variance = total.allocated - total.forecast;
    total.burnPct = total.allocated ? Math.round((total.spent / total.allocated) * 1000) / 10 : 0;
    return total;
  },

  // ── OP #17 Defensibility timeline ──
  logDefensibility(payload) {
    const id = uid('DEF');
    const rec = { id, at: new Date().toISOString(), actor: 'M. Kirkland', ...payload };
    state.defensibilityEvents.unshift(rec);
    if (state.defensibilityEvents.length > 300) state.defensibilityEvents.length = 300;
    bus.emit('defensibility.logged', rec);
    return rec;
  },
  defensibilityByMatter(matterId) {
    return state.defensibilityEvents.filter(e => e.matterId === matterId);
  },

  // ── OP #18 Saved views ──
  saveView(view) {
    const id = uid('DCV');
    const sv = { id, system: false, ...view };
    state.savedViews.push(sv);
    audit('create', 'savedView', id, { name: sv.name });
    bus.emit('view.saved', sv);
    return sv;
  },
  deleteView(id) {
    const v = state.savedViews.find(x => x.id === id);
    if (!v || v.system) return false;
    state.savedViews = state.savedViews.filter(x => x.id !== id);
    audit('delete', 'savedView', id);
    bus.emit('view.deleted', { id });
    return true;
  },

  // ── Cross-entity queries ──
  custodiansForMatter(matterId) { return state.custodians.filter(c => c.matterId === matterId); },
  holdsForMatter(matterId)      { return state.holds.filter(h => h.matterId === matterId); },
  requestsForMatter(matterId)   { return state.requests.filter(r => r.matterId === matterId); },
  productionsForMatter(matterId){ return state.productions.filter(p => p.matter && state.requests.find(r => r.matter === p.matter && r.matterId === matterId)); },
  interviewForCustodian(custodianId) { return state.custodianInterviews.find(i => i.custodianId === custodianId); },
  acksForHold(holdId)           { return state.holdAcknowledgments.filter(a => a.holdId === holdId); },
  unackedCustodians(holdId) {
    const h = state.holds.find(x => x.id === holdId);
    if (!h) return [];
    const acked = new Set(state.holdAcknowledgments.filter(a => a.holdId === holdId).map(a => a.custodianId));
    return state.custodians.filter(c => c.matterId === h.matterId && !acked.has(c.id));
  },

  // ── Snapshot / exports ──
  snapshot() { return clone(state); },
  state,
  bus,
};

window.DiscoveryStore = API;

// React hook — re-renders on bus events (topic list or '*')
window.useDcStore = (topics) => {
  const [, force] = React.useReducer(x => x + 1, 0);
  React.useEffect(() => {
    const list = topics && topics.length ? topics : ['*'];
    const offs = list.map(t => bus.on(t, () => force()));
    return () => offs.forEach(off => off && off());
  }, []);
  return state;
};

// ── Cross-platform bridges ──
// 1. A matter opens → emit discovery context (open requests, holds, productions)
window.addEventListener('arbiter:matter.opened', (ev) => {
  const m = ev.detail;
  if (!m || !m.id) return;
  const context = {
    matterId: m.id,
    requests:    API.requestsForMatter(m.id),
    holds:       API.holdsForMatter(m.id),
    custodians:  API.custodiansForMatter(m.id),
    productions: state.productions.filter(p => p.matter === m.name),
  };
  bus.emit('matter.discovery.context', context);
});

// 2. A jurisdictions deadline lands on a discovery request → flag urgency
window.addEventListener('arbiter:jx.docket.deadline.enriched', (ev) => {
  const d = ev.detail && ev.detail.deadline;
  if (!d || !d.requestId) return;
  const r = state.requests.find(x => x.id === d.requestId);
  if (r) bus.emit('request.deadline.linked', { request: r, deadline: d, rules: ev.detail.applicableRules });
});

// 3. A task with category='Discovery' → broadcast into activity stream
window.addEventListener('arbiter:task.created', (ev) => {
  const t = ev.detail;
  if (!t || t.category !== 'Discovery') return;
  addActivity({ actor: t.assignee || 'Tasks', action: 'Linked task', target: t.title || t.id, severity: 'info' });
});

// Seed defensibility timeline with visible historical events from existing data
state.holds.forEach(h => {
  state.defensibilityEvents.push({
    id: uid('DEF'),
    matterId: h.matterId, matter: h.matter, phase: 'Preserve',
    event: `Hold ${h.id} issued — ${h.custodianCount} custodians`,
    at: h.issuedDate + 'T09:00:00Z',
    actor: h.issuedBy,
    evidence: `Scope: ${h.scope}`,
  });
});
state.productions.filter(p => p.sentDate).forEach(p => {
  state.defensibilityEvents.push({
    id: uid('DEF'),
    matterId: null, matter: p.matter, phase: 'Produce',
    event: `Production ${p.id} shipped — ${p.docCount.toLocaleString()} docs`,
    at: p.sentDate + 'T15:00:00Z',
    actor: p.reviewedBy,
    evidence: `Bates: ${p.batesRange}`,
  });
});
state.defensibilityEvents.sort((a, b) => {
  const ak = a.at || a.date || '';
  const bk = b.at || b.date || '';
  return ak < bk ? 1 : -1;
});

recompute();

})();
