/* ============================================================
   REPORTS MODULE
   Two sections: the register of reports the business runs, and the
   analytics that cut across every other module.
   ============================================================ */

const NEW_REPORT = () => ({
  id:'', reference:'', name:'', description:'', reportType:'Operational', frequency:'Monthly',
  status:'Active', outputFormat:'Dashboard', owner:null, businessOwner:null, sourceUrl:'',
  distribution:'', nextDue:'', lastProduced:'', retention:'', isRegulatory:false, componentIds:[]
});

function ReportsModule({state, idx, go, act, toast, route}){
  const [section, setSection] = useState('register');
  const [editing, setEditing] = useState(null);
  const [open, setOpen] = useState(null);

  useEffect(()=>{
    if (route && route.action === 'new') setEditing(NEW_REPORT());
    if (route && route.tab) setSection(route.tab);
  }, [route]);

  const canEdit = (id) => {
    const ids = state.permissions.editableReportIds;
    return ids === '*' || (Array.isArray(ids) && ids.includes(id));
  };

  return (
    <div className="mod">
      <div className="mod-head">
        <div className="tabs" style={{margin:0}}>
          <button className={'tab' + (section==='register'?' active':'')} onClick={()=>setSection('register')}>
            <I.chart size={15}/>Report register <span className="n">{state.reports.length}</span></button>
          <button className={'tab' + (section==='analytics'?' active':'')} onClick={()=>setSection('analytics')}>
            <I.shield size={15}/>Analytics</button>
        </div>
      </div>

      {section === 'register' &&
        <ReportRegister state={state} idx={idx} go={go} act={act} canEdit={canEdit} onOpen={setOpen}/>}
      {section === 'analytics' &&
        <div className="mod-body"><div className="mod-scroll"><AnalyticsSection state={state} idx={idx} go={go}/></div></div>}

      {open &&
        <ReportDetail report={state.reports.find(r => r.id === open.id) || open}
          state={state} idx={idx} go={go} act={act} editable={canEdit(open.id)}
          onClose={()=>setOpen(null)}
          onEdit={()=>{ setEditing(state.reports.find(r => r.id === open.id)); setOpen(null); }}/>}

      {editing &&
        <ReportForm value={editing} state={state} onClose={()=>setEditing(null)}
          onSave={async (v)=>{
            const isNew = !v.id;
            const ok = await act(isNew ? API.post('/api/reports', v) : API.put(`/api/reports/${v.id}`, v),
              isNew ? 'Report added' : 'Report saved');
            if (ok) setEditing(null);
          }}/>}
    </div>
  );
}

/* --------------------------------------------------------------- register */

function ReportRegister({state, idx, go, act, canEdit, onOpen}){
  const [q, setQ] = useState(()=>urlParam('q'));
  const [fFreq, setFFreq] = useState(()=>urlParam('freq'));
  const [fType, setFType] = useState(()=>urlParam('type'));
  const [fStatus, setFStatus] = useState(()=>urlParam('status'));
  const [regOnly, setRegOnly] = useState(()=>urlParam('reg')==='1');
  useFilterUrlSync({q:[q,''], freq:[fFreq,''], type:[fType,''], status:[fStatus,''], reg:[regOnly?'1':'','']});
  const {sort, sorted, Th} = useSort('name', {
    name: r => r.name, type: r => r.reportType, freq: r => r.frequency, status: r => r.status,
    owner: r => partyName(r.businessOwner, idx) || 'zzz', due: r => r.nextDue || '9999'
  });
  const clearFilters = () => { setQ('');setFType('');setFFreq('');setFStatus('');setRegOnly(false); };
  const hasFilters = !!(q||fType||fFreq||fStatus||regOnly);

  const rows = useMemo(()=>{
    const term = q.trim().toLowerCase();
    const list = state.reports.filter(r => {
      if (fFreq && r.frequency !== fFreq) return false;
      if (fType && r.reportType !== fType) return false;
      if (fStatus && r.status !== fStatus) return false;
      if (regOnly && !r.isRegulatory) return false;
      if (!term) return true;
      return [r.name, r.reference, r.description, r.reportType, r.distribution,
        partyName(r.owner, idx), partyName(r.businessOwner, idx)].join(' ').toLowerCase().includes(term);
    });
    return sorted(list);
  }, [state.reports, q, fFreq, fType, fStatus, regOnly, idx, sort]);

  const due = state.reports.filter(r => isOverdue(r.nextDue) && r.status === 'Active');
  const exportList = () => exportCsv('fuse-reports.csv',
    ['Reference','Name','Type','Frequency','Status','Format','Technical owner','Business owner',
     'Regulatory','Next due','Last produced','Recipients','Source components'],
    rows.map(r => [r.reference, r.name, r.reportType, r.frequency, r.status, r.outputFormat,
      partyName(r.owner, idx), partyName(r.businessOwner, idx), r.isRegulatory ? 'Yes' : 'No',
      r.nextDue, r.lastProduced,
      r.recipients.map(x => partyName(x.party, idx)).join(' / '),
      (r.componentIds||[]).map(cid => (idx.comp[cid]||{}).identifier).filter(Boolean).join(' ')]));

  return (
    <>
      <div className="mod-head">
        <div className="stats-row">
          <div className="stat"><div className="n">{state.reports.length}</div><div className="l">Reports</div></div>
          <div className="stat"><div className="n">{state.reports.filter(r=>r.status==='Active').length}</div>
            <div className="l">Active</div></div>
          <div className="stat"><div className="n">{state.reports.filter(r=>r.isRegulatory).length}</div>
            <div className="l">Regulatory</div></div>
          <div className="stat"><div className="n" style={{color: due.length ? 'var(--amber)' : undefined}}>{due.length}</div>
            <div className="l">Past due</div></div>
          <div className="stat"><div className="n">{state.reports.filter(r=>!r.owner||!r.businessOwner).length}</div>
            <div className="l">Missing an owner</div></div>
        </div>

        <div className="row wrap">
          <div className="spacer"/>
          <button className="btn" onClick={exportList}><I.down size={14}/>CSV</button>
        </div>

        <div className="row wrap">
          <SearchBox value={q} onChange={setQ} aria="Search reports"
                     title="Search name, owner or recipients"/>
          <select style={{width:'auto'}} value={fType} onChange={e=>setFType(e.target.value)}>
            <option value="">All types</option>
            {(state.lookups.report_type||[]).map(t=><option key={t}>{t}</option>)}</select>
          <select style={{width:'auto'}} value={fFreq} onChange={e=>setFFreq(e.target.value)}>
            <option value="">Any frequency</option>
            {(state.reportFrequencies||[]).map(t=><option key={t}>{t}</option>)}</select>
          <select style={{width:'auto'}} value={fStatus} onChange={e=>setFStatus(e.target.value)}>
            <option value="">All statuses</option>
            {(state.reportStatuses||[]).map(t=><option key={t}>{t}</option>)}</select>
          <button className={'chip-toggle'+(regOnly?' on':'')} onClick={()=>setRegOnly(v=>!v)}>Regulatory only</button>
          {hasFilters && <button className="btn ghost sm" onClick={clearFilters}>Clear</button>}
          <div className="spacer"/>
          <span className="xsmall dim">{rows.length} shown</span>
        </div>
      </div>

      <div className="mod-body">
      <div className="card">
        <div className="card-head"><span className="xsmall dim">Click a row to open</span></div>
        {rows.length ? (
          <div className="tbl-wrap">
            <table>
              <thead><tr>
                <Th k="name" label="Report" style={{width:'28%'}}/>
                <Th k="type" label="Type"/>
                <Th k="freq" label="Frequency"/>
                <Th k="status" label="Status"/>
                <Th k="owner" label="Business owner"/>
                <Th k="due" label="Next due"/>
                <th>Recipients</th><th>Sources</th>
              </tr></thead>
              <tbody>
                {rows.map(r => {
                  const bo = resolveParty(r.businessOwner, idx);
                  const overdue = isOverdue(r.nextDue) && r.status === 'Active';
                  return (
                    <tr key={r.id} className="clickable" onClick={()=>onOpen(r)}>
                      <td>
                        <div style={{fontWeight:600}}>{r.name}
                          {r.isRegulatory && <Badge tone="red" >Regulatory</Badge>}</div>
                        <div className="cell-sub">
                          {r.reference && <span className="mono">{r.reference} · </span>}{r.outputFormat}</div>
                      </td>
                      <td className="small muted">{r.reportType}</td>
                      <td><Badge tone="blue">{r.frequency}</Badge></td>
                      <td><Badge tone={r.status==='Active'?'green':r.status==='Paused'?'amber':'slate'} dot>{r.status}</Badge></td>
                      <td className="small">{bo
                        ? <span className="row" style={{gap:6}}><PartyAvatar resolved={bo} size="sm"/>{bo.name}</span>
                        : <span className="dim">—</span>}</td>
                      <td className="small">{r.nextDue
                        ? <Badge tone={overdue ? 'red' : 'slate'}>{fmtDate(r.nextDue)}</Badge>
                        : <span className="dim">—</span>}</td>
                      <td className="small mono">{r.recipients.length}</td>
                      <td>
                        <div className="pill-list">
                          {(r.componentIds||[]).slice(0,2).map(cid => idx.comp[cid]
                            ? <span key={cid} className="badge slate">{idx.comp[cid].identifier}</span> : null)}
                          {(r.componentIds||[]).length > 2 &&
                            <span className="badge slate">+{r.componentIds.length - 2}</span>}
                          {!(r.componentIds||[]).length && <span className="dim small">—</span>}
                        </div>
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        ) : <EmptyState icon={<I.chart size={28}/>} title="No reports match"
              action={hasFilters ? <button className="btn sm" onClick={clearFilters}>Clear filters</button> : null}/>}
      </div>
      </div>
    </>
  );
}

function ReportDetail({report, state, idx, go, act, editable, onClose, onEdit}){
  const [adding, setAdding] = useState(false);
  const [party, setParty] = useState(null);
  const r = report;
  const owner = resolveParty(r.owner, idx);
  const bo = resolveParty(r.businessOwner, idx);
  const overdue = isOverdue(r.nextDue) && r.status === 'Active';
  const linked = (r.componentIds||[]).map(id => idx.comp[id]).filter(Boolean);

  return (
    <Modal wide title={r.name} onClose={onClose}
      footer={<>
        <button className="btn" onClick={onClose}>Close</button>
        {editable && <button className="btn primary" onClick={onEdit}><I.edit size={14}/>Edit</button>}
      </>}>
      <div className="row wrap" style={{gap:8, marginBottom:14}}>
        <Badge tone={r.status==='Active'?'green':r.status==='Paused'?'amber':'slate'} dot>{r.status}</Badge>
        <Badge tone="blue">{r.frequency}</Badge>
        <Badge tone="purple">{r.reportType}</Badge>
        <Badge tone="slate">{r.outputFormat}</Badge>
        {r.isRegulatory && <Badge tone="red">Regulatory</Badge>}
        {overdue && <Badge tone="red">Past due</Badge>}
        {r.sourceUrl && <a className="btn sm" href={r.sourceUrl} target="_blank" rel="noopener noreferrer">
          <I.link size={13}/>Open report</a>}
      </div>

      {r.description && <p className="muted pre-wrap" style={{marginTop:0}}>{r.description}</p>}

      <dl className="kv-block" style={{marginTop:16}}>
        <dt>Reference</dt><dd className="mono">{r.reference || <span className="dim">—</span>}</dd>
        <dt>Business owner</dt><dd>{bo
          ? <span className="row" style={{gap:7}}><PartyAvatar resolved={bo} size="sm"/>{bo.name}</span>
          : <span className="dim">Not assigned</span>}</dd>
        <dt>Produced by</dt><dd>{owner
          ? <span className="row" style={{gap:7}}><PartyAvatar resolved={owner} size="sm"/>{owner.name}</span>
          : <span className="dim">Not assigned</span>}</dd>
        <dt>Next due</dt><dd>{r.nextDue
          ? <span style={overdue ? {color:'var(--red)',fontWeight:600} : null}>{fmtDate(r.nextDue)}</span>
          : <span className="dim">—</span>}</dd>
        <dt>Last produced</dt><dd>{fmtDate(r.lastProduced) || <span className="dim">—</span>}</dd>
        <dt>Retention</dt><dd>{r.retention || <span className="dim">—</span>}</dd>
        <dt>Distribution</dt><dd className="pre-wrap">{r.distribution || <span className="dim">—</span>}</dd>
      </dl>

      <div className="sub-head">Recipients</div>
      <div className="card">
        {r.recipients.length === 0
          ? <EmptyState icon={<I.users size={24}/>} title="Nobody recorded as a recipient"/>
          : <div>
              {r.recipients.map(x => {
                const p = resolveParty(x.party, idx);
                return (
                  <div className="owner-row" key={x.id}>
                    <PartyAvatar resolved={p}/>
                    <div className="owner-meta">
                      <div className="owner-name">{p ? p.name : 'Unknown'}</div>
                      <div className="xsmall dim">{p ? p.sub : ''}</div>
                    </div>
                    {p && p.email && <a className="icon-btn" href={'mailto:'+p.email}><I.mail size={15}/></a>}
                    {editable &&
                      <button className="icon-btn" title="Remove"
                        onClick={()=>act(API.del(`/api/reports/${r.id}/recipients/${x.id}`), 'Recipient removed')}>
                        <I.x size={14}/></button>}
                  </div>
                );
              })}
            </div>}
        {editable &&
          <div className="card-body" style={{borderTop:'1px solid var(--border)'}}>
            {adding
              ? <div className="row" style={{gap:8}}>
                  <div style={{flex:1}}><PartySelect value={party} state={state} onChange={setParty} placeholder="Choose…"/></div>
                  <button className="btn sm primary" disabled={!party}
                    onClick={async ()=>{
                      const ok = await act(API.post(`/api/reports/${r.id}/recipients`, {party}), 'Recipient added');
                      if (ok) { setParty(null); setAdding(false); }
                    }}>Add</button>
                  <button className="btn sm" onClick={()=>setAdding(false)}>Cancel</button>
                </div>
              : <button className="btn sm" onClick={()=>setAdding(true)}><I.plus size={13}/>Add recipient</button>}
          </div>}
      </div>

      <div className="sub-head">Source components</div>
      {linked.length
        ? <div className="pill-list">
            {linked.map(c =>
              <span key={c.id} className="chip-toggle" style={{cursor:'pointer'}}
                onClick={()=>{ onClose(); go({view:'component', id:c.id}); }}>
                <TierBadge tier={c.tier}/> <span style={{marginLeft:5}}>{c.name}</span></span>)}
          </div>
        : <p className="dim small" style={{margin:0}}>No source systems recorded.</p>}
    </Modal>
  );
}

function ReportForm({value, state, onClose, onSave}){
  const [r, setR] = useState(()=>clone(value));
  const [busy, setBusy] = useState(false);
  const set = (k,v) => setR(x => ({...x, [k]:v}));
  const isNew = !value.id;

  return (
    <Modal wide title={isNew ? 'New report' : 'Edit ' + value.name} onClose={onClose}
      footer={<>
        <button className="btn" onClick={onClose} disabled={busy}>Cancel</button>
        <button className="btn primary" disabled={!r.name.trim() || busy}
          onClick={async ()=>{ setBusy(true); try { await onSave(r); } finally { setBusy(false); } }}>
          {busy ? 'Saving…' : isNew ? 'Add report' : 'Save changes'}</button>
      </>}>
      <div className="form-grid">
        <Field label="Name"><input type="text" value={r.name} autoFocus
          onChange={e=>set('name', e.target.value)} placeholder="e.g. Monthly board pack"/></Field>
        <Field label="Reference"><input type="text" value={r.reference}
          onChange={e=>set('reference', e.target.value)} placeholder="e.g. REP-001"/></Field>
      </div>
      <Field label="Description"><textarea value={r.description}
        onChange={e=>set('description', e.target.value)} placeholder="What does this report tell people?"/></Field>

      <div className="sub-head">Cadence and format</div>
      <div className="form-grid">
        <Field label="Type">
          <select value={r.reportType} onChange={e=>set('reportType', e.target.value)}>
            {(state.lookups.report_type||['Operational']).map(t => <option key={t}>{t}</option>)}</select></Field>
        <Field label="Frequency">
          <select value={r.frequency} onChange={e=>set('frequency', e.target.value)}>
            {(state.reportFrequencies||[]).map(t => <option key={t}>{t}</option>)}</select></Field>
        <Field label="Status">
          <select value={r.status} onChange={e=>set('status', e.target.value)}>
            {(state.reportStatuses||[]).map(t => <option key={t}>{t}</option>)}</select></Field>
        <Field label="Output format">
          <select value={r.outputFormat} onChange={e=>set('outputFormat', e.target.value)}>
            {(state.reportFormats||[]).map(t => <option key={t}>{t}</option>)}</select></Field>
        <Field label="Next due"><input type="date" value={r.nextDue}
          onChange={e=>set('nextDue', e.target.value)}/></Field>
        <Field label="Last produced"><input type="date" value={r.lastProduced}
          onChange={e=>set('lastProduced', e.target.value)}/></Field>
      </div>
      <label className="row" style={{gap:8,cursor:'pointer',marginBottom:14}}>
        <input type="checkbox" checked={r.isRegulatory} onChange={e=>set('isRegulatory', e.target.checked)}/>
        <span className="small">This is a regulatory or statutory report</span>
      </label>

      <div className="sub-head">Ownership and distribution</div>
      <div className="form-grid">
        <Field label="Business owner" hint="Accountable for the content">
          <PartySelect value={r.businessOwner} state={state} onChange={v=>set('businessOwner', v)}/></Field>
        <Field label="Produced by" hint="Runs and publishes it">
          <PartySelect value={r.owner} state={state} onChange={v=>set('owner', v)}/></Field>
      </div>
      <Field label="Report link"><input type="url" value={r.sourceUrl}
        onChange={e=>set('sourceUrl', e.target.value)} placeholder="https://"/></Field>
      <div className="form-grid">
        <Field label="Distribution notes" hint="Named recipients are added on the report itself.">
          <textarea value={r.distribution} onChange={e=>set('distribution', e.target.value)}
            placeholder="e.g. Board distribution list, company secretary"/></Field>
        <Field label="Retention"><input type="text" value={r.retention}
          onChange={e=>set('retention', e.target.value)} placeholder="e.g. 7 years"/></Field>
      </div>

      <div className="sub-head">Source components</div>
      <ComponentPicker state={state} value={r.componentIds} onChange={v=>set('componentIds', v)}/>
    </Modal>
  );
}
/* ============================================================
   ANALYTICS SECTION (reused from the previous version)
   ============================================================ */
function AnalyticsSection({state, idx, go}){
  const {out} = useMemo(()=>depGraph(state), [state]);
  const [who, setWho] = useState('');
  const sel = parsePartyKey(who);

  const forParty = useMemo(()=>{
    if (!sel) return null;
    return state.components.map(c => {
      const caps = [];
      OWNER_ROLES.forEach(o => {
        const r = c.owners && c.owners[o.key];
        if (r && r.kind===sel.kind && r.id===sel.id) caps.push(o.label);
      });
      (c.stakeholders||[]).forEach(s => {
        if (s.party && s.party.kind===sel.kind && s.party.id===sel.id)
          caps.push((idx.cap[s.capacityId]||{}).name || 'Stakeholder');
      });
      (c.raci||[]).forEach(r => {
        if (r.party && r.party.kind===sel.kind && r.party.id===sel.id){
          const a = idx.activity[r.activityId];
          caps.push(`${r.letter} · ${a ? a.name : 'activity'}`);
        }
      });
      return caps.length ? {c, caps} : null;
    }).filter(Boolean);
  }, [state, sel, idx]);

  const vendors = useMemo(()=>{
    const m = {};
    state.components.forEach(c => {
      const v = (c.vendor||'Unspecified').trim() || 'Unspecified';
      (m[v] = m[v] || []).push(c);
    });
    return Object.entries(m).sort((a,b)=>b[1].length - a[1].length);
  }, [state]);

  const tierBreach = state.components.filter(c => c.status !== 'Archived')
    .map(c => ({c, offenders: (out[c.id]||[]).filter(d => {
        const dep = idx.comp[d.toId];
        return dep && d.criticality==='Hard' && dep.tier > c.tier;
      }).map(d => idx.comp[d.toId])}))
    .filter(x => x.offenders.length);

  const partyOptions = [
    ...state.people.map(p=>({k:'person:'+p.id, label:p.name, group:'People'})),
    ...state.roles.map(r=>({k:'role:'+r.id, label:r.name, group:'Roles'}))
  ];

  // Cross-module figures, so the analytics section is not only about components.
  const openRequests = state.requests.filter(r => !['Done','Rejected'].includes(r.status));
  const byArea = {};
  openRequests.forEach(r => { const k = r.businessArea || 'Unspecified'; byArea[k] = (byArea[k]||0)+1; });
  const requestsPerComponent = {};
  state.requests.forEach(r => (r.componentIds||[]).forEach(cid => {
    requestsPerComponent[cid] = (requestsPerComponent[cid]||0) + 1;
  }));
  const busiest = Object.entries(requestsPerComponent)
    .map(([cid,n]) => ({c: idx.comp[cid], n})).filter(x => x.c)
    .sort((a,b)=>b.n-a.n).slice(0,8);
  const undocumented = state.components.filter(c =>
    c.status !== 'Archived' && !state.documents.some(d => (d.componentIds||[]).includes(c.id)));

  return (
    <div className="stack">
      <div className="card">
        <div className="card-head"><h3>Who is responsible for what</h3><div className="spacer"/>
          <select style={{width:280}} value={who} onChange={e=>setWho(e.target.value)}>
            <option value="">Choose a person or role…</option>
            <optgroup label="People">
              {partyOptions.filter(o=>o.group==='People').map(o=><option key={o.k} value={o.k}>{o.label}</option>)}</optgroup>
            <optgroup label="Roles">
              {partyOptions.filter(o=>o.group==='Roles').map(o=><option key={o.k} value={o.k}>{o.label}</option>)}</optgroup>
          </select></div>
        {!forParty && <EmptyState icon={<I.users size={28}/>} title="Pick someone to see their portfolio"
          body="Includes ownership, stakeholder capacities and every RACI assignment."/>}
        {forParty && !forParty.length && <EmptyState icon={<I.users size={28}/>} title="No components recorded against them"/>}
        {forParty && forParty.length > 0 &&
          <div className="tbl-wrap">
            <table>
              <thead><tr><th>Component</th><th>Type</th><th>Tier</th><th>Status</th><th>Involvement</th></tr></thead>
              <tbody>{[...forParty].sort((a,b)=>a.c.tier-b.c.tier).map(({c,caps}) =>
                <tr key={c.id} className="clickable" onClick={()=>go({view:'component',id:c.id})}>
                  <td><span className="cell-name">{c.name}</span>
                    <span className="cell-sub mono" style={{display:'block'}}>{c.identifier}</span></td>
                  <td className="small muted">{(idx.type[c.typeId]||{}).name}</td>
                  <td><TierBadge tier={c.tier}/></td>
                  <td><Badge tone={statusTone(c.status)} dot>{c.status}</Badge></td>
                  <td><div className="pill-list">{caps.map((x,i)=><Badge key={i} tone="accent">{x}</Badge>)}</div></td>
                </tr>)}</tbody>
            </table>
          </div>}
      </div>

      <div className="grid" style={{gridTemplateColumns:'repeat(auto-fit,minmax(360px,1fr))'}}>
        <div className="card">
          <div className="card-head"><h3>Open requests by business area</h3>
            <Badge tone="slate">{openRequests.length}</Badge></div>
          <div className="card-body tight">
            {openRequests.length === 0
              ? <EmptyState icon={<I.check size={26}/>} title="Nothing open"/>
              : Object.entries(byArea).sort((a,b)=>b[1]-a[1]).map(([area,n]) => (
                  <div className="side-row" key={area}>
                    <span style={{minWidth:0,flex:1}}>
                      <span style={{fontWeight:600}}>{area}</span>
                      <span className="bar" style={{marginTop:6}}>
                        <span style={{width:(n/openRequests.length*100)+'%',background:'var(--accent)'}}/></span>
                    </span>
                    <strong className="small">{n}</strong>
                  </div>))}
          </div>
        </div>

        <div className="card">
          <div className="card-head"><h3>Components attracting the most requests</h3></div>
          <div className="card-body tight">
            {busiest.length === 0
              ? <EmptyState icon={<I.inbox size={26}/>} title="No requests linked to components yet"/>
              : busiest.map(({c,n}) =>
                  <div className="side-row" key={c.id} style={{cursor:'pointer'}}
                       onClick={()=>go({view:'component', id:c.id})}>
                    <span style={{minWidth:0}}>
                      <span style={{fontWeight:600}}>{c.name} <TierBadge tier={c.tier}/></span>
                      <span className="xsmall dim" style={{display:'block'}}>{c.identifier}</span>
                    </span>
                    <strong className="small">{n}</strong>
                  </div>)}
          </div>
        </div>

        <div className="card">
          <div className="card-head"><h3>Components with no documentation</h3>
            <Badge tone={undocumented.length ? 'amber' : 'green'}>{undocumented.length}</Badge></div>
          <div className="card-body tight scroll-y" style={{maxHeight:380}}>
            {undocumented.length === 0
              ? <EmptyState icon={<I.check size={26}/>} title="Everything has at least one document"/>
              : undocumented.map(c =>
                  <div className="side-row" key={c.id} style={{cursor:'pointer'}}
                       onClick={()=>go({view:'component', id:c.id})}>
                    <span style={{minWidth:0}}>
                      <span style={{fontWeight:600}}>{c.name}</span>
                      <span className="xsmall dim" style={{display:'block'}}>
                        {(idx.type[c.typeId]||{}).name}</span>
                    </span>
                    <TierBadge tier={c.tier}/>
                  </div>)}
          </div>
        </div>

        <div className="card">
          <div className="card-head"><h3>Tier consistency</h3>
            <Badge tone={tierBreach.length?'amber':'green'}>{tierBreach.length}</Badge></div>
          <div className="card-body tight">
            <div className="card-body" style={{paddingBottom:6}}>
              <span className="xsmall dim">Components with a hard dependency on something rated less critical than themselves.</span>
            </div>
            {!tierBreach.length && <EmptyState icon={<I.check size={26}/>} title="No tier inconsistencies"/>}
            {tierBreach.map(({c,offenders}) =>
              <div key={c.id} className="side-row" style={{cursor:'pointer'}} onClick={()=>go({view:'component',id:c.id})}>
                <span style={{minWidth:0}}>
                  <span style={{fontWeight:600}}>{c.name} <TierBadge tier={c.tier}/></span>
                  <span className="xsmall dim" style={{display:'block'}}>
                    relies on {offenders.map(o=>`${o.name} (T${o.tier})`).join(', ')}</span>
                </span>
              </div>)}
          </div>
        </div>

        <div className="card">
          <div className="card-head"><h3>Vendor concentration</h3><Badge tone="slate">{vendors.length}</Badge></div>
          <div className="card-body tight scroll-y" style={{maxHeight:380}}>
            {vendors.map(([v, list]) =>
              <div key={v} className="side-row">
                <span style={{minWidth:0}}>
                  <span style={{fontWeight:600}}>{v}</span>
                  <span className="xsmall dim" style={{display:'block'}}>
                    {list.map(c=>c.name).slice(0,3).join(', ')}{list.length>3 ? ` +${list.length-3} more` : ''}</span>
                </span>
                <span className="row" style={{gap:5}}>
                  {list.some(c=>c.tier===1) && <Badge tone="red">T1</Badge>}
                  <strong className="small">{list.length}</strong>
                </span>
              </div>)}
          </div>
        </div>
      </div>

      <div className="card">
        <div className="card-head"><h3>Full relationship register</h3><Badge tone="slate">{state.dependencies.length}</Badge></div>
        <div className="tbl-wrap" style={{maxHeight:460}}>
          <table>
            <thead><tr><th>Component</th><th></th><th>Depends on</th><th>Type</th><th>Criticality</th><th>Notes</th></tr></thead>
            <tbody>{state.dependencies.map(d => {
              const a = idx.comp[d.fromId], b = idx.comp[d.toId];
              if (!a || !b) return null;
              return (
                <tr key={d.id}>
                  <td className="clickable" onClick={()=>go({view:'component',id:a.id})}>
                    <span className="cell-name">{a.name}</span>
                    <span className="cell-sub mono" style={{display:'block'}}>{a.identifier}</span></td>
                  <td className="dim"><I.arrowD size={14}/></td>
                  <td className="clickable" onClick={()=>go({view:'component',id:b.id})}>
                    <span className="cell-name">{b.name}</span>
                    <span className="cell-sub mono" style={{display:'block'}}>{b.identifier}</span></td>
                  <td className="small">{d.type}</td>
                  <td><Badge tone={d.criticality==='Hard'?'red':'slate'}>{d.criticality}</Badge></td>
                  <td className="small muted">{d.notes||'—'}</td>
                </tr>);
            })}</tbody>
          </table>
        </div>
      </div>
    </div>
  );
}

