// ============================================================
// ShieldTech Secret Weapon — List screens
// Public Bids · Contractor / List Builder · Vendor Applications
// ============================================================

// ─────────────────────────────────────────────────────────────
// PUBLIC BID TRACKER
// ─────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────
// BID OPPORTUNITY COMMAND CENTER
// Generates trade-matched opportunities from files. Filtering,
// scoring, categorization & build logic all driven by the trade
// selections in Settings (the single source of truth).
// ─────────────────────────────────────────────────────────────
function ScreenBids({ goto }) {
  const [trades] = useSelectedTrades();
  const [filter, setFilter] = React.useState('all');
  const [scan, setScan] = React.useState('done');   // idle | scanning | done
  const [scanStep, setScanStep] = React.useState('');
  const [curating, setCurating] = React.useState(false);
  const [detail, setDetail] = React.useState(null);
  const [build, setBuild] = React.useState(null);
  const [dismissed, setDismissed] = React.useState([]);

  // Generate & enrich every file-sourced opportunity against selected trades.
  const all = React.useMemo(() => {
    const raw = [...BIDS, ...INBOX_OPPS];
    return rankOpportunities(raw.map(o => enrichOpportunity(o, trades)))
      .filter(o => !dismissed.includes(o.id));
  }, [trades, dismissed]);

  const fits = all.filter(o => o.isFit);
  const newOpps = all.filter(o => o.new && o.isFit);

  const filters = [
    { id: 'all', label: 'All', n: all.length },
    { id: 'high', label: 'High priority', n: all.filter(o => o.labels.some(l => l.t === 'High priority')).length },
    { id: 'due', label: 'Due soon', n: all.filter(o => o.daysLeft != null && o.daysLeft >= 0 && o.daysLeft <= 7).length },
    { id: 'review', label: 'Needs review', n: all.filter(o => o.labels.some(l => l.t === 'Needs review' || l.t === 'Missing info')).length },
    ...trades.slice(0, 4).map(id => ({ id: 'trade:' + id, label: TRADE_BY_ID[id]?.short, n: all.filter(o => o.matched.includes(id)).length })),
  ];

  const shown = all.filter(o => {
    if (filter === 'all') return true;
    if (filter === 'high') return o.labels.some(l => l.t === 'High priority');
    if (filter === 'due') return o.daysLeft != null && o.daysLeft >= 0 && o.daysLeft <= 7;
    if (filter === 'review') return o.labels.some(l => l.t === 'Needs review' || l.t === 'Missing info');
    if (filter.startsWith('trade:')) return o.matched.includes(filter.slice(6));
    return true;
  });

  const runScan = async () => {
    setScan('scanning');
    setScanStep('Refreshing saved sources and trade matches…');
    try {
      await window.ShieldTechApi?.hydrateGlobals?.();
      setScanStep('Task radar and opportunity matches are up to date.');
    } catch (error) {
      window.dispatchEvent(new CustomEvent('stw:api-error', { detail: { message: error.message, code: error.code || 'SCAN_FAILED' } }));
    } finally {
      setTimeout(() => { setScan('done'); setScanStep(''); }, 700);
    }
  };

  return (
    <div className="slide-up" style={{ display: 'flex', flexDirection: 'column', minHeight: '100%' }}>
      {/* Sticky compact header */}
      <div style={{ position: 'sticky', top: 0, zIndex: 20, background: 'linear-gradient(to bottom, #05070A 72%, rgba(5,7,10,0))', paddingBottom: 6 }}>
        <MobileHeader subtitle="OPPORTUNITIES" title={`${fits.length} match your trades`} large={false}
          trailing={<IconButton icon="refresh" size={40} onClick={runScan}/>}/>

        {/* Trade-context bar — Settings is the source of truth */}
        <button onClick={() => goto('settings')} type="button" style={{
          margin: '0 20px', width: 'calc(100% - 40px)', display: 'flex', alignItems: 'center', gap: 8,
          padding: '8px 12px', borderRadius: 12, background: 'rgba(168,85,247,0.07)',
          border: '1px solid rgba(168,85,247,0.22)', cursor: 'pointer', textAlign: 'left',
        }}>
          <Icon name="shield" size={14} color={C.assistantSoft}/>
          <span style={{ flex: 1, minWidth: 0, font: '500 12px/16px Inter, system-ui, sans-serif', color: C.fg2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
            Matched to <span style={{ color: C.assistantSoft, fontWeight: 700 }}>{trades.length} trades</span> · {trades.slice(0, 3).map(id => TRADE_BY_ID[id]?.short).join(', ')}{trades.length > 3 ? '…' : ''}
          </span>
          <span style={{ font: '600 11px/1 Inter, system-ui, sans-serif', color: C.assistantSoft, display: 'inline-flex', alignItems: 'center', gap: 3, flex: 'none' }}>
            Edit <Icon name="settings" size={12} color={C.assistantSoft}/>
          </span>
        </button>

        {/* Quick filters */}
        <div className="no-scrollbar" style={{ padding: '10px 20px 4px', display: 'flex', gap: 6, overflowX: 'auto' }}>
          {filters.map(f => {
            const on = filter === f.id;
            return (
              <button key={f.id} onClick={() => setFilter(f.id)} type="button" style={{
                padding: '7px 12px', borderRadius: 999, flex: 'none',
                background: on ? C.cardElev : 'transparent',
                color: on ? C.fg1 : C.fgMuted,
                border: `1px solid ${on ? C.borderStrong : C.border}`,
                font: '600 12px/1 Inter, system-ui, sans-serif', cursor: 'pointer',
                display: 'inline-flex', alignItems: 'center', gap: 5,
              }}>
                {f.label}<span style={{ opacity: 0.65, fontWeight: 500 }}>{f.n}</span>
              </button>
            );
          })}
        </div>
      </div>

      {/* Scrollable content */}
      <div style={{ flex: 1, padding: '4px 20px 140px' }}>
        {/* Generate from files */}
        <button onClick={() => setCurating(true)} type="button" style={{
          width: '100%', display: 'flex', alignItems: 'center', gap: 10, padding: '12px 14px', marginTop: 4,
          borderRadius: 14, background: 'rgba(0,163,255,0.06)', border: '1px dashed rgba(0,163,255,0.32)',
          color: C.fg1, cursor: 'pointer', textAlign: 'left',
        }}>
          <div style={{ width: 32, height: 32, flex: 'none', borderRadius: 9, background: 'rgba(0,163,255,0.12)', border: '1px solid rgba(0,163,255,0.28)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <Icon name="cloud" size={16} color={C.blueElectric}/>
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ font: '600 14px/1.2 Inter, system-ui, sans-serif', color: C.fg1 }}>Generate from a file or URL</div>
            <div style={{ font: '400 12px/16px Inter, system-ui, sans-serif', color: C.fg2, marginTop: 2 }}>Upload a bid doc or paste a portal link.</div>
          </div>
          <Icon name="arrowRight" size={16} color={C.blueElectric}/>
        </button>

        {/* Scanning state */}
        {scan === 'scanning' && (
          <div style={{ marginTop: 12, padding: 14, background: C.card, border: `1px solid ${C.border}`, borderRadius: 16 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <Icon name="refresh" size={15} color={C.blueElectric} style={{ animation: 'spin 0.9s linear infinite' }}/>
              <span className="ai-shimmer" style={{ font: '600 13px/1 Inter, system-ui, sans-serif' }}>{scanStep || 'Scanning…'}</span>
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 12 }}>
              <LoadingShimmer height={72}/>
              <LoadingShimmer height={72} style={{ width: '92%' }}/>
            </div>
          </div>
        )}

        {scan === 'done' && (
          <>
            {/* New opportunities inbox */}
            {newOpps.length > 0 && (
              <div style={{ marginTop: 16 }}>
                <SectionHeaderInline icon="inbox" title={`New from files · ${newOpps.length}`} hint="Triage"/>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                  {newOpps.map(o => (
                    <OpportunityCard key={o.id} o={o} isNew onOpen={() => setDetail(o)} onBuild={() => setBuild(o)}
                      onDismiss={() => setDismissed(d => [...d, o.id])}/>
                  ))}
                </div>
              </div>
            )}

            {/* Ranked opportunities */}
            <div style={{ marginTop: 16 }}>
              <SectionHeaderInline icon="gavel" title={filter === 'all' ? 'All opportunities' : 'Filtered'} hint={`${shown.length} shown`}/>
              {shown.length === 0 ? (
                <OppEmptyState trades={trades} goto={goto}/>
              ) : (
                <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                  {shown.map(o => (
                    <OpportunityCard key={o.id} o={o} onOpen={() => setDetail(o)} onBuild={() => setBuild(o)}
                      onDismiss={() => setDismissed(d => [...d, o.id])}/>
                  ))}
                </div>
              )}
            </div>
          </>
        )}
      </div>

      {curating && <CurateSheet onClose={() => setCurating(false)}/>}
      {detail && <OppDetailSheet o={detail} onClose={() => setDetail(null)} onBuild={() => { setBuild(detail); setDetail(null); }}/>}
      {build && <BuildBidSheet o={build} onClose={() => setBuild(null)}/>}
    </div>
  );
}

function SectionHeaderInline({ icon, title, hint }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '4px 0 10px' }}>
      <Icon name={icon} size={14} color={C.fgMuted}/>
      <Caption>{title}</Caption>
      {hint && <span style={{ marginLeft: 'auto', font: '500 11px/1 Inter, system-ui, sans-serif', color: C.fgMuted }}>{hint}</span>}
    </div>
  );
}

// Priority label → badge tone
function PriorityLabel({ label }) {
  return <StatusPill kind={label.tone} size="sm" dot={false}>{label.t}</StatusPill>;
}

// Compact, one-handed opportunity card
function OpportunityCard({ o, onOpen, onBuild, onDismiss, isNew }) {
  const fitColor = o.fit >= 75 ? '#86EFAC' : o.fit >= 50 ? '#FCD34D' : '#FCA5A5';
  const srcLabel = { curated: 'Curated', uploaded: 'Uploaded', new: 'New file', manual: 'Manual' }[o.sourceType] || 'File';
  return (
    <Card hover onClick={onOpen} glow={isNew && o.fit >= 85 ? 'blue' : null} style={{ padding: 14 }}>
      {/* top: state + title + fit */}
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10 }}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 5, flexWrap: 'wrap' }}>
            <span style={{ padding: '3px 7px', borderRadius: 6, background: '#142131', border: `1px solid ${C.border}`, font: '700 10px/1 Inter, system-ui, sans-serif', color: C.blueElectric }}>{o.state || '—'}</span>
            <span style={{ font: '500 10px/1 Inter, system-ui, sans-serif', color: C.fgMuted, letterSpacing: '0.04em', textTransform: 'uppercase' }}>{o.market}</span>
            {o.labels[0] && <PriorityLabel label={o.labels[0]}/>}
          </div>
          <div style={{ font: '700 15px/1.25 Inter, system-ui, sans-serif', color: C.fg1, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>{o.name}</div>
        </div>
        <div style={{ textAlign: 'center', flex: 'none' }}>
          <div style={{ position: 'relative', width: 46, height: 46 }}>
            <div style={{ width: 46, height: 46, borderRadius: 999, background: `conic-gradient(${fitColor} ${o.fit * 3.6}deg, ${C.cardElev} 0)` }}/>
            <div style={{ position: 'absolute', inset: 4, borderRadius: 999, background: C.card, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
              <span className="tabular" style={{ font: '700 15px/1 Rajdhani, "Inter", system-ui, sans-serif', color: fitColor }}>{o.fit}</span>
            </div>
          </div>
          <div style={{ font: '500 8px/1 Inter, system-ui, sans-serif', color: C.fgMuted, letterSpacing: '0.1em', marginTop: 3 }}>FIT</div>
        </div>
      </div>

      {/* matched trades */}
      {o.isFit ? (
        <div style={{ display: 'flex', gap: 5, flexWrap: 'wrap', marginTop: 10 }}>
          {o.matchedLabels.map(t => (
            <span key={t} style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '3px 8px', borderRadius: 999, background: 'rgba(0,163,255,0.08)', border: '1px solid rgba(0,163,255,0.22)', color: C.blueElectric, font: '600 10px/1 Inter, system-ui, sans-serif' }}>
              <Icon name="check" size={9} color={C.blueElectric} strokeWidth={3}/>{t}
            </span>
          ))}
        </div>
      ) : (
        <div style={{ marginTop: 10, font: '500 12px/16px Inter, system-ui, sans-serif', color: C.fgMuted, display: 'inline-flex', alignItems: 'center', gap: 6 }}>
          <Icon name="xCircle" size={13} color={C.fgMuted}/> No selected trade matched
        </div>
      )}

      {/* meta row */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 10, font: '500 11px/1 Inter, system-ui, sans-serif', color: C.fgMuted, flexWrap: 'wrap' }}>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}><Icon name="calendar" size={12} color={C.fgMuted}/>{o.due ? `Due ${o.due}` : 'No due date'}</span>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}><Icon name="signal" size={12} color={C.fgMuted}/>{o.confidence}</span>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}><Icon name="fileText" size={12} color={C.fgMuted}/><span style={{ maxWidth: 120, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{srcLabel}</span></span>
      </div>

      {/* next action */}
      <div style={{ marginTop: 10, padding: '9px 12px', background: 'rgba(0,163,255,0.06)', border: '1px solid rgba(0,163,255,0.16)', borderRadius: 12, display: 'flex', alignItems: 'center', gap: 8 }}>
        <Icon name="zap" size={13} color={C.blueElectric}/>
        <span style={{ flex: 1, font: '600 12px/16px Inter, system-ui, sans-serif', color: C.fg1 }}>{o.nextAction}</span>
      </div>

      {/* action row */}
      <div style={{ display: 'flex', gap: 6, marginTop: 10 }} onClick={e => e.stopPropagation()}>
        <button onClick={onOpen} type="button" style={oppBtn(false)}>Details</button>
        {o.isFit
          ? <button onClick={onBuild} type="button" style={oppBtn(true)}>Build bid</button>
          : <button onClick={onDismiss} type="button" style={oppBtn(false)}>Dismiss</button>}
      </div>

      {isNew && (
        <div style={{ display: 'flex', gap: 6, marginTop: 6 }} onClick={e => e.stopPropagation()}>
          {['Pursue', 'Save', 'Review', 'Ignore'].map((a, i) => (
            <button key={a} onClick={a === 'Ignore' ? onDismiss : a === 'Pursue' || a === 'Save' ? () => window.ShieldTechApi?.addOpportunityToTracker?.(o.id, a === 'Pursue' ? 'Hot' : 'Warm').catch(error => window.dispatchEvent(new CustomEvent('stw:api-error', { detail: { message: error.message, code: error.code || 'TRACKER_ADD_FAILED' } }))) : onOpen} type="button" style={{
              flex: 1, height: 32, borderRadius: 9, cursor: 'pointer',
              background: i === 0 ? '#052E16' : C.cardElev,
              color: i === 0 ? '#86EFAC' : C.fg2,
              border: `1px solid ${i === 0 ? '#166534' : C.border}`,
              font: '600 11px/1 Inter, system-ui, sans-serif',
            }}>{a}</button>
          ))}
        </div>
      )}
    </Card>
  );
}

function oppBtn(primary) {
  return {
    flex: 1, height: 38, borderRadius: 11, cursor: 'pointer',
    background: primary ? 'linear-gradient(135deg,#00A3FF,#29C7FF)' : C.cardElev,
    color: primary ? '#03182A' : C.fg1,
    border: primary ? 0 : `1px solid ${C.border}`,
    font: '700 13px/1 Inter, system-ui, sans-serif',
    boxShadow: primary ? '0 0 16px rgba(0,163,255,0.24)' : 'none',
  };
}

function OppEmptyState({ trades, goto }) {
  return (
    <Card style={{ padding: 24, textAlign: 'center' }}>
      <div style={{ width: 52, height: 52, borderRadius: 16, background: C.cardElev, border: `1px solid ${C.border}`, display: 'flex', alignItems: 'center', justifyContent: 'center', color: C.fgMuted, margin: '0 auto 12px' }}>
        <Icon name="inbox" size={24}/>
      </div>
      <div style={{ font: '700 17px/1.2 Rajdhani, "Inter", system-ui, sans-serif', color: C.fg1 }}>No matching opportunities</div>
      <div style={{ font: '400 13px/19px Inter, system-ui, sans-serif', color: C.fg2, marginTop: 6, maxWidth: 260, marginLeft: 'auto', marginRight: 'auto' }}>
        Opportunities are matched to the {trades.length} trades selected in Settings. Upload bid files or adjust your trade selections.
      </div>
      <div style={{ display: 'flex', gap: 8, marginTop: 14, justifyContent: 'center' }}>
        <SecondaryButton leadingIcon="settings" onClick={() => goto('settings')}>Edit trades</SecondaryButton>
      </div>
    </Card>
  );
}

// Curate-from-URL bottom sheet — paste a portal link, extract docs + links.
// Backend: POST /opportunities/curate { url } → { name, buyer, due, docs[], links[] }
function CurateSheet({ onClose }) {
  const [url, setUrl] = React.useState('');
  const [phase, setPhase] = React.useState('input'); // input | fetching | result
  const [steps, setSteps] = React.useState([]);
  const [found, setFound] = React.useState(null);

  const start = async () => {
    const raw = url.trim();
    const u = raw.startsWith('http') ? raw : `https://${raw}`;
    if (!raw) return window.dispatchEvent(new CustomEvent('stw:api-error', { detail: { message: 'Paste a real source URL before curating.', code: 'VALIDATION_ERROR' } }));
    setUrl(u);
    setPhase('fetching');
    setSteps(['Registering source URL…']);
    try {
      const data = await window.ShieldTechApi?.curateUrl?.(u);
      const opp = data.opportunity || {};
      const doc = data.document || {};
      setFound({
        id: opp.id,
        name: opp.title || 'Saved source',
        buyer: opp.buyer || 'Needs Review',
        state: opp.state || '—',
        due: opp.dueDate ? new Date(opp.dueDate).toLocaleDateString([], { month: 'short', day: 'numeric' }) : 'Needs review',
        docs: [{ kind: 'rfp', name: doc.fileName || 'Source metadata', meta: doc.processingStatus || 'metadata saved' }],
        links: [{ kind: 'portal', label: `Open ${opp.sourceName || 'source'}`, meta: opp.sourceUrl || u }],
      });
      setSteps(p => [...p, data.message || 'Opportunity queued for review.']);
      setPhase('result');
    } catch (error) {
      window.dispatchEvent(new CustomEvent('stw:api-error', { detail: { message: error.message, code: error.code || 'CURATE_FAILED' } }));
      setPhase('input');
    }
  };

  return (
    <div onClick={onClose} style={{ position: 'absolute', inset: 0, zIndex: 90, background: 'rgba(3,5,8,0.7)', backdropFilter: 'blur(6px)', WebkitBackdropFilter: 'blur(6px)', display: 'flex', alignItems: 'flex-end' }}>
      <div onClick={e => e.stopPropagation()} className="slide-up" style={{
        width: '100%', maxHeight: '86%', overflowY: 'auto', background: C.card,
        borderTop: `1px solid ${C.borderStrong}`, borderRadius: '24px 24px 0 0',
        padding: '8px 18px 24px', boxShadow: '0 -16px 50px rgba(0,0,0,0.5)',
      }}>
        <div style={{ width: 40, height: 4, borderRadius: 999, background: C.borderStrong, margin: '8px auto 16px' }}/>

        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 14 }}>
          <div className="glow-blue" style={{ width: 38, height: 38, borderRadius: 11, background: 'rgba(0,163,255,0.12)', border: '1px solid rgba(0,163,255,0.32)', display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 'none' }}>
            <Icon name="cloud" size={18} color={C.blueElectric}/>
          </div>
          <div style={{ flex: 1 }}>
            <div style={{ font: '700 18px/1.15 Rajdhani, "Inter", system-ui, sans-serif' }}>Curate from source</div>
            <div style={{ font: '400 13px/18px Inter, system-ui, sans-serif', color: C.fg2 }}>Paste a bid or RFP link.</div>
          </div>
          <IconButton icon="x" size={32} color={C.fgMuted} bg="transparent" border="transparent" onClick={onClose}/>
        </div>

        {/* URL input */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '12px 14px', background: C.cardElev, border: `1px solid ${C.border}`, borderRadius: 14 }}>
          <Icon name="branch" size={16} color={C.fgMuted}/>
          <input value={url} onChange={e => setUrl(e.target.value)} placeholder="https://pennbid.procureware.com/…"
            onKeyDown={e => e.key === 'Enter' && phase === 'input' && start()}
            style={{ flex: 1, background: 'transparent', border: 0, outline: 'none', color: C.fg1, font: '500 14px/1 JetBrains Mono, monospace' }}/>
        </div>
        <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 8 }}>
          {['PennBid', 'NJSTART', 'Public Purchase', 'NYSCR'].map(s => (
            <span key={s} style={{ font: '500 11px/1 Inter, system-ui, sans-serif', color: C.fgMuted, padding: '5px 10px', borderRadius: 999, background: C.cardElev, border: `1px solid ${C.border}` }}>{s}</span>
          ))}
        </div>

        {/* Fetching log */}
        {phase === 'fetching' && (
          <div style={{ marginTop: 16, padding: 14, background: C.cardElev, borderRadius: 14, border: `1px solid ${C.border}` }}>
            {steps.map((s, i) => (
              <div key={i} className="fade-in" style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 0' }}>
                <Icon name="check" size={13} color="#86EFAC" strokeWidth={2.5}/>
                <span style={{ font: '500 12px/18px JetBrains Mono, monospace', color: C.fg2 }}>{s}</span>
              </div>
            ))}
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '6px 0 2px' }}>
              <TypingDots color={C.blueElectric}/>
              <span className="ai-shimmer" style={{ font: '600 12px/18px Inter, system-ui, sans-serif' }}>Curating opportunity…</span>
            </div>
          </div>
        )}

        {/* Result */}
        {phase === 'result' && found && (
          <div className="fade-in" style={{ marginTop: 16 }}>
            <div style={{ padding: 14, background: C.cardElev, borderRadius: 14, border: `1px solid ${C.border}` }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                <span style={{ padding: '3px 8px', borderRadius: 6, background: '#142131', border: `1px solid ${C.border}`, font: '700 10px/1 Inter, system-ui, sans-serif', color: C.blueElectric }}>{found.state}</span>
                <Caption>Found · due {found.due}</Caption>
              </div>
              <div style={{ font: '700 16px/1.2 Inter, system-ui, sans-serif', marginTop: 6 }}>{found.name}</div>
              <div style={{ font: '400 13px/18px Inter, system-ui, sans-serif', color: C.fg2, marginTop: 2 }}>{found.buyer}</div>
            </div>

            <div style={{ marginTop: 10 }}>
              <Caption>{found.docs.length} files extracted</Caption>
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginTop: 8 }}>
                {found.docs.map((d, i) => {
                  const m = DOC_META[d.kind] || DOC_META.form;
                  return (
                    <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 10px', background: '#0B1118', borderRadius: 12, border: `1px solid ${C.border}` }}>
                      <div style={{ width: 30, height: 30, flex: 'none', borderRadius: 8, background: 'rgba(255,255,255,0.03)', border: `1px solid ${C.border}`, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                        <Icon name={m.icon} size={14} color={m.tint}/>
                      </div>
                      <div style={{ flex: 1, minWidth: 0 }}>
                        <div style={{ font: '600 13px/1.25 Inter, system-ui, sans-serif', color: C.fg1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{d.name}</div>
                        <div style={{ font: '500 11px/1.3 JetBrains Mono, monospace', color: C.fgMuted, marginTop: 2 }}>{d.meta}</div>
                      </div>
                      <Icon name="check" size={14} color="#86EFAC" strokeWidth={2.5}/>
                    </div>
                  );
                })}
              </div>
            </div>

            <div style={{ marginTop: 16 }}>
              <PrimaryButton full glow leadingIcon="plus" onClick={async () => {
                try {
                  await window.ShieldTechApi?.addOpportunityToTracker?.(found.id, 'Hot');
                  onClose();
                } catch (error) {
                  window.dispatchEvent(new CustomEvent('stw:api-error', { detail: { message: error.message, code: error.code || 'TRACKER_ADD_FAILED' } }));
                }
              }}>Add to bid tracker</PrimaryButton>
            </div>
          </div>
        )}

        {/* Action */}
        {phase === 'input' && (
          <div style={{ marginTop: 16 }}>
            <PrimaryButton full glow leadingIcon="cloud" onClick={start}>Curate documents & links</PrimaryButton>
          </div>
        )}
      </div>
    </div>
  );
}

function PublicBidCard({ b }) {
  const fitColor = b.fit >= 70 ? '#86EFAC' : b.fit >= 40 ? '#FCD34D' : '#FCA5A5';
  return (
    <Card hover style={{ padding: 16 }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginBottom: 10 }}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
            <span style={{ padding: '3px 8px', borderRadius: 6, background: '#142131', border: `1px solid ${C.border}`, font: '700 10px/1 Inter, system-ui, sans-serif', color: C.blueElectric, letterSpacing: '0.04em' }}>{b.state}</span>
            <span style={{ font: '500 11px/1 Inter, system-ui, sans-serif', color: C.fgMuted, letterSpacing: '0.04em', textTransform: 'uppercase' }}>{b.source}</span>
            {b.sourceLink && <Icon name="arrowUpRight" size={12} color={C.fgMuted}/>}
          </div>
          <div style={{ font: '700 16px/1.2 Inter, system-ui, sans-serif', color: C.fg1 }}>{b.name}</div>
          <div style={{ font: '400 13px/18px Inter, system-ui, sans-serif', color: C.fg2, marginTop: 2 }}>{b.buyer}</div>
        </div>
        <div style={{ textAlign: 'right', flex: 'none' }}>
          <Caption>Fit</Caption>
          <div className="tabular" style={{ font: '700 24px/1 Rajdhani, "Inter", system-ui, sans-serif', color: fitColor, marginTop: 4 }}>{b.fit}</div>
        </div>
      </div>

      {/* Dates */}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, marginTop: 8 }}>
        <DueBlock label="Due" value={b.due} urgent={b.status === 'Decision'}/>
        <DueBlock label="Pre-bid" value={b.preBid}/>
      </div>

      {/* Scope */}
      <div style={{ marginTop: 10, padding: '10px 12px', background: C.cardElev, borderRadius: 12 }}>
        <Caption>Scope</Caption>
        <div style={{ font: '500 13px/18px Inter, system-ui, sans-serif', color: C.fg2, marginTop: 4 }}>{b.scope}</div>
      </div>

      {/* Risk flags */}
      {b.flags.length > 0 && (
        <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 10 }}>
          {b.flags.map(f => (
            <span key={f} style={{
              display: 'inline-flex', alignItems: 'center', gap: 4,
              padding: '4px 9px', borderRadius: 999,
              background: '#3A2506', color: '#FCD34D', border: '1px solid #92400E',
              font: '600 11px/1 Inter, system-ui, sans-serif',
            }}>
              <Icon name="alertTriangle" size={11} color="#FCD34D"/> {f}
            </span>
          ))}
        </div>
      )}

      {/* Documents + links */}
      <BidDocuments b={b}/>

      {/* Footer */}
      <div style={{ marginTop: 12, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
        <div>
          <Caption>Strategy</Caption>
          <div style={{ font: '600 13px/1 Inter, system-ui, sans-serif', color: C.fg1, marginTop: 4 }}>{b.strategy}</div>
        </div>
        <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
          <StatusPill kind={b.status === 'No-bid likely' ? 'dead' : b.status === 'Decision' ? 'warm' : 'nurture'} size="sm" dot={false}>{b.status}</StatusPill>
        </div>
      </div>

      <div style={{ marginTop: 10, padding: '10px 12px', background: 'rgba(0,163,255,0.06)', border: '1px solid rgba(0,163,255,0.18)', borderRadius: 12 }}>
        <Caption color={C.blue}>Next</Caption>
        <div style={{ font: '600 14px/20px Inter, system-ui, sans-serif', color: C.fg1, marginTop: 2 }}>{b.next}</div>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, marginTop: 10 }}>
        <SecondaryButton full leadingIcon="x" onClick={() => window.ShieldTechApi?.addOpportunityToTracker?.(b.id, 'Dead').catch(error => window.dispatchEvent(new CustomEvent('stw:api-error', { detail: { message: error.message, code: error.code || 'NO_BID_FAILED' } })))}>No-bid</SecondaryButton>
        <PrimaryButton full leadingIcon="check" onClick={() => window.ShieldTechApi?.addOpportunityToTracker?.(b.id, 'Hot').catch(error => window.dispatchEvent(new CustomEvent('stw:api-error', { detail: { message: error.message, code: error.code || 'BID_TRACK_FAILED' } })))}>Bid</PrimaryButton>
      </div>
    </Card>
  );
}

function DueBlock({ label, value, urgent }) {
  return (
    <div style={{ padding: '10px 12px', background: urgent ? '#1A1306' : C.cardElev, border: `1px solid ${urgent ? '#92400E' : C.border}`, borderRadius: 12 }}>
      <Caption color={urgent ? '#FCD34D' : C.fgMuted}>{label}</Caption>
      <div style={{ font: '700 14px/1 Inter, system-ui, sans-serif', color: urgent ? '#FCD34D' : C.fg1, marginTop: 4, fontVariantNumeric: 'tabular-nums' }}>{value}</div>
    </div>
  );
}

// Doc-kind → icon + accent
const DOC_META = {
  rfp:      { icon: 'fileText', tint: '#29C7FF', label: 'RFP' },
  drawing:  { icon: 'layers',   tint: '#FCD34D', label: 'Drawing' },
  spec:     { icon: 'shield',   tint: '#D8B4FE', label: 'Spec' },
  form:     { icon: 'edit',     tint: '#94A3B8', label: 'Form' },
  addendum: { icon: 'alertTriangle', tint: '#FCD34D', label: 'Addendum' },
};
const LINK_META = {
  portal:   { icon: 'building' },
  qa:       { icon: 'inbox' },
  planroom: { icon: 'users' },
  request:  { icon: 'mail' },
};

function BidDocuments({ b }) {
  const [docs, setDocs] = React.useState(b.docs || []);
  const [links, setLinks] = React.useState(b.links || []);
  const [open, setOpen] = React.useState(false);
  const [syncState, setSyncState] = React.useState('idle'); // idle | fetching | done
  const [synced, setSynced] = React.useState(b.synced || 'never');
  const fileInputRef = React.useRef(null);

  if ((b.docs || []).length === 0 && (b.links || []).length === 0 && !b.sourceUrl) return null;

  const newCount = docs.filter(d => d.isNew).length;
  const visible = open ? docs : docs.slice(0, 2);

  const resync = async () => {
    if (syncState === 'fetching') return;
    setSyncState('fetching');
    try {
      await window.ShieldTechApi?.resyncOpportunity?.(b.id);
      setSyncState('done');
      setSynced('just now');
    } catch (error) {
      window.dispatchEvent(new CustomEvent('stw:api-error', { detail: { message: error.message, code: error.code || 'RESYNC_FAILED' } }));
      setSyncState('idle');
      return;
    }
    setTimeout(() => setSyncState('idle'), 1600);
  };

  const uploadAttachment = async (file) => {
    if (!file) return;
    setSyncState('fetching');
    try {
      await window.ShieldTechApi?.uploadFile?.(file);
      setTimeout(() => setSyncState('idle'), 1600);
    } catch (error) {
      window.dispatchEvent(new CustomEvent('stw:api-error', { detail: { message: error.message, code: error.code || 'UPLOAD_FAILED' } }));
      setSyncState('idle');
    }
  };

  const addLink = async () => {
    const value = window.prompt('Paste a source URL to curate');
    if (!value) return;
    try {
      await window.ShieldTechApi?.curateUrl?.(value.startsWith('http') ? value : `https://${value}`);
    } catch (error) {
      window.dispatchEvent(new CustomEvent('stw:api-error', { detail: { message: error.message, code: error.code || 'LINK_CURATE_FAILED' } }));
    }
  };

  const host = b.portal?.host || (b.sourceUrl || '').replace(/^https?:\/\//, '').split('/')[0];

  return (
    <div style={{ marginTop: 12, padding: 12, background: C.cardElev, borderRadius: 16, border: `1px solid ${C.border}` }}>
      {/* Curation source bar */}
      {b.sourceUrl && (
        <div style={{ marginBottom: 12, padding: 10, borderRadius: 12, background: 'rgba(0,163,255,0.06)', border: '1px solid rgba(0,163,255,0.18)' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <div style={{ width: 28, height: 28, flex: 'none', borderRadius: 8, background: 'rgba(0,163,255,0.10)', border: '1px solid rgba(0,163,255,0.22)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
              <Icon name={syncState === 'fetching' ? 'refresh' : 'cloud'} size={14} color={C.blueElectric} style={syncState === 'fetching' ? { animation: 'spin 0.9s linear infinite' } : {}}/>
            </div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                <Caption color={C.blue}>Curated from source</Caption>
                {b.autoSync && <span style={{ font: '600 9px/1 Inter, system-ui, sans-serif', color: '#86EFAC', letterSpacing: '0.06em', textTransform: 'uppercase' }}>· Auto</span>}
              </div>
              <a href={b.sourceUrl} target="_blank" rel="noopener noreferrer" style={{ display: 'block', font: '500 11px/1.3 JetBrains Mono, monospace', color: C.fg2, marginTop: 3, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', textDecoration: 'none' }}>
                {host}
              </a>
            </div>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 8 }}>
            <span style={{ font: '400 11px/1 Inter, system-ui, sans-serif', color: syncState === 'fetching' ? C.blueElectric : C.fgMuted, flex: 1 }}>
              {syncState === 'fetching' ? 'Pulling files & links from portal…'
                : syncState === 'done' ? 'Up to date — no new documents'
                : `Synced ${synced} · ${docs.length} files, ${links.length} links`}
            </span>
            <button onClick={resync} type="button" disabled={syncState === 'fetching'} style={{
              height: 30, padding: '0 12px', borderRadius: 9, flex: 'none',
              background: syncState === 'done' ? '#052E16' : C.card,
              color: syncState === 'done' ? '#86EFAC' : C.blueElectric,
              border: `1px solid ${syncState === 'done' ? '#166534' : 'rgba(0,163,255,0.28)'}`,
              font: '600 12px/1 Inter, system-ui, sans-serif',
              cursor: syncState === 'fetching' ? 'wait' : 'pointer',
              display: 'inline-flex', alignItems: 'center', gap: 6,
            }}>
              <Icon name={syncState === 'done' ? 'check' : 'refresh'} size={13}
                color={syncState === 'done' ? '#86EFAC' : C.blueElectric}
                style={syncState === 'fetching' ? { animation: 'spin 0.9s linear infinite' } : {}}/>
              {syncState === 'fetching' ? 'Syncing' : syncState === 'done' ? 'Synced' : 'Re-sync'}
            </button>
          </div>
        </div>
      )}

      {/* Header */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
        <Icon name="paperclip" size={14} color={C.fgMuted}/>
        <Caption>Documents</Caption>
        <span style={{ font: '600 11px/1 Inter, system-ui, sans-serif', color: C.fgMuted }}>{docs.length}</span>
        {newCount > 0 && (
          <span style={{ padding: '2px 7px', borderRadius: 999, background: '#3A2506', color: '#FCD34D', border: '1px solid #92400E', font: '700 10px/1 Inter, system-ui, sans-serif', letterSpacing: '0.04em', textTransform: 'uppercase' }}>
            {newCount} new
          </span>
        )}
      </div>

      {/* Fetching skeleton */}
      {syncState === 'fetching' && (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 6, marginBottom: 6 }}>
          <LoadingShimmer height={46}/>
          <LoadingShimmer height={46} style={{ width: '88%' }}/>
        </div>
      )}

      {/* Doc rows */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6, opacity: syncState === 'fetching' ? 0.5 : 1, transition: 'opacity 200ms ease' }}>
        {visible.map(d => <DocRow key={d.id} d={d}/>)}
      </div>

      {docs.length > 2 && (
        <button onClick={() => setOpen(!open)} type="button" style={{
          marginTop: 8, width: '100%', padding: '8px', borderRadius: 10,
          background: 'transparent', border: `1px solid ${C.border}`, color: C.fg2,
          font: '600 12px/1 Inter, system-ui, sans-serif', cursor: 'pointer',
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 6,
        }}>
          {open ? 'Show less' : `Show ${docs.length - 2} more`}
          <Icon name={open ? 'chevronUp' : 'chevronDown'} size={14} color={C.fgMuted}/>
        </button>
      )}

      {/* Links */}
      {links.length > 0 && (
        <div style={{ marginTop: 10, paddingTop: 10, borderTop: `1px solid ${C.border}` }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
            <Icon name="branch" size={13} color={C.fgMuted}/>
            <Caption>Links</Caption>
            <span style={{ font: '600 11px/1 Inter, system-ui, sans-serif', color: C.fgMuted }}>{links.length}</span>
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
            {links.map(l => <BidLinkRow key={l.id} l={l}/>)}
          </div>
        </div>
      )}

      {/* Attach */}
      <div style={{ marginTop: 10, display: 'flex', gap: 6 }}>
        <input ref={fileInputRef} type="file" style={{ display: 'none' }} onChange={e => uploadAttachment(e.target.files?.[0])}/>
        <button type="button" onClick={() => fileInputRef.current?.click()} style={attachBtnStyle}>
          <Icon name="plus" size={13} color={C.fg2}/> Add file
        </button>
        <button type="button" onClick={addLink} style={attachBtnStyle}>
          <Icon name="paperclip" size={13} color={C.fg2}/> Add link
        </button>
      </div>
    </div>
  );
}

const attachBtnStyle = {
  flex: 1, height: 36, borderRadius: 10, background: 'transparent',
  border: `1px dashed ${C.borderStrong}`, color: C.fg2,
  font: '600 12px/1 Inter, system-ui, sans-serif', cursor: 'pointer',
  display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 6,
};

function DocRow({ d }) {
  const m = DOC_META[d.kind] || DOC_META.form;
  const meta = [d.type, d.size, d.pages ? `${d.pages} pp` : d.sheets ? `${d.sheets} sheets` : null]
    .filter(Boolean).join(' · ');
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 10px', background: '#0B1118', borderRadius: 12, border: `1px solid ${d.isNew ? '#92400E' : C.border}` }}>
      <div style={{ width: 32, height: 32, flex: 'none', borderRadius: 9, background: 'rgba(255,255,255,0.03)', border: `1px solid ${C.border}`, display: 'flex', alignItems: 'center', justifyContent: 'center', position: 'relative' }}>
        <Icon name={m.icon} size={15} color={m.tint}/>
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
          <span style={{ font: '600 13px/1.25 Inter, system-ui, sans-serif', color: C.fg1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{d.name}</span>
          {d.isNew && <span style={{ flex: 'none', font: '700 9px/1 Inter, system-ui, sans-serif', color: '#FCD34D', letterSpacing: '0.06em', textTransform: 'uppercase' }}>New</span>}
        </div>
        <div style={{ font: '500 11px/1.3 JetBrains Mono, monospace', color: C.fgMuted, marginTop: 3, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
          {meta}{d.flag ? ` · ${d.flag}` : ''}
        </div>
      </div>
      {d.from === 'source' && (
        <span title="Pulled from the opportunity source" style={{ flex: 'none', display: 'inline-flex', alignItems: 'center', gap: 3, padding: '3px 7px', borderRadius: 999, background: 'rgba(0,163,255,0.08)', border: '1px solid rgba(0,163,255,0.20)', color: C.blueElectric, font: '600 9px/1 Inter, system-ui, sans-serif', letterSpacing: '0.04em', textTransform: 'uppercase' }}>
          <Icon name="cloud" size={10} color={C.blueElectric}/> Source
        </span>
      )}
      <div style={{ display: 'flex', gap: 4, flex: 'none' }}>
        <button type="button" title="Open" onClick={() => window.dispatchEvent(new CustomEvent('stw:api-error', { detail: { message: 'This document record is metadata only. Open the source portal link or upload the file to view it.', code: 'DOCUMENT_METADATA_ONLY' } }))} style={iconBtnStyle}>
          <Icon name="eye" size={14} color={C.fg2}/>
        </button>
        <button type="button" title="Download" onClick={() => window.dispatchEvent(new CustomEvent('stw:api-error', { detail: { message: 'No stored file binary is available for download yet. Metadata was saved without faking a file.', code: 'DOCUMENT_BINARY_MISSING' } }))} style={iconBtnStyle}>
          <Icon name="download" size={14} color={C.fg2}/>
        </button>
      </div>
    </div>
  );
}

const iconBtnStyle = {
  width: 30, height: 30, borderRadius: 8, background: C.cardElev,
  border: `1px solid ${C.border}`, display: 'flex', alignItems: 'center',
  justifyContent: 'center', cursor: 'pointer', padding: 0,
};

function BidLinkRow({ l }) {
  const m = LINK_META[l.kind] || LINK_META.portal;
  const dead = !l.url;
  return (
    <a href={dead ? undefined : `https://${l.url}`} target="_blank" rel="noopener noreferrer" style={{
      display: 'flex', alignItems: 'center', gap: 10, padding: '8px 10px', borderRadius: 12,
      background: 'transparent', border: `1px solid ${C.border}`, textDecoration: 'none',
      cursor: dead ? 'default' : 'pointer',
    }}>
      <div style={{ width: 28, height: 28, flex: 'none', borderRadius: 8, background: 'rgba(0,163,255,0.08)', border: '1px solid rgba(0,163,255,0.20)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <Icon name={m.icon} size={14} color={C.blueElectric}/>
      </div>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ font: '600 13px/1.25 Inter, system-ui, sans-serif', color: dead ? C.fg2 : C.blueElectric, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{l.label}</div>
        {l.meta && <div style={{ font: '400 11px/1.3 Inter, system-ui, sans-serif', color: C.fgMuted, marginTop: 2 }}>{l.meta}</div>}
      </div>
      <Icon name={dead ? 'mail' : 'arrowUpRight'} size={15} color={C.fgMuted} style={{ flex: 'none' }}/>
    </a>
  );
}

// ─────────────────────────────────────────────────────────────
// OPPORTUNITY DETAIL — full breakdown (bottom sheet)
// ─────────────────────────────────────────────────────────────
function OppDetailSheet({ o, onClose, onBuild }) {
  const fitColor = o.fit >= 75 ? '#86EFAC' : o.fit >= 50 ? '#FCD34D' : '#FCA5A5';
  const srcLabel = { curated: 'Curated file', uploaded: 'Uploaded file', new: 'New file', manual: 'Manual import' }[o.sourceType] || 'File';
  return (
    <SheetShell onClose={onClose}>
      {/* Header */}
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 8, marginBottom: 10 }}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 6 }}>
            {o.labels.slice(0, 3).map((l, i) => <PriorityLabel key={i} label={l}/>)}
          </div>
          <div style={{ font: '700 20px/1.18 Rajdhani, "Inter", system-ui, sans-serif', letterSpacing: '-0.01em' }}>{o.name}</div>
          <div style={{ font: '400 13px/18px Inter, system-ui, sans-serif', color: C.fg2, marginTop: 3 }}>{o.buyer} · {o.market}</div>
        </div>
        <IconButton icon="x" size={32} color={C.fgMuted} bg="transparent" border="transparent" onClick={onClose}/>
      </div>

      {/* Score trio */}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 8 }}>
        <ScoreTile label="Fit score" value={o.fit} suffix="/100" color={fitColor}/>
        <ScoreTile label="Confidence" value={o.confidence} text color={o.confidence === 'High' ? '#86EFAC' : o.confidence === 'Medium' ? '#FCD34D' : '#FCA5A5'}/>
        <ScoreTile label="Bid readiness" value={o.ready} suffix="%" color={o.ready >= 70 ? '#86EFAC' : '#FCD34D'}/>
      </div>

      {/* Matched trades + why */}
      <div style={{ marginTop: 12, padding: 12, background: 'rgba(0,163,255,0.06)', border: '1px solid rgba(0,163,255,0.18)', borderRadius: 14 }}>
        <Caption color={C.blue}>Matched trades</Caption>
        {o.isFit ? (
          <>
            <div style={{ display: 'flex', gap: 5, flexWrap: 'wrap', marginTop: 8 }}>
              {o.matchedLabels.map(t => (
                <span key={t} style={{ padding: '4px 9px', borderRadius: 999, background: '#142131', border: `1px solid ${C.border}`, color: C.blueElectric, font: '600 11px/1 Inter, system-ui, sans-serif' }}>{t}</span>
              ))}
            </div>
            <div style={{ font: '400 12px/17px Inter, system-ui, sans-serif', color: C.fg2, marginTop: 8 }}>
              Matched because the scope mentions {o.matchReasons.slice(0, 4).join(', ')}.
            </div>
          </>
        ) : (
          <div style={{ font: '400 12px/17px Inter, system-ui, sans-serif', color: C.fg2, marginTop: 6 }}>
            No selected trade matched this file. Adjust trades in Settings to capture it.
          </div>
        )}
      </div>

      {/* Key facts */}
      <div style={{ marginTop: 12, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
        <Fact label="Due date" value={o.due || 'Not found'} warn={!o.due}/>
        <Fact label="Site walk" value={o.preBid || 'Not listed'}/>
        <Fact label="Location" value={o.state || 'Unknown'}/>
        <Fact label="Strategy" value={o.strategy || 'TBD'}/>
      </div>

      {/* Scope */}
      <div style={{ marginTop: 12 }}>
        <Caption>Scope summary</Caption>
        <div style={{ marginTop: 6, padding: 12, background: C.cardElev, borderRadius: 12, font: '400 13px/19px Inter, system-ui, sans-serif', color: C.fg2 }}>{o.scope || 'No scope details found — open source file.'}</div>
      </div>

      {/* Missing info */}
      {o.completeness.missing.length > 0 && (
        <div style={{ marginTop: 12, padding: 12, background: '#1A1306', border: '1px solid #92400E', borderRadius: 14 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <Icon name="alertTriangle" size={13} color="#FCD34D"/>
            <Caption color="#FCD34D">Missing information</Caption>
          </div>
          <div style={{ display: 'flex', gap: 5, flexWrap: 'wrap', marginTop: 8 }}>
            {o.completeness.missing.map(m => (
              <span key={m} style={{ padding: '3px 8px', borderRadius: 999, background: '#3A2506', color: '#FCD34D', border: '1px solid #92400E', font: '600 10px/1 Inter, system-ui, sans-serif' }}>{m}</span>
            ))}
          </div>
        </div>
      )}

      {/* Documents + links + source curation (reuses existing engine) */}
      <BidDocuments b={o}/>

      {/* Next action */}
      <div style={{ marginTop: 12, padding: '11px 14px', background: 'rgba(0,163,255,0.08)', border: '1px solid rgba(0,163,255,0.24)', borderRadius: 14, display: 'flex', alignItems: 'center', gap: 10 }}>
        <Icon name="zap" size={16} color={C.blueElectric}/>
        <div style={{ flex: 1 }}>
          <Caption color={C.blue}>Recommended next action</Caption>
          <div style={{ font: '700 14px/18px Inter, system-ui, sans-serif', color: C.fg1, marginTop: 2 }}>{o.nextAction}</div>
        </div>
      </div>

      {/* Actions */}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, marginTop: 14 }}>
        <SecondaryButton full leadingIcon="plus" onClick={async () => {
          try {
            await window.ShieldTechApi?.addOpportunityToTracker?.(o.id, 'Hot');
            onClose();
          } catch (error) {
            window.dispatchEvent(new CustomEvent('stw:api-error', { detail: { message: error.message, code: error.code || 'TRACKER_ADD_FAILED' } }));
          }
        }}>Add to tracker</SecondaryButton>
        {o.isFit
          ? <PrimaryButton full glow leadingIcon="layers" onClick={onBuild}>Build bid</PrimaryButton>
          : <SecondaryButton full leadingIcon="x" onClick={onClose}>Dismiss</SecondaryButton>}
      </div>
    </SheetShell>
  );
}

// ─────────────────────────────────────────────────────────────
// BUILD BID — structure the opportunity into a working bid package
// using selected trades + extracted scope.
// ─────────────────────────────────────────────────────────────
function BuildBidSheet({ o, onClose }) {
  const [busy, setBusy] = React.useState(true);
  const [bidPackage, setBidPackage] = React.useState(null);
  React.useEffect(() => {
    let alive = true;
    window.ShieldTechApi?.buildOpportunity?.(o.id)
      .then(data => { if (alive) setBidPackage(data.bidPackage); })
      .catch(error => window.dispatchEvent(new CustomEvent('stw:api-error', { detail: { message: error.message, code: error.code || 'BID_BUILD_FAILED' } })))
      .finally(() => { if (alive) setBusy(false); });
    return () => { alive = false; };
  }, [o.id]);
  const rfis = o.completeness.missing.map(m => `Confirm ${m.toLowerCase()} with procurement`);
  if (o.preBid && !/waived|tbd|—/i.test(o.preBid)) rfis.unshift('Confirm attendance for mandatory site walk');
  const exportDraft = () => {
    const payload = bidPackage || window.ShieldTechApi?.state?.lastBidPackage;
    if (!payload) return window.dispatchEvent(new CustomEvent('stw:api-error', { detail: { message: 'Bid package has not finished building yet.', code: 'BID_BUILD_PENDING' } }));
    const blob = new Blob([JSON.stringify({ opportunity: o, bidPackage: payload }, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `${o.id}-bid-package.json`;
    a.click();
    URL.revokeObjectURL(url);
  };
  return (
    <SheetShell onClose={onClose}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}>
        <div className="glow-blue" style={{ width: 38, height: 38, borderRadius: 11, background: 'rgba(0,163,255,0.12)', border: '1px solid rgba(0,163,255,0.32)', display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 'none' }}>
          <Icon name="layers" size={18} color={C.blueElectric}/>
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ font: '700 18px/1.15 Rajdhani, "Inter", system-ui, sans-serif' }}>Build bid package</div>
          <div style={{ font: '400 12px/16px Inter, system-ui, sans-serif', color: C.fg2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{busy ? 'Building from verified source records…' : o.name}</div>
        </div>
        <IconButton icon="x" size={32} color={C.fgMuted} bg="transparent" border="transparent" onClick={onClose}/>
      </div>

      {/* Readiness banner */}
      <div style={{ padding: 12, background: C.cardElev, borderRadius: 14, border: `1px solid ${C.border}` }}>
        <LinearProgress value={o.ready} total={100} label="Bid readiness" sublabel={`${o.ready}%`}/>
        <div style={{ font: '400 12px/16px Inter, system-ui, sans-serif', color: C.fgMuted, marginTop: 8 }}>
          Built from {o.matchedLabels.length} matched trade{o.matchedLabels.length !== 1 ? 's' : ''} and the extracted scope. Pricing is not included.
        </div>
      </div>

      <BuildSection title="Included trades" icon="check" row>
        {o.matchedLabels.map(t => <BuildChip key={t} text={t} ok/>)}
      </BuildSection>

      <BuildSection title="Scope of work" icon="fileText">
        <div style={{ font: '400 13px/19px Inter, system-ui, sans-serif', color: C.fg2 }}>{o.scope}</div>
      </BuildSection>

      <BuildSection title="Labor categories" icon="hardHat">
        {o.labor.length ? o.labor.map((l, i) => <BuildLine key={i} text={l}/>) : <Muted>Add trades in Settings to populate.</Muted>}
      </BuildSection>

      <BuildSection title="Material categories" icon="inbox">
        {o.materials.length ? o.materials.map((m, i) => <BuildLine key={i} text={m}/>) : <Muted>—</Muted>}
      </BuildSection>

      <BuildSection title="Assumptions & exclusions" icon="alert">
        <BuildLine text="Assumes normal business-hours access unless noted" />
        <BuildLine text="Excludes line-voltage, conduit, and patching by others" />
        <BuildLine text="Excludes permits/fees unless required by spec" />
      </BuildSection>

      <BuildSection title="Risk flags" icon="alertTriangle" row>
        {(o.flags || []).length ? (o.flags || []).map(f => <BuildChip key={f} text={f} warn/>) : <Muted>None detected</Muted>}
      </BuildSection>

      <BuildSection title="Questions / RFIs" icon="mail">
        {rfis.length ? rfis.map((r, i) => <BuildLine key={i} text={r}/>) : <Muted>None — scope is clear.</Muted>}
      </BuildSection>

      <BuildSection title="Pricing" icon="gavel">
        <div style={{ padding: '10px 12px', background: '#1A1306', border: '1px solid #92400E', borderRadius: 12, font: '600 12px/17px Inter, system-ui, sans-serif', color: '#FCD34D' }}>
          Pricing data not found in source files — estimate required.
        </div>
      </BuildSection>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, marginTop: 14 }}>
        <SecondaryButton full leadingIcon="download" onClick={exportDraft}>Export draft</SecondaryButton>
        <PrimaryButton full glow leadingIcon="plus" onClick={async () => {
          try {
            await window.ShieldTechApi?.addOpportunityToTracker?.(o.id, 'Hot');
            onClose();
          } catch (error) {
            window.dispatchEvent(new CustomEvent('stw:api-error', { detail: { message: error.message, code: error.code || 'TRACKER_ADD_FAILED' } }));
          }
        }}>Add to tracker</PrimaryButton>
      </div>
    </SheetShell>
  );
}

// Shared bottom-sheet shell
function SheetShell({ children, onClose }) {
  return (
    <div onClick={onClose} style={{ position: 'absolute', inset: 0, zIndex: 90, background: 'rgba(3,5,8,0.7)', backdropFilter: 'blur(6px)', WebkitBackdropFilter: 'blur(6px)', display: 'flex', alignItems: 'flex-end' }}>
      <div onClick={e => e.stopPropagation()} className="slide-up" style={{
        width: '100%', maxHeight: '90%', overflowY: 'auto', background: C.card,
        borderTop: `1px solid ${C.borderStrong}`, borderRadius: '24px 24px 0 0',
        padding: '8px 18px 28px', boxShadow: '0 -16px 50px rgba(0,0,0,0.5)',
      }}>
        <div style={{ width: 40, height: 4, borderRadius: 999, background: C.borderStrong, margin: '8px auto 16px' }}/>
        {children}
      </div>
    </div>
  );
}

function ScoreTile({ label, value, suffix, color, text }) {
  return (
    <div style={{ padding: '10px 8px', background: C.cardElev, border: `1px solid ${C.border}`, borderRadius: 14, textAlign: 'center' }}>
      <div className="tabular" style={{ font: `700 ${text ? 16 : 22}px/1 Rajdhani, "Inter", system-ui, sans-serif`, color }}>{value}<span style={{ font: '600 11px/1 Inter, system-ui, sans-serif', color: C.fgMuted }}>{suffix}</span></div>
      <div style={{ font: '500 9px/1.2 Inter, system-ui, sans-serif', color: C.fgMuted, letterSpacing: '0.06em', textTransform: 'uppercase', marginTop: 5 }}>{label}</div>
    </div>
  );
}

function Fact({ label, value, warn }) {
  return (
    <div style={{ padding: '10px 12px', background: C.cardElev, border: `1px solid ${warn ? '#92400E' : C.border}`, borderRadius: 12 }}>
      <Caption color={warn ? '#FCD34D' : C.fgMuted}>{label}</Caption>
      <div style={{ font: '600 13px/1.2 Inter, system-ui, sans-serif', color: warn ? '#FCD34D' : C.fg1, marginTop: 4 }}>{value}</div>
    </div>
  );
}

function BuildSection({ title, icon, children, row }) {
  return (
    <div style={{ marginTop: 12 }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
        <Icon name={icon} size={14} color={C.fgMuted}/>
        <Caption>{title}</Caption>
      </div>
      <div style={{ display: 'flex', flexDirection: row ? 'row' : 'column', flexWrap: row ? 'wrap' : 'nowrap', gap: 6 }}>{children}</div>
    </div>
  );
}
function BuildLine({ text }) {
  return (
    <div style={{ display: 'flex', alignItems: 'flex-start', gap: 8 }}>
      <span style={{ width: 5, height: 5, borderRadius: 999, background: C.blueElectric, marginTop: 7, flex: 'none' }}/>
      <span style={{ font: '400 13px/19px Inter, system-ui, sans-serif', color: C.fg2 }}>{text}</span>
    </div>
  );
}
function BuildChip({ text, ok, warn }) {
  const c = ok ? { bg: '#052E16', fg: '#86EFAC', br: '#166534' } : warn ? { bg: '#3A2506', fg: '#FCD34D', br: '#92400E' } : { bg: C.cardElev, fg: C.fg2, br: C.border };
  return <span style={{ display: 'inline-flex', marginRight: 6, padding: '4px 10px', borderRadius: 999, background: c.bg, color: c.fg, border: `1px solid ${c.br}`, font: '600 11px/1 Inter, system-ui, sans-serif' }}>{text}</span>;
}
function Muted({ children }) {
  return <span style={{ font: '400 12px/17px Inter, system-ui, sans-serif', color: C.fgMuted }}>{children}</span>;
}

// ─────────────────────────────────────────────────────────────
// CONTRACTOR / LIST BUILDER
// ─────────────────────────────────────────────────────────────
function ScreenContractors({ goto }) {
  const cats = ['All', ...Array.from(new Set(CONTRACTORS.map(c => c.cat)))];
  const [cat, setCat] = React.useState('All');
  const filtered = cat === 'All' ? CONTRACTORS : CONTRACTORS.filter(c => c.cat === cat);

  // group by category if "All"
  const grouped = cat === 'All'
    ? Array.from(new Set(filtered.map(c => c.cat))).map(c => ({ cat: c, items: filtered.filter(x => x.cat === c) }))
    : [{ cat, items: filtered }];

  return (
    <div className="slide-up" style={{ padding: '0 0 140px' }}>
      <MobileHeader subtitle="LISTS" title={`${CONTRACTORS.length} targets`}
        trailing={null}/>

      <div style={{ padding: '4px 20px 0', font: '400 14px/20px Inter, system-ui, sans-serif', color: C.fg2 }}>
        Hand-built list of who to call. Don’t outsource the list.
      </div>

      <div className="no-scrollbar" style={{ padding: '12px 20px 8px', display: 'flex', gap: 6, overflowX: 'auto' }}>
        {cats.map(s => {
          const on = cat === s;
          return (
            <button key={s} onClick={() => setCat(s)} type="button" style={{
              padding: '8px 14px', borderRadius: 999,
              background: on ? C.cardElev : 'transparent',
              color: on ? C.fg1 : C.fgMuted,
              border: `1px solid ${on ? C.borderStrong : C.border}`,
              font: '600 13px/1 Inter, system-ui, sans-serif', cursor: 'pointer', flex: 'none',
              whiteSpace: 'nowrap',
            }}>{s}</button>
          );
        })}
      </div>

      {grouped.map(g => (
        <div key={g.cat} style={{ padding: '6px 20px 0' }}>
          {cat === 'All' && (
            <div style={{ padding: '6px 0 8px', display: 'flex', alignItems: 'center', gap: 8 }}>
              <Icon name={catIcon(g.cat)} size={14} color={C.blueElectric}/>
              <Caption>{g.cat}</Caption>
              <span style={{ font: '500 11px/1 Inter, system-ui, sans-serif', color: C.fgMuted, opacity: 0.7 }}>· {g.items.length}</span>
            </div>
          )}
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {g.items.map(c => <ContractorCard key={c.id} c={c}/>)}
          </div>
        </div>
      ))}
    </div>
  );
}

function catIcon(cat) {
  return {
    'Electrical Contractor': 'zap', 'GC': 'hardHat', 'Public Buyer': 'building',
    'Regional Integrator': 'branch', 'National Integrator': 'branch',
    'Distributor': 'inbox', 'Manufacturer': 'cloud',
    'Facilities Lead': 'building', 'Property Manager': 'building',
    'Vendor Application': 'inbox',
  }[cat] || 'users';
}

function ContractorCard({ c }) {
  return (
    <Card hover style={{ padding: 14 }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10 }}>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ font: '700 15px/1.2 Inter, system-ui, sans-serif', color: C.fg1 }}>{c.name}</div>
          <div style={{ font: '400 12px/16px Inter, system-ui, sans-serif', color: C.fgMuted, marginTop: 2, display: 'inline-flex', alignItems: 'center', gap: 6 }}>
            <Icon name="mapPin" size={12} color={C.fgMuted}/> {c.territory}
          </div>
        </div>
        <StatusPill kind={c.status === 'Active' ? 'warm' : c.status === 'Cold' ? 'dead' : 'nurture'} size="sm" dot={false}>{c.status}</StatusPill>
      </div>

      <div style={{ marginTop: 10, padding: '10px 12px', background: C.cardElev, borderRadius: 12 }}>
        <Caption>Ask for</Caption>
        <div style={{ font: '600 13px/18px Inter, system-ui, sans-serif', color: C.fg1, marginTop: 2 }}>{c.ask}</div>
        <div style={{ font: '400 12px/16px Inter, system-ui, sans-serif', color: C.fgMuted, marginTop: 4 }}>via {c.route}</div>
      </div>

      <div style={{ marginTop: 10, font: '400 13px/18px Inter, system-ui, sans-serif', color: C.fg2 }}>
        <span style={{ color: C.blueElectric, fontWeight: 600 }}>Why →</span> {c.why}
      </div>

      <div style={{ marginTop: 10, display: 'flex', alignItems: 'center', gap: 8, justifyContent: 'space-between' }}>
        <div>
          <Caption color={C.blue}>Next</Caption>
          <div style={{ font: '600 13px/1 Inter, system-ui, sans-serif', color: C.fg1, marginTop: 2 }}>{c.next} · {c.due}</div>
        </div>
        <button type="button" onClick={() => window.ShieldTechApi?.addTargetToDeck?.(c).catch(error => window.dispatchEvent(new CustomEvent('stw:api-error', { detail: { message: error.message, code: error.code || 'TARGET_ADD_FAILED' } })))} style={{ height: 36, padding: '0 14px', borderRadius: 10, background: 'linear-gradient(135deg,#00A3FF,#29C7FF)', color: '#03182A', border: 0, font: '600 13px/1 Inter, system-ui, sans-serif', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 6 }}>
          Add to deck<Icon name="arrowRight" size={12} color="#03182A" strokeWidth={2.5}/>
        </button>
      </div>
    </Card>
  );
}

// ─────────────────────────────────────────────────────────────
// VENDOR APPLICATIONS
// ─────────────────────────────────────────────────────────────
function ScreenVendors({ goto }) {
  const stuck = VENDORS.filter(v => v.stuck);
  return (
    <div className="slide-up" style={{ padding: '0 0 140px' }}>
      <MobileHeader subtitle="VENDOR APPS" title={`${VENDORS.length} applications`}
        trailing={null}/>

      <div style={{ padding: '4px 20px 0', font: '400 14px/20px Inter, system-ui, sans-serif', color: C.fg2 }}>
        {stuck.length} stuck. {VENDORS.filter(v => v.status === 'Approved').length} approved.
      </div>

      <div style={{ padding: '14px 20px 0', display: 'flex', flexDirection: 'column', gap: 12 }}>
        {VENDORS.map(v => <VendorAppCard key={v.id} v={v}/>)}
      </div>
    </div>
  );
}

function VendorAppCard({ v }) {
  const statusKind = v.status === 'Approved' ? 'done' : v.status === 'Incomplete' ? 'hot' : v.status === 'Awaiting response' ? 'warm' : 'nurture';
  const docKeys = Object.keys(v.docs);
  const docsDone = docKeys.filter(k => v.docs[k]).length;
  const docLabels = { W9: 'W9', COI: 'COI', capability: 'Cap.', references: 'Refs', licenses: 'Lic.', safety: 'Safety' };

  return (
    <Card hover glow={v.stuck && v.status === 'Incomplete' ? 'danger' : null} style={{ padding: 16 }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
        <Avatar initials={v.vendor.split(' ').map(w => w[0]).slice(0, 2).join('')} size={42}/>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
            <div style={{ font: '700 15px/1.2 Inter, system-ui, sans-serif', color: C.fg1, flex: 1 }}>{v.vendor}</div>
            <StatusPill kind={statusKind} size="sm" dot={false}>{v.status}</StatusPill>
          </div>
          <div style={{ font: '500 11px/16px Inter, system-ui, sans-serif', color: C.fgMuted, letterSpacing: '0.04em', textTransform: 'uppercase', marginTop: 2 }}>{v.type}</div>
        </div>
      </div>

      {v.stuck && v.status === 'Incomplete' && (
        <div style={{ marginTop: 10, padding: '8px 12px', background: '#1B0908', border: '1px solid #7F1D1D', borderRadius: 12, display: 'flex', alignItems: 'center', gap: 8 }}>
          <Icon name="alertTriangle" size={14} color="#FCA5A5"/>
          <span style={{ font: '500 12px/16px Inter, system-ui, sans-serif', color: '#FCA5A5' }}>
            Stuck for {v.days} days — missing docs blocking submission.
          </span>
        </div>
      )}
      {v.stuck && v.status === 'Awaiting response' && (
        <div style={{ marginTop: 10, padding: '8px 12px', background: '#1A1306', border: '1px solid #92400E', borderRadius: 12, display: 'flex', alignItems: 'center', gap: 8 }}>
          <Icon name="clock" size={14} color="#FCD34D"/>
          <span style={{ font: '500 12px/16px Inter, system-ui, sans-serif', color: '#FCD34D' }}>
            Quiet for {v.days} days. Time to nudge.
          </span>
        </div>
      )}

      {/* Doc checklist */}
      <div style={{ marginTop: 12 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
          <Caption>Documents</Caption>
          <span className="tabular" style={{ font: '600 12px/1 Inter, system-ui, sans-serif', color: docsDone === docKeys.length ? '#86EFAC' : C.fg2 }}>
            {docsDone}/{docKeys.length}
          </span>
        </div>
        <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
          {docKeys.map(k => {
            const done = v.docs[k];
            return (
              <span key={k} style={{
                display: 'inline-flex', alignItems: 'center', gap: 4,
                padding: '4px 9px', borderRadius: 999,
                background: done ? '#052E16' : C.cardElev,
                color: done ? '#86EFAC' : C.fgMuted,
                border: `1px solid ${done ? '#166534' : C.border}`,
                font: '600 11px/1 Inter, system-ui, sans-serif',
              }}>
                <Icon name={done ? 'check' : 'x'} size={11} color={done ? '#86EFAC' : C.fgMuted} strokeWidth={2.5}/>
                {docLabels[k]}
              </span>
            );
          })}
        </div>
      </div>

      {/* Footer */}
      <div style={{ marginTop: 12, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
        <div>
          <Caption>Submitted</Caption>
          <div style={{ font: '600 13px/1 Inter, system-ui, sans-serif', color: C.fg1, marginTop: 4 }}>{v.submitted}</div>
        </div>
        <div>
          <Caption color={C.blue}>Next</Caption>
          <div style={{ font: '600 13px/1 Inter, system-ui, sans-serif', color: C.fg1, marginTop: 4 }}>{v.next}</div>
        </div>
      </div>
    </Card>
  );
}

Object.assign(window, {
  ScreenBids, ScreenContractors, ScreenVendors,
  PublicBidCard, ContractorCard, VendorAppCard, catIcon,
});
