// DOC PLATFORM — event bus, mutable store, extended enterprise data
// Cross-platform coordination: emits window CustomEvents ("arbiter:doc:*")
// and internal bus topics. Any platform can subscribe.
(function () {
  const T_ds = window.ArbiterTokens;

  // ── EVENT BUS ────────────────────────────────────────────────────────────
  const DocBus = {
    _subs: new Map(),
    on(topic, fn) {
      if (!this._subs.has(topic)) this._subs.set(topic, new Set());
      this._subs.get(topic).add(fn);
      return () => { const s = this._subs.get(topic); if (s) s.delete(fn); };
    },
    emit(topic, data) {
      (this._subs.get(topic) || new Set()).forEach(fn => { try { fn(data); } catch (e) { console.error(e); } });
      (this._subs.get('*') || new Set()).forEach(fn => { try { fn({ topic, data }); } catch (e) { console.error(e); } });
      try { window.dispatchEvent(new CustomEvent('arbiter:' + topic, { detail: data })); } catch (e) {}
    },
  };
  window.DocBus = DocBus;

  // ── EXTENDED DATA ────────────────────────────────────────────────────────

  // Custodian detail for legal holds (improvement #11)
  const DOC_CUSTODIANS = [
    { id: 'CU-1',  name: 'M. Kirkland',   email: 'mkirkland@firm.com',    role: 'Partner',          holdId: 'LH-001', ack: true,  ackDate: '2024-04-13', questionnaire: 'Complete', reminders: 0,  lastReminder: '2026-04-01' },
    { id: 'CU-2',  name: 'L. Torres',     email: 'ltorres@firm.com',       role: 'Associate',        holdId: 'LH-001', ack: true,  ackDate: '2024-04-14', questionnaire: 'Complete', reminders: 0,  lastReminder: '2026-04-01' },
    { id: 'CU-3',  name: 'J. Park',       email: 'jpark@firm.com',         role: 'Associate',        holdId: 'LH-001', ack: true,  ackDate: '2024-04-15', questionnaire: 'Partial',  reminders: 2,  lastReminder: '2026-04-08' },
    { id: 'CU-4',  name: 'T. Nakamura',   email: 'tnakamura@redstone.com', role: 'Client contact',   holdId: 'LH-001', ack: false, ackDate: null,         questionnaire: 'Pending',  reminders: 4,  lastReminder: '2026-04-10' },
    { id: 'CU-5',  name: 'D. Redstone',   email: 'dredstone@redstone.com', role: 'Client (CEO)',     holdId: 'LH-001', ack: true,  ackDate: '2024-04-20', questionnaire: 'Complete', reminders: 1,  lastReminder: '2026-03-15' },
    { id: 'CU-6',  name: 'A. Kessler',    email: 'akessler@redstone.com',  role: 'Client (IT)',      holdId: 'LH-001', ack: false, ackDate: null,         questionnaire: 'Pending',  reminders: 5,  lastReminder: '2026-04-12' },
    { id: 'CU-7',  name: 'S. Chen',       email: 'schen@firm.com',         role: 'Partner',          holdId: 'LH-002', ack: true,  ackDate: '2024-03-09', questionnaire: 'Complete', reminders: 0,  lastReminder: '2026-03-15' },
    { id: 'CU-8',  name: 'B. Okafor',     email: 'bokafor@pacshipping.com',role: 'Client (GC)',      holdId: 'LH-002', ack: true,  ackDate: '2024-03-11', questionnaire: 'Complete', reminders: 1,  lastReminder: '2026-02-20' },
    { id: 'CU-9',  name: 'R. Vasquez',    email: 'rvasquez@firm.com',      role: 'Senior Associate', holdId: 'LH-005', ack: true,  ackDate: '2023-11-06', questionnaire: 'Complete', reminders: 0,  lastReminder: '2026-02-20' },
    { id: 'CU-10', name: 'A. Petrov',     email: 'apetrov@firm.com',       role: 'Partner',          holdId: 'LH-006', ack: true,  ackDate: '2024-09-16', questionnaire: 'Complete', reminders: 0,  lastReminder: '2026-04-05' },
    { id: 'CU-11', name: 'K. Adebayo',    email: 'kadebayo@atlasfin.com',  role: 'Client (CFO)',     holdId: 'LH-006', ack: false, ackDate: null,         questionnaire: 'Pending',  reminders: 6,  lastReminder: '2026-04-15' },
  ];

  // eSign envelopes (improvement #6)
  const DOC_ENVELOPES = [
    { id: 'EV-1', docId: 13, docName: 'Merger Agreement — NovaTech/Apex',      status: 'Completed', sent: '2026-03-28 09:15', completed: '2026-03-30 14:22', createdBy: 'A. Petrov',   signers: [ { name: 'D. Havel (NovaTech CEO)', status: 'Signed', when: '2026-03-29 11:04' }, { name: 'P. Ashford (Apex CEO)', status: 'Signed', when: '2026-03-30 14:22' }, { name: 'A. Petrov (Witness)', status: 'Signed', when: '2026-03-30 14:22' } ], reminders: 1 },
    { id: 'EV-2', docId: 9,  docName: 'Settlement Offer — Atlas Financial',     status: 'Awaiting', sent: '2026-04-19 15:30', completed: null,                 createdBy: 'A. Petrov',   signers: [ { name: 'K. Adebayo (Atlas CFO)', status: 'Pending', when: null }, { name: 'Atlas GC', status: 'Pending', when: null } ], reminders: 2 },
    { id: 'EV-3', docId: null, docName: 'Engagement Letter — Harbor District', status: 'Awaiting', sent: '2026-04-20 11:12', completed: null,                 createdBy: 'R. Vasquez',  signers: [ { name: 'Harbor Dist. Board Chair', status: 'Viewed', when: '2026-04-20 13:40' }, { name: 'R. Vasquez', status: 'Signed', when: '2026-04-20 11:18' } ], reminders: 1 },
    { id: 'EV-4', docId: null, docName: 'NDA — Sterling witness',              status: 'Completed', sent: '2026-04-10 09:00', completed: '2026-04-11 16:45', createdBy: 'S. Chen',     signers: [ { name: 'J. Ahmadi', status: 'Signed', when: '2026-04-11 16:45' }, { name: 'S. Chen', status: 'Signed', when: '2026-04-10 09:02' } ], reminders: 0 },
    { id: 'EV-5', docId: null, docName: 'Expert retention — Dr. Patel',        status: 'Declined', sent: '2026-04-02 10:00',  completed: '2026-04-04 09:15',  createdBy: 'R. Vasquez',  signers: [ { name: 'Dr. N. Patel', status: 'Declined', when: '2026-04-04 09:15' } ], reminders: 2 },
  ];

  // Trash / recycle bin (improvement #7)
  const DOC_TRASH = [
    { id: 'TR-1', docId: 9001, name: 'Opposition to MSJ — Draft v2 (superseded)', type: 'Brief',       matter: 'Redstone v. Meridian', size: '2.1 MB', ext: 'DOCX', deletedBy: 'M. Kirkland', deletedAt: '2026-04-18 14:22', purgeAt: '2026-05-18', reason: 'Superseded by v3' },
    { id: 'TR-2', docId: 9002, name: 'Initial engagement notes',                   type: 'Memo',        matter: 'Chen v. Atlas',        size: '84 KB',  ext: 'DOCX', deletedBy: 'A. Petrov',   deletedAt: '2026-04-15 11:08', purgeAt: '2026-05-15', reason: 'Consolidated' },
    { id: 'TR-3', docId: 9003, name: 'Old privilege log — draft',                  type: 'Log',         matter: 'Firm-wide',            size: '1.8 MB', ext: 'XLSX', deletedBy: 'J. Park',     deletedAt: '2026-04-11 17:00', purgeAt: '2026-05-11', reason: 'Outdated' },
    { id: 'TR-4', docId: 9004, name: 'Vendor invoice scan (dup)',                  type: 'Evidence',    matter: 'Sterling Pharma',      size: '220 KB', ext: 'PDF',  deletedBy: 'S. Chen',     deletedAt: '2026-04-09 10:15', purgeAt: '2026-05-09', reason: 'Duplicate upload' },
    { id: 'TR-5', docId: 9005, name: 'Draft declaration — Harmon (rejected)',      type: 'Evidence',    matter: 'Pacific Shipping',     size: '340 KB', ext: 'DOCX', deletedBy: 'L. Torres',   deletedAt: '2026-04-05 09:42', purgeAt: '2026-05-05', reason: 'Client-rejected' },
  ];

  // Pinned org-wide (improvement #9 — distinct from starred/recent)
  const DOC_PINNED = [
    { docId: 3,  pinnedBy: 'M. Kirkland', pinnedAt: '2026-04-03', note: 'Trial-critical exhibit' },
    { docId: 15, pinnedBy: 'J. Park',     pinnedAt: '2026-04-12', note: 'Firm-wide reference' },
    { docId: 13, pinnedBy: 'A. Petrov',   pinnedAt: '2026-03-28', note: 'Pending regulatory review' },
  ];

  const DOC_RECENTLY_VIEWED = [
    { docId: 1,  viewedAt: '2026-04-21 10:42', user: 'M. Kirkland' },
    { docId: 17, viewedAt: '2026-04-21 10:20', user: 'M. Kirkland' },
    { docId: 9,  viewedAt: '2026-04-21 09:55', user: 'M. Kirkland' },
    { docId: 8,  viewedAt: '2026-04-21 09:14', user: 'M. Kirkland' },
    { docId: 3,  viewedAt: '2026-04-20 16:48', user: 'M. Kirkland' },
    { docId: 14, viewedAt: '2026-04-20 15:20', user: 'M. Kirkland' },
    { docId: 2,  viewedAt: '2026-04-20 14:03', user: 'M. Kirkland' },
  ];

  // Doc→Doc relationships (improvement #10 — related docs graph)
  const DOC_RELATIONSHIPS = [
    { from: 1,  to: 2,  type: 'cites',       note: 'Cites depo transcript Harrington' },
    { from: 1,  to: 3,  type: 'cites',       note: 'Relies on Mitchell damages report' },
    { from: 1,  to: 17, type: 'parent-of',   note: 'Contains responses referenced in Set 3' },
    { from: 1,  to: 16, type: 'supersedes',  note: 'Expands arguments raised in Motion to Compel' },
    { from: 3,  to: 2,  type: 'cites',       note: 'Mitchell references Harrington admissions' },
    { from: 8,  to: 19, type: 'parent-of',   note: 'Claim chart referenced in source-code brief' },
    { from: 9,  to: 20, type: 'reply-to',    note: 'Mediation statement responds to offer' },
    { from: 4,  to: 5,  type: 'attached-to', note: 'Exhibit A to complaint' },
    { from: 4,  to: 18, type: 'relates-to',  note: 'Depo outline targets Harmon (named in complaint)' },
    { from: 13, to: 10, type: 'relates-to',  note: 'FCPA concerns raised during Apex diligence' },
    { from: 14, to: 1,  type: 'relates-to',  note: 'Same firm MSJ patterns' },
  ];

  // AI insights (improvement #17)
  const DOC_AI_INSIGHTS = {
    1: {
      summary: 'Opposition brief arguing that defendants\' summary-judgment motion should be denied. Relies heavily on Harrington deposition admissions and Mitchell\'s $14.2M-$18.7M damages model. 47 pages.',
      keyPoints: ['Section III argument: duty of disclosure survived the 2019 amendment', 'Mitchell methodology withstands Daubert challenge', 'Harrington admissions at pp.87-92 contradict defendants\' narrative', 'Damages range backed by 3 independent models'],
      autoTags: ['MSJ','opposition','damages','Daubert','Harrington'],
      piiFlags: [],
      duplicates: [],
      similarDocs: [14, 19, 16],
      clauses: [],
      readTimeMin: 32,
    },
    3: {
      summary: 'Expert damages report by Dr. Mitchell. Establishes a range of $14.2M-$18.7M in economic losses using a three-model approach (lost profits, diminished firm value, lost opportunities).',
      keyPoints: ['Three-model convergence strengthens reliability','Daubert-proof methodology','Uses industry-standard discount rate (8.4%)','Addresses mitigation defense preemptively'],
      autoTags: ['expert','damages','Mitchell','$14.2M-$18.7M','Daubert'],
      piiFlags: [{ type: 'SSN', count: 0 }, { type: 'DOB', count: 2, note: 'Witness DOB referenced twice' }],
      duplicates: [],
      similarDocs: [1, 12],
      clauses: [],
      readTimeMin: 58,
    },
    13: {
      summary: 'Executed merger agreement between NovaTech and Apex. Includes MAC clause, reverse break fee, and 30-day regulatory window.',
      keyPoints: ['MAC clause triggers on ≥15% revenue decline','Reverse break fee: $12M payable by NovaTech','Regulatory out after 30 days','Standard rep-and-warranty insurance carveout'],
      autoTags: ['merger','MAC','break-fee','regulatory'],
      piiFlags: [],
      duplicates: [],
      similarDocs: [],
      clauses: [
        { name: 'Governing Law',             text: 'Delaware', risk: 'low' },
        { name: 'Material Adverse Change',   text: '≥15% revenue decline over any trailing 2Q', risk: 'medium' },
        { name: 'Reverse break fee',          text: '$12M payable by NovaTech on regulatory failure', risk: 'high' },
        { name: 'Regulatory termination right',text: 'Either party may terminate after 30 days', risk: 'medium' },
        { name: 'Rep/warranty insurance',     text: 'Buyer-side RWI; $50M tower', risk: 'low' },
      ],
      readTimeMin: 84,
    },
    17: {
      summary: 'Draft interrogatory responses to Set 3. Partner review required by tomorrow.',
      keyPoints: ['Responses to Interrogatories 1-12','Asserts privilege on 3 responses','Due Apr 21 — partner review urgent'],
      autoTags: ['interrogatory','Set 3','privilege','urgent'],
      piiFlags: [{ type: 'SSN', count: 1, note: 'Found in response #7 — redact before service' }],
      duplicates: [],
      similarDocs: [1],
      clauses: [],
      readTimeMin: 18,
    },
    10: {
      summary: 'Draft FCPA compliance memo for Sterling. Incorporates Q1 2026 findings on Southeast Asia operations.',
      keyPoints: ['Three potentially problematic transactions flagged','Recommendation: voluntary disclosure','Self-report window closing Q2'],
      autoTags: ['FCPA','compliance','self-disclosure','Sterling'],
      piiFlags: [],
      duplicates: [{ docId: 11, confidence: 0.34, overlap: 'Shared exhibits from Q3 audit' }],
      similarDocs: [11],
      clauses: [],
      readTimeMin: 24,
    },
  };

  // Upload velocity (30d, per day) — improvement #18
  const DOC_VELOCITY = (() => {
    const days = [];
    const end = new Date('2026-04-21');
    for (let i = 29; i >= 0; i--) {
      const d = new Date(end); d.setDate(d.getDate() - i);
      const dow = d.getDay();
      const base = dow === 0 || dow === 6 ? 3 : 14;
      const jitter = Math.round(Math.sin(i * 0.7) * 4 + (i % 5));
      days.push({ date: d.toISOString().slice(0, 10), uploads: Math.max(0, base + jitter), edits: Math.max(0, base * 2 + jitter * 2) });
    }
    return days;
  })();

  // Stale documents (not opened in 180+ days) — improvement #18
  const DOC_STALE = [
    { docId: 6,  name: 'Thornton Will — Original 2018',          matter: 'Thornton Estate',  lastOpened: '2024-10-02', daysStale: 567, size: '340 KB', recommendation: 'Archive — matter closed' },
    { docId: 12, name: 'Eminent Domain Valuation Report',        matter: 'Harbor District',  lastOpened: '2025-09-14', daysStale: 220, size: '6.7 MB', recommendation: 'Archive' },
    { docId: 15, name: 'Privilege Log — All Matters Q1',         matter: 'Firm-wide',        lastOpened: '2025-11-02', daysStale: 171, size: '2.1 MB', recommendation: 'Review — may still be referenced' },
    { docId: 16, name: 'Motion to Compel — Brief',                matter: 'Redstone',         lastOpened: '2025-10-22', daysStale: 182, size: '1.1 MB', recommendation: 'Archive — filed and granted' },
    { docId: 14, name: 'Class Certification Motion',             matter: 'Greenfield',       lastOpened: '2025-09-30', daysStale: 204, size: '1.9 MB', recommendation: 'Archive — filed' },
  ];

  // Duplicate detection (improvement #17)
  const DOC_DUPLICATES = [
    { group: 'DG-1', docIds: [10, 11], confidence: 0.34, reason: 'Overlapping Q3 2025 audit exhibits' },
    { group: 'DG-2', docIds: [1, 16],  confidence: 0.28, reason: 'Shared argument templates' },
  ];

  // Storage cost by matter (improvement #18)
  const DOC_COST_BY_MATTER = [
    { matter: 'Redstone v. Meridian',      gb: 8.4, costMo: 42.00 },
    { matter: 'Pacific Shipping Antitrust', gb: 5.2, costMo: 26.00 },
    { matter: 'Sterling Pharma',            gb: 4.7, costMo: 23.50 },
    { matter: 'Greenfield Environmental',   gb: 3.6, costMo: 18.00 },
    { matter: 'NovaTech Merger',            gb: 2.1, costMo: 10.50 },
    { matter: 'Thornton Estate',            gb: 1.5, costMo:  7.50 },
    { matter: 'Blackwell IP',               gb: 1.2, costMo:  6.00 },
    { matter: 'Chen v. Atlas',              gb: 0.9, costMo:  4.50 },
    { matter: 'Other',                      gb: 1.1, costMo:  5.50 },
  ];

  // Matter activity heatmap (improvement #18): 7 days × 24 hours
  const DOC_HEATMAP = (() => {
    const rows = [];
    for (let d = 0; d < 7; d++) {
      const hours = [];
      for (let h = 0; h < 24; h++) {
        const business = (d >= 1 && d <= 5) && (h >= 8 && h <= 19);
        const late     = (d >= 1 && d <= 5) && (h >= 20 || h <= 2);
        const val = business ? Math.round(8 + Math.sin((d + h) * 0.9) * 4 + (h === 10 || h === 14 ? 6 : 0)) : late ? Math.round(2 + Math.sin(h) * 2) : Math.round(Math.max(0, Math.sin(d + h) * 2));
        hours.push(Math.max(0, val));
      }
      rows.push(hours);
    }
    return rows;
  })();

  // Confidentiality trend (improvement #18)
  const DOC_CONFID_TREND = [
    { month: '2025-11', Public: 14, Internal: 28, Confidential: 42, 'Highly Confidential': 12, 'Work Product': 8 },
    { month: '2025-12', Public: 12, Internal: 31, Confidential: 48, 'Highly Confidential': 14, 'Work Product': 9 },
    { month: '2026-01', Public: 18, Internal: 34, Confidential: 52, 'Highly Confidential': 16, 'Work Product': 11 },
    { month: '2026-02', Public: 16, Internal: 30, Confidential: 58, 'Highly Confidential': 19, 'Work Product': 13 },
    { month: '2026-03', Public: 22, Internal: 36, Confidential: 61, 'Highly Confidential': 22, 'Work Product': 15 },
    { month: '2026-04', Public: 19, Internal: 38, Confidential: 68, 'Highly Confidential': 24, 'Work Product': 17 },
  ];

  // Share risk scoring (improvement #15)
  const DOC_SHARE_RISK = {
    'SH-1': { score: 18, factors: ['Known recipient domain','Expiring in >30d','2 views recorded'] },
    'SH-2': { score: 12, factors: ['Known recipient','Public-facing','View-only'] },
    'SH-3': { score: 68, factors: ['Comment-scope enables exfiltration','3 downloads in 48h (unusual)','Off-hours access 02:14 UTC'] },
    'SH-4': { score: 24, factors: ['No expiry','Family member recipient','View-only'] },
    'SH-5': { score:  5, factors: ['Public filing','No sensitive data'] },
    'SH-6': { score: 82, factors: ['Opposing counsel','Highly Confidential content','Expires tomorrow — extend or revoke'] },
  };

  // Conflicts (improvement #5)
  const DOC_CONFLICTS = [
    { id: 'CF-1', docId: 1, docName: 'Opposition to MSJ — Draft v3', detected: '2026-04-20 16:12', branchA: { user: 'M. Kirkland', change: 'Edits to Section III.B' }, branchB: { user: 'L. Torres', change: 'Added citation at p.87' }, severity: 'Medium', status: 'Unresolved' },
    { id: 'CF-2', docId: 8, docName: 'Patent Claim Chart — Blackwell', detected: '2026-04-19 14:40', branchA: { user: 'J. Park', change: 'Added prior-art row 27' }, branchB: { user: 'M. Kirkland', change: 'Reordered rows 14-20' }, severity: 'Low', status: 'Unresolved' },
    { id: 'CF-3', docId: 17, docName: 'Interrogatory Responses — Set 3', detected: '2026-04-20 09:18', branchA: { user: 'L. Torres', change: 'Response #7 revised' }, branchB: { user: 'M. Kirkland', change: 'Response #7 privilege objection added' }, severity: 'High', status: 'Unresolved' },
  ];

  // Saved searches (improvement #8) — now stateful
  const DOC_SAVED_SEARCHES_V2 = [
    { id: 'SS-1', name: 'My drafts',             query: 'status:Draft author:me',    resultCount: 3,  owner: 'M. Kirkland', icon: '•' },
    { id: 'SS-2', name: 'Redstone hot docs',     query: 'matter:Redstone tag:hot',    resultCount: 3,  owner: 'shared',      icon: '◆' },
    { id: 'SS-3', name: 'Due this week',         query: 'due:<=7d',                   resultCount: 5,  owner: 'M. Kirkland', icon: 'flag' },
    { id: 'SS-4', name: 'Highly Confidential',   query: 'confidentiality:"Highly Confidential"', resultCount: 4, owner: 'shared', icon: '◈' },
    { id: 'SS-5', name: 'Filed this month',      query: 'status:Filed modified:month', resultCount: 3, owner: 'shared',      icon: '▦' },
    { id: 'SS-6', name: 'Under hold',             query: 'hold:active',                resultCount: 7,  owner: 'shared',      icon: '◇' },
    { id: 'SS-7', name: 'Needs my approval',      query: 'approver:me status:Pending', resultCount: 3,  owner: 'M. Kirkland', icon: 'ok' },
  ];

  // ── CENTRAL STORE ───────────────────────────────────────────────────────
  const state = {
    docs:              window.DOC_ITEMS,
    approvals:         window.DOC_APPROVALS,
    shares:            window.DOC_SHARES,
    holds:             window.DOC_LEGAL_HOLDS,
    retention:         window.DOC_RETENTION_POLICIES,
    audit:             [...window.DOC_AUDIT_TRAIL],
    checkouts:         window.DOC_CHECKOUTS,
    workflows:         window.DOC_WORKFLOWS,
    ocr:               window.DOC_OCR_PIPELINES,
    dlp:               window.DOC_DLP_POLICIES,
    accessReviews:     window.DOC_ACCESS_REVIEWS,
    workspaces:        window.DOC_WORKSPACES,
    templates:         window.DOC_TEMPLATES,
    custodians:        DOC_CUSTODIANS,
    envelopes:         DOC_ENVELOPES,
    trash:             DOC_TRASH,
    pinned:            DOC_PINNED,
    recentlyViewed:    DOC_RECENTLY_VIEWED,
    relationships:     DOC_RELATIONSHIPS,
    aiInsights:        DOC_AI_INSIGHTS,
    velocity:          DOC_VELOCITY,
    stale:             DOC_STALE,
    duplicates:        DOC_DUPLICATES,
    costByMatter:      DOC_COST_BY_MATTER,
    heatmap:           DOC_HEATMAP,
    confidTrend:       DOC_CONFID_TREND,
    shareRisk:         DOC_SHARE_RISK,
    conflicts:         DOC_CONFLICTS,
    savedSearches:     DOC_SAVED_SEARCHES_V2,
    starred:           new Set(window.DOC_ITEMS.filter(d => d.starred).map(d => d.id)),
    kpis:              { ...window.DOC_KPIS },
  };

  const mkAuditId = () => 'A-' + Math.random().toString(36).slice(2, 8).toUpperCase();
  const nowStamp  = () => new Date().toISOString().slice(0, 16).replace('T', ' ');

  function audit(user, action, doc, severity = 'Info') {
    const entry = { id: mkAuditId(), when: nowStamp(), user, action, doc, ip: '10.12.4.88', severity };
    state.audit.unshift(entry);
    if (state.audit.length > 60) state.audit.length = 60;
    DocBus.emit('doc.audit', entry);
    return entry;
  }

  const DocStore = {
    get: () => state,

    // ── STAR / PIN / TOUCH / FAVORITES (#9) ──
    toggleStar(docId, user = 'M. Kirkland') {
      if (state.starred.has(docId)) state.starred.delete(docId);
      else state.starred.add(docId);
      DocBus.emit('doc.starred', { docId, starred: state.starred.has(docId) });
      audit(user, state.starred.has(docId) ? 'Starred' : 'Unstarred', state.docs.find(d => d.id === docId)?.name || 'Unknown');
    },
    pin(docId, user = 'M. Kirkland', note = '') {
      if (!state.pinned.find(p => p.docId === docId)) {
        state.pinned.unshift({ docId, pinnedBy: user, pinnedAt: new Date().toISOString().slice(0, 10), note });
        DocBus.emit('doc.pinned', { docId, user });
        audit(user, 'Pinned', state.docs.find(d => d.id === docId)?.name || 'Unknown');
      }
    },
    unpin(docId, user = 'M. Kirkland') {
      state.pinned = state.pinned.filter(p => p.docId !== docId);
      DocBus.emit('doc.unpinned', { docId, user });
    },
    touch(docId, user = 'M. Kirkland') {
      state.recentlyViewed = [{ docId, viewedAt: nowStamp(), user }, ...state.recentlyViewed.filter(r => r.docId !== docId)].slice(0, 20);
      DocBus.emit('doc.viewed', { docId, user });
    },

    // ── UPLOAD / DELETE / RESTORE (#1, #7) ──
    upload(meta, user = 'M. Kirkland') {
      const id = Math.max(...state.docs.map(d => d.id)) + 1;
      const newDoc = {
        id, name: meta.name, type: meta.type || 'Other', matter: meta.matter || 'Unassigned', matterId: meta.matterId || null,
        folderId: meta.folderId || 'F-ROOT', author: user, modified: new Date().toISOString().slice(0, 10), created: new Date().toISOString().slice(0, 10),
        size: meta.size || '1 MB', ext: meta.ext || 'PDF', status: 'Draft', starred: false, locked: false, lockedBy: null,
        confidentiality: meta.confidentiality || 'Internal', permissions: meta.permissions || [user], tags: meta.tags || [],
        linkedTasks: [], linkedDeadlines: [],
        versions: [{ v: '1.0', date: 'Today', author: user, size: meta.size || '1 MB', notes: 'Initial upload' }],
        comments: [], activity: [{ action: 'Uploaded', user, time: 'Just now' }],
      };
      state.docs.push(newDoc);
      window.ALL_DOCS_REF = state.docs;
      state.kpis.totalDocs++;
      audit(user, 'Uploaded', newDoc.name);
      DocBus.emit('doc.uploaded', { doc: newDoc });
      // Fire any matching workflow
      state.workflows.filter(w => w.trigger.includes('Upload')).forEach(w => { w.runs++; w.lastRun = new Date().toISOString().slice(0, 10); DocBus.emit('workflow.triggered', { workflowId: w.id, docId: id }); });
      // OCR pipeline trigger for PDFs
      if (newDoc.ext === 'PDF') { const p = state.ocr[0]; p.queue++; DocBus.emit('ocr.queued', { docId: id, pipelineId: p.id }); }
      return newDoc;
    },
    softDelete(docId, user = 'M. Kirkland', reason = 'Deleted by user') {
      const doc = state.docs.find(d => d.id === docId);
      if (!doc) return;
      // Block delete if under legal hold
      if (this.isUnderHold(docId)) {
        DocBus.emit('doc.deleteBlocked', { docId, reason: 'Under legal hold' });
        audit(user, 'Delete blocked (hold)', doc.name, 'Alert');
        return false;
      }
      const trashEntry = { id: 'TR-' + Date.now(), docId, name: doc.name, type: doc.type, matter: doc.matter, size: doc.size, ext: doc.ext, deletedBy: user, deletedAt: nowStamp(), purgeAt: new Date(Date.now() + 30 * 864e5).toISOString().slice(0, 10), reason };
      state.trash.unshift(trashEntry);
      state.docs = state.docs.filter(d => d.id !== docId);
      window.ALL_DOCS_REF = state.docs;
      state.kpis.totalDocs--;
      audit(user, 'Deleted', doc.name, 'Warn');
      DocBus.emit('doc.deleted', { docId, trashEntry });
      return true;
    },
    restoreFromTrash(trashId, user = 'M. Kirkland') {
      const entry = state.trash.find(t => t.id === trashId);
      if (!entry) return;
      state.trash = state.trash.filter(t => t.id !== trashId);
      // Rebuild a minimal doc; preserves id
      const doc = { id: entry.docId, name: entry.name, type: entry.type, matter: entry.matter, matterId: null, folderId: 'F-ROOT', author: user, modified: new Date().toISOString().slice(0, 10), created: entry.deletedAt.slice(0, 10), size: entry.size, ext: entry.ext, status: 'Draft', starred: false, locked: false, lockedBy: null, confidentiality: 'Internal', permissions: [user], tags: [], linkedTasks: [], linkedDeadlines: [], versions: [{ v: '1.0', date: 'Restored', author: user, size: entry.size, notes: 'Restored from trash' }], comments: [], activity: [{ action: 'Restored', user, time: 'Just now' }] };
      state.docs.push(doc);
      window.ALL_DOCS_REF = state.docs;
      state.kpis.totalDocs++;
      audit(user, 'Restored', entry.name);
      DocBus.emit('doc.restored', { trashId, doc });
    },
    purgeFromTrash(trashId, user = 'M. Kirkland') {
      const entry = state.trash.find(t => t.id === trashId);
      if (!entry) return;
      state.trash = state.trash.filter(t => t.id !== trashId);
      audit(user, 'Purged', entry.name, 'Alert');
      DocBus.emit('doc.purged', { trashId });
    },

    // ── APPROVAL FLOW (#4) ──
    decideApproval(approvalId, decision, comment = '', user = 'M. Kirkland') {
      const a = state.approvals.find(x => x.id === approvalId);
      if (!a) return;
      a.status = decision;
      a.decidedAt = nowStamp();
      a.decisionComment = comment;
      a.decidedBy = user;
      audit(user, `Approval ${decision}`, a.doc, decision === 'Approved' ? 'Info' : decision === 'Changes' ? 'Warn' : 'Info');
      DocBus.emit('doc.approvalDecided', { approvalId, decision, approval: a });
      state.kpis.pendingApprovals = state.approvals.filter(x => x.status === 'Pending').length;
    },
    reassignApproval(approvalId, newApprover, user = 'M. Kirkland') {
      const a = state.approvals.find(x => x.id === approvalId);
      if (!a) return;
      const old = a.approver;
      a.approver = newApprover;
      audit(user, 'Approval reassigned', `${a.doc} (${old} → ${newApprover})`, 'Info');
      DocBus.emit('doc.approvalReassigned', { approvalId, from: old, to: newApprover });
    },

    // ── SHARES (#15) ──
    createShare(docId, recipient, scope = 'View', expires = null, user = 'M. Kirkland') {
      const doc = state.docs.find(d => d.id === docId);
      const share = { id: 'SH-' + Date.now(), doc: doc?.name || 'Unknown', recipient, scope, expires, createdBy: user, status: 'Active' };
      state.shares.push(share);
      state.shareRisk[share.id] = { score: scope === 'Public' ? 8 : scope === 'Comment' ? 45 : 22, factors: [`Scope: ${scope}`, expires ? 'Expires ' + expires : 'No expiry'] };
      audit(user, 'Shared', doc?.name || 'Unknown', 'Warn');
      DocBus.emit('doc.shareCreated', { share });
      DocBus.emit('doc.sharedExternally', { docId, share });
    },
    revokeShare(shareId, user = 'M. Kirkland') {
      const s = state.shares.find(x => x.id === shareId);
      if (!s) return;
      s.status = 'Revoked';
      audit(user, 'Share revoked', s.doc);
      DocBus.emit('doc.shareRevoked', { shareId });
    },

    // ── LEGAL HOLDS (#11) ──
    isUnderHold(docId) {
      const doc = state.docs.find(d => d.id === docId);
      if (!doc) return false;
      return state.holds.some(h => h.status === 'Active' && (h.matter === doc.matter || doc.matter?.includes(h.matter?.split(' ')[0])));
    },
    applyHold(holdId, user = 'M. Kirkland') {
      const h = state.holds.find(x => x.id === holdId);
      if (!h) return;
      h.status = 'Active';
      audit(user, 'Hold applied', h.name, 'Warn');
      DocBus.emit('doc.holdApplied', { holdId, hold: h });
    },
    releaseHold(holdId, user = 'M. Kirkland') {
      const h = state.holds.find(x => x.id === holdId);
      if (!h) return;
      h.status = 'Released';
      audit(user, 'Hold released', h.name, 'Warn');
      DocBus.emit('doc.holdReleased', { holdId });
    },
    sendHoldReminder(holdId, custodianId, user = 'M. Kirkland') {
      const c = state.custodians.find(x => x.id === custodianId);
      if (!c) return;
      c.reminders++;
      c.lastReminder = new Date().toISOString().slice(0, 10);
      audit(user, 'Hold reminder sent', `${c.name} (${c.email})`);
      DocBus.emit('doc.holdReminder', { holdId, custodianId });
    },
    ackHold(custodianId) {
      const c = state.custodians.find(x => x.id === custodianId);
      if (!c) return;
      c.ack = true;
      c.ackDate = new Date().toISOString().slice(0, 10);
      audit(c.name, 'Acknowledged hold', c.holdId);
      DocBus.emit('doc.holdAck', { custodianId });
    },

    // ── ACCESS REVIEWS (#14) ──
    certifyAccessReview(reviewId, user = 'M. Kirkland') {
      const r = state.accessReviews.find(x => x.id === reviewId);
      if (!r) return;
      r.status = 'Current';
      r.lastReview = new Date().toISOString().slice(0, 10);
      audit(user, 'Access review certified', r.folder);
      DocBus.emit('access.certified', { reviewId });
    },

    // ── BULK ACTIONS (#3) ──
    bulkAction(docIds, action, payload, user = 'M. Kirkland') {
      const count = docIds.length;
      switch (action) {
        case 'star':     docIds.forEach(id => state.starred.add(id)); break;
        case 'unstar':   docIds.forEach(id => state.starred.delete(id)); break;
        case 'delete':   docIds.forEach(id => this.softDelete(id, user, 'Bulk delete')); break;
        case 'tag':      docIds.forEach(id => { const d = state.docs.find(x => x.id === id); if (d) d.tags = [...new Set([...(d.tags || []), payload])]; }); break;
        case 'move':     docIds.forEach(id => { const d = state.docs.find(x => x.id === id); if (d) d.folderId = payload; }); break;
        case 'confidentiality': docIds.forEach(id => { const d = state.docs.find(x => x.id === id); if (d) d.confidentiality = payload; }); break;
        case 'hold':     docIds.forEach(id => audit(user, 'Added to hold', state.docs.find(x => x.id === id)?.name || '', 'Warn')); break;
      }
      audit(user, `Bulk ${action} (${count})`, `${count} documents`);
      DocBus.emit('doc.bulkAction', { action, docIds, count, payload });
    },

    // ── CONFLICTS (#5) ──
    resolveConflict(conflictId, winner, user = 'M. Kirkland') {
      const c = state.conflicts.find(x => x.id === conflictId);
      if (!c) return;
      c.status = 'Resolved';
      c.resolvedBy = user;
      c.resolvedAt = nowStamp();
      c.winner = winner;
      audit(user, 'Conflict resolved', c.docName);
      DocBus.emit('doc.conflictResolved', { conflictId, winner });
    },

    // ── SAVED SEARCHES (#8) ──
    saveSearch(name, query, user = 'M. Kirkland') {
      const s = { id: 'SS-' + Date.now(), name, query, resultCount: 0, owner: user, icon: '*' };
      state.savedSearches.push(s);
      DocBus.emit('search.saved', { search: s });
      return s;
    },
    deleteSavedSearch(id) {
      state.savedSearches = state.savedSearches.filter(s => s.id !== id);
      DocBus.emit('search.deleted', { id });
    },

    // ── eSIGN (#6) ──
    sendEnvelope(docId, signers, user = 'M. Kirkland') {
      const doc = state.docs.find(d => d.id === docId);
      const env = { id: 'EV-' + Date.now(), docId, docName: doc?.name || 'Unknown', status: 'Awaiting', sent: nowStamp(), completed: null, createdBy: user, signers: signers.map(s => ({ name: s, status: 'Pending', when: null })), reminders: 0 };
      state.envelopes.unshift(env);
      audit(user, 'eSign envelope sent', doc?.name || 'Unknown');
      DocBus.emit('esign.sent', { envelope: env });
    },
  };

  window.DocStore = DocStore;

  // ── React hook: useDocStore causes re-render when any of the provided topics fires ──
  window.useDocStore = function (topics = ['*']) {
    const [, force] = React.useState(0);
    React.useEffect(() => {
      const arr = Array.isArray(topics) ? topics : [topics];
      const unsubs = arr.map(t => DocBus.on(t, () => force(n => n + 1)));
      return () => unsubs.forEach(u => u());
    }, []);
    return DocStore.get();
  };

  // ── Expose top-level data used by older code ──
  window.DOC_CUSTODIANS = DOC_CUSTODIANS;
  window.DOC_ENVELOPES = DOC_ENVELOPES;
  window.DOC_TRASH = DOC_TRASH;
  window.DOC_PINNED = DOC_PINNED;
  window.DOC_RECENTLY_VIEWED = DOC_RECENTLY_VIEWED;
  window.DOC_RELATIONSHIPS = DOC_RELATIONSHIPS;
  window.DOC_AI_INSIGHTS = DOC_AI_INSIGHTS;
  window.DOC_VELOCITY = DOC_VELOCITY;
  window.DOC_STALE = DOC_STALE;
  window.DOC_DUPLICATES = DOC_DUPLICATES;
  window.DOC_COST_BY_MATTER = DOC_COST_BY_MATTER;
  window.DOC_HEATMAP = DOC_HEATMAP;
  window.DOC_CONFID_TREND = DOC_CONFID_TREND;
  window.DOC_SHARE_RISK = DOC_SHARE_RISK;
  window.DOC_CONFLICTS = DOC_CONFLICTS;
  window.DOC_SAVED_SEARCHES_V2 = DOC_SAVED_SEARCHES_V2;

  // ── CROSS-PLATFORM BRIDGE (external → doc) ──
  // Any platform can dispatch these via window.dispatchEvent(new CustomEvent('arbiter:external:*'))
  window.addEventListener('arbiter:external:matterClosed', (e) => {
    const matter = e.detail?.matter;
    if (!matter) return;
    const affected = state.docs.filter(d => d.matter === matter);
    audit('System', `Matter closed: applied retention to ${affected.length} docs`, matter, 'Warn');
    DocBus.emit('doc.retentionApplied', { matter, count: affected.length });
  });
  window.addEventListener('arbiter:external:taskCreated', (e) => {
    const { taskId, docId } = e.detail || {};
    if (!docId) return;
    const d = state.docs.find(x => x.id === docId);
    if (d) { d.linkedTasks = [...new Set([...(d.linkedTasks || []), taskId])]; DocBus.emit('doc.linkedToTask', { docId, taskId }); }
  });
  window.addEventListener('arbiter:external:signdeskCompleted', (e) => {
    const envId = e.detail?.envelopeId;
    const env = state.envelopes.find(x => x.id === envId);
    if (env) { env.status = 'Completed'; env.completed = nowStamp(); DocBus.emit('esign.completed', { envelope: env }); }
  });

})();
