// Fabric — Timeline & Catalog tabs. Split out of fabric.jsx. Loads after fabric.jsx.
const { useState: useFx, useMemo: useFxM } = React;

// --------- TIMELINE TAB — interwoven spine ---------

function TimelineTab({ pulse }) {
  return (
    <div style={{ padding: '24px 32px', maxWidth: 1080, margin: '0 auto' }}>
      {/* Filter chips */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 20, flexWrap: 'wrap' }}>
        <span className="t-micro text-tertiary">Lanes</span>
        {Object.entries(LANE_CONFIG).map(([k, v]) => (
          <span key={k} style={{
            display: 'inline-flex', alignItems: 'center', gap: 6,
            padding: '4px 10px', borderRadius: 14,
            background: 'var(--surface)', border: '1px solid var(--border-subtle)',
            fontSize: 11, fontWeight: 500,
          }}>
            <span style={{ width: 8, height: 8, borderRadius: 2, background: v.color }}/>
            {v.label}
          </span>
        ))}
        <div style={{ flex: 1 }}/>
        <button className="btn sm">Filter <Icons.ChevronD size={11}/></button>
        <button className="btn sm">Range: 2 weeks <Icons.ChevronD size={11}/></button>
      </div>

      {/* The spine */}
      <div style={{ position: 'relative' }}>
        {/* Center line */}
        <div style={{
          position: 'absolute', left: '50%', top: 0, bottom: 0,
          width: 1, background: 'var(--border)',
          transform: 'translateX(-0.5px)',
        }}/>

        {/* Live pulse at top */}
        <div style={{
          position: 'absolute', left: '50%', top: -6,
          width: 14, height: 14, borderRadius: '50%',
          background: 'var(--success)', opacity: 0.2,
          transform: 'translateX(-50%)',
          animation: 'parclePulseLoop 2s ease-out infinite',
        }}/>
        <div style={{
          position: 'absolute', left: '50%', top: -2,
          width: 6, height: 6, borderRadius: '50%',
          background: 'var(--success)',
          transform: 'translateX(-50%)',
        }}/>

        {/* Events */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 4, paddingTop: 18 }}>
          {ACME_TIMELINE.map((ev, i) => (
            <TimelineEvent key={i} ev={ev} index={i} live={i === 0}/>
          ))}
        </div>

        {/* Bottom cap */}
        <div style={{ display: 'flex', justifyContent: 'center', marginTop: 12, paddingBottom: 40 }}>
          <button className="btn sm">Load older activity</button>
        </div>
      </div>
    </div>
  );
}

function TimelineEvent({ ev, index, live }) {
  const lane = LANE_CONFIG[ev.lane];
  const v = VENDORS[ev.src];
  const onLeft = lane.side === 'left';

  return (
    <div style={{
      display: 'grid',
      gridTemplateColumns: '1fr 40px 1fr',
      alignItems: 'start',
      minHeight: 80,
    }}>
      {/* Left side */}
      <div style={{ paddingRight: 20, textAlign: 'right' }}>
        {onLeft && <EventCard ev={ev} lane={lane} v={v} side="left" live={live}/>}
      </div>

      {/* Center: dot + connector */}
      <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', paddingTop: 10, position: 'relative' }}>
        {/* Connector line from dot to card */}
        <div style={{
          position: 'absolute', top: 16,
          [onLeft ? 'right' : 'left']: '50%',
          width: 16, height: 1, background: 'var(--border)',
        }}/>
        <div style={{
          width: 14, height: 14, borderRadius: '50%',
          background: 'var(--surface)', border: `2px solid ${lane.color}`,
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          position: 'relative', zIndex: 1,
          boxShadow: live ? `0 0 0 4px ${lane.color}22` : 'none',
        }}>
          {live && (
            <span style={{
              position: 'absolute', inset: -6,
              borderRadius: '50%',
              animation: 'parclePulseLoop 2s ease-out infinite',
              background: lane.color, opacity: 0.3,
            }}/>
          )}
        </div>
        <div className="mono-sm mono text-tertiary" style={{ marginTop: 6, fontSize: 10, whiteSpace: 'nowrap' }}>
          {ev.t}
        </div>
      </div>

      {/* Right side */}
      <div style={{ paddingLeft: 20 }}>
        {!onLeft && <EventCard ev={ev} lane={lane} v={v} side="right" live={live}/>}
      </div>
    </div>
  );
}

function EventCard({ ev, lane, v, side, live }) {
  return (
    <div style={{
      display: 'inline-block',
      textAlign: 'left',
      maxWidth: 440, width: '100%',
      background: 'var(--surface)',
      border: '1px solid var(--border-subtle)',
      borderLeft: side === 'right' ? `3px solid ${lane.color}` : '1px solid var(--border-subtle)',
      borderRight: side === 'left' ? `3px solid ${lane.color}` : '1px solid var(--border-subtle)',
      borderRadius: 8,
      padding: '10px 14px',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
        <VendorLogo vendor={ev.src} size={16} radius={3}/>
        <span className="mono-sm mono text-tertiary" style={{ fontSize: 11 }}>
          {v.name} · {ev.date}
        </span>
        {live && (
          <span style={{
            padding: '1px 6px', borderRadius: 4,
            background: 'var(--success-bg)', color: 'var(--success)',
            fontSize: 9, fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.04em',
          }}>Live</span>
        )}
      </div>
      <div style={{ fontSize: 13, lineHeight: '20px' }}>
        <span style={{ fontWeight: 500 }}>{ev.actor}</span>{' '}
        <span className="text-secondary">{ev.verb}</span>{' '}
        <span style={{ fontWeight: 500, color: lane.color }}>{ev.target}</span>
      </div>
      <div className="t-small text-secondary" style={{ marginTop: 4, fontStyle: ev.preview.startsWith('“') ? 'italic' : 'normal' }}>
        {ev.preview}
      </div>
    </div>
  );
}

// --------- CATALOG TAB — snapshot-driven entity catalog ---------

function CatalogTab({ snapshot, loading, error, reload, openEntity }) {
  const [q, setQ] = useFx('');
  const [cat, setCat] = useFx('all');
  const [typ, setTyp] = useFx('all');
  const [dom, setDom] = useFx('all');

  const view = useFxM(() => {
    if (!snapshot) return { rows: [], total: 0 };
    const nodes = allGraphNodes(snapshot);
    const edges = allGraphEdges(snapshot);
    const nodeIndex = buildGraphNodeIndex(nodes);
    const { outByNode, inByNode } = buildGraphAdjacency(edges);
    const enriched = nodes.map(n => {
      const out = outByNode.get(n.id) || [];
      const inE = inByNode.get(n.id) || [];
      const domain = deriveNodeDomain(n, out, nodeIndex);
      const confEdges = [...out, ...inE].filter(e => e.confidence != null);
      const avgConf = confEdges.length
        ? confEdges.reduce((s, e) => s + e.confidence, 0) / confEdges.length
        : null;
      return {
        id: n.id,
        category: n.category,
        type: n.type || null,
        name: n.name,
        // Synonyms can be legacy strings or structured SynonymEntry objects
        // (`{name, at, origin, evidence_ref, state}`) per AskGraphDelta. Skip
        // soft-undone entries and flatten to active names for search/render.
        synonyms: (n.synonyms || [])
          .map(s => (typeof s === 'string' ? s : (s && s.state !== 'undo' ? s.name : null)))
          .filter(s => typeof s === 'string' && s),
        origin: n.origin,
        domain,
        edgeCount: out.length + inE.length,
        avgConf,
      };
    });
    const total = enriched.length;
    const qn = q.trim().toLowerCase();
    const filtered = enriched.filter(r => {
      if (cat !== 'all' && r.category !== cat) return false;
      if (typ !== 'all') {
        if (typ === 'concept') { if (r.category !== 'concept') return false; }
        else { if (r.type !== typ) return false; }
      }
      if (dom !== 'all' && r.domain !== dom) return false;
      if (qn) {
        const hay = (r.name + ' ' + r.synonyms.join(' ') + ' ' + r.id).toLowerCase();
        if (!hay.includes(qn)) return false;
      }
      return true;
    });
    filtered.sort((a, b) => b.edgeCount - a.edgeCount);
    return { rows: filtered, total };
  }, [snapshot, q, cat, typ, dom]);

  if (loading && !snapshot) {
    return (
      <div style={{ padding: '48px 32px', textAlign: 'center' }}>
        <div className="skeleton" style={{ width: 220, height: 18, margin: '0 auto' }}/>
        <div className="mono-sm mono text-tertiary" style={{ marginTop: 10 }}>Loading graph catalog…</div>
      </div>
    );
  }
  if (error) {
    return (
      <div style={{ padding: 40, textAlign: 'center' }}>
        <Icons.AlertTri size={28} style={{ color: 'var(--error)' }}/>
        <div className="t-h3" style={{ marginTop: 10, color: 'var(--error)' }}>Failed to load graph</div>
        <div className="text-secondary t-small" style={{ marginTop: 4 }}>{error}</div>
        <button className="btn sm" style={{ marginTop: 16 }} onClick={reload}>
          <Icons.Refresh size={12}/> Retry
        </button>
      </div>
    );
  }
  if (!snapshot) return null;

  const MAX = 200;

  return (
    <div style={{ padding: '24px 32px' }}>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 12, flexWrap: 'wrap' }}>
        <h2 className="t-h2" style={{ margin: 0 }}>Graph catalog</h2>
        <span className="mono-sm mono text-tertiary">
          {view.rows.length.toLocaleString()} shown · {view.total.toLocaleString()} total nodes
        </span>
      </div>

      <div style={{ display: 'flex', gap: 10, marginTop: 16, alignItems: 'center', flexWrap: 'wrap' }}>
        <div style={{ position: 'relative', flex: 1, maxWidth: 380, minWidth: 200 }}>
          <Icons.Search size={13} style={{ position: 'absolute', left: 12, top: 10, color: 'var(--text-tertiary)' }}/>
          <input className="input" placeholder="Search name, id, synonyms…" value={q} onChange={e => setQ(e.target.value)} style={{ paddingLeft: 34 }}/>
        </div>
        <SelectBtn label="Category" value={cat} onChange={setCat} options={[
          ['all', 'All'], ['factual', 'Factual'], ['concept', 'Concept'],
        ]}/>
        <SelectBtn label="Type" value={typ} onChange={setTyp} options={[
          ['all', 'All'], ['table', 'Table'], ['column', 'Column'],
          ['document', 'Document'], ['chunk', 'Chunk'], ['concept', 'Concept'],
        ]}/>
        <SelectBtn label="Domain" value={dom} onChange={setDom} options={[
          ['all', 'All'], ['db', 'DB'], ['doc', 'Docs'], ['bridge', 'Bridge'], ['orphan', 'Orphan'],
        ]}/>
      </div>

      <div className="card" style={{ marginTop: 16, overflow: 'hidden' }}>
        <table className="parcle">
          <thead>
            <tr>
              <th style={{ width: 80 }}>Category</th>
              <th style={{ width: 95 }}>Type</th>
              <th>Name</th>
              <th style={{ width: 80 }}>Domain</th>
              <th style={{ width: 140 }}>Confidence</th>
              <th style={{ width: 80 }}>Edges</th>
              <th style={{ width: 80 }}></th>
            </tr>
          </thead>
          <tbody>
            {view.rows.slice(0, MAX).map(r => {
              const dc = DOMAIN_COLORS[r.domain] || DOMAIN_COLORS.orphan;
              return (
                <tr key={r.id}
                  onClick={() => openEntity && openEntity({ id: r.id, type: r.type || r.category, label: r.name, sub: r.domain })}
                  style={{ cursor: 'pointer' }}>
                  <td><span className={`chip ${r.category === 'concept' ? 'accent' : ''}`} style={{ fontSize: 10 }}>{r.category}</span></td>
                  <td className="mono-sm mono text-secondary">{r.type || '—'}</td>
                  <td>
                    <div style={{ fontWeight: 500 }}>{r.name}</div>
                    <div className="mono-sm mono text-tertiary" style={{ fontSize: 10, wordBreak: 'break-all' }}>{r.id}</div>
                    {r.synonyms.length > 0 && (
                      <div style={{ marginTop: 4, display: 'flex', gap: 3, flexWrap: 'wrap' }}>
                        {r.synonyms.slice(0, 4).map((s, i) => <span key={s + '_' + i} className="chip" style={{ fontSize: 10 }}>{s}</span>)}
                        {r.synonyms.length > 4 && <span className="text-tertiary" style={{ fontSize: 10 }}>+{r.synonyms.length - 4}</span>}
                      </div>
                    )}
                  </td>
                  <td>
                    <span className="chip" style={{ background: dc + '22', color: dc, border: 0, fontSize: 10 }}>{r.domain}</span>
                  </td>
                  <td>{r.avgConf != null ? <ConfBar conf={r.avgConf}/> : <span className="text-tertiary">—</span>}</td>
                  <td className="mono-sm mono text-secondary">{r.edgeCount}</td>
                  <td><button className="btn sm ghost">Open <Icons.ChevronR size={11}/></button></td>
                </tr>
              );
            })}
          </tbody>
        </table>
        {view.rows.length > MAX && (
          <div style={{ padding: '10px 18px', borderTop: '1px solid var(--border-subtle)', textAlign: 'center' }} className="mono-sm mono text-tertiary">
            Showing first {MAX} of {view.rows.length.toLocaleString()} — narrow filters to see more
          </div>
        )}
        {view.rows.length === 0 && (
          <div style={{ padding: 30, textAlign: 'center' }} className="t-small text-tertiary">
            No nodes match the current filters.
          </div>
        )}
      </div>
    </div>
  );
}

function SelectBtn({ label, value, onChange, options }) {
  return (
    <label style={{ position: 'relative', display: 'inline-flex', alignItems: 'center' }}>
      <select value={value} onChange={e => onChange(e.target.value)}
        className="btn"
        style={{
          padding: '0 28px 0 12px', height: 32,
          appearance: 'none', WebkitAppearance: 'none', MozAppearance: 'none',
          fontSize: 13, fontWeight: 500,
          background: 'var(--surface)', color: 'var(--text-primary)',
          cursor: 'pointer',
        }}>
        {options.map(([v, l]) => <option key={v} value={v}>{label}: {l}</option>)}
      </select>
      <Icons.ChevronD size={11} style={{ position: 'absolute', right: 10, pointerEvents: 'none', color: 'var(--text-tertiary)' }}/>
    </label>
  );
}
