/* ===========================================================
   Views (set 1): Overview, Pipeline, Runs, RunDetail
   =========================================================== */

const { useState: useState1, useEffect: useEffect1, useMemo: useMemo1, useRef: useRef1, Fragment } = React;

/* ------------------------- OVERVIEW ------------------------- */
function OverviewView({ runs, nav }) {
  const today = runs.length;
  const published = runs.filter(r => r.publisher.success).length;
  const succRate = Math.round((runs.filter(r => r.status === 'success').length / runs.length) * 100);
  const avgDur = runs.reduce((a, r) => a + r.duration_seconds, 0) / runs.length;
  const avgDurY = avgDur * 0.92;

  // Build funnel from all runs aggregated
  const totalFound = runs.reduce((a, r) => a + r.collector.total_found, 0);
  const dedup      = runs.reduce((a, r) => a + r.cooldown.candidates_after_cooldown, 0);
  const editorSel  = runs.length;
  const qualOK     = runs.filter(r => r.quality.passed).length;
  const pubOK      = runs.filter(r => r.publisher.success).length;
  const stages = [
    { name: 'Collected',       value: totalFound, tip: 'RSS scrape · 9 sources' },
    { name: 'Deduplicated',    value: dedup,      tip: 'Jaccard ≥ 0.78 + cooldown' },
    { name: 'Editor-selected', value: editorSel,  tip: 'Top-1 per run' },
    { name: 'Quality-passed',  value: qualOK,     tip: 'Rúbrica editorial OK' },
    { name: 'Published',       value: pubOK,      tip: 'WordPress 201' },
  ];

  // Live pipeline (latest run)
  const latest = runs[0];
  const stageList = [
    { name: 'RSS Collector',   status: 'ok',   model: 'qwen2.5:14b',    dur: '3.2s' },
    { name: 'Deduplication',   status: 'ok',   model: 'local jaccard',  dur: '0.4s' },
    { name: 'Cooldown',        status: 'ok',   model: 'local',          dur: '0.1s' },
    { name: 'Trends agent',    status: 'ok',   model: 'qwen2.5:14b',    dur: '4.8s' },
    { name: 'Editor',          status: 'ok',   model: 'qwen2.5:14b',    dur: '6.1s' },
    { name: 'Extractor',       status: 'ok',   model: 'http/scrape',    dur: '2.0s' },
    { name: 'Dual writer',     status: 'run',  model: 'mistral · gemma',dur: '12.4s' },
    { name: 'Selector',        status: 'idle', model: 'qwen2.5:14b',    dur: '—' },
    { name: 'Fact-checker',    status: 'idle', model: 'qwen2.5:14b',    dur: '—' },
    { name: 'Quality gate',    status: 'idle', model: 'local rúbrica',  dur: '—' },
    { name: 'SEO agent',       status: 'idle', model: 'gemma4:e4b',     dur: '—' },
    { name: 'WordPress',       status: 'idle', model: 'wp-rest',        dur: '—' },
  ];

  // anomaly: longest run that exceeds 1.5x mean
  const anomalous = runs.find(r => r.duration_seconds > avgDur * 1.5);

  const durSparkData = runs.map(r => r.duration_seconds).reverse();
  const pubSparkData = runs.map(r => r.publisher.success ? 1 : 0).reverse();

  return (
    <div className="page">
      <div className="page-header">
        <div>
          <span className="kicker">Sala de redacción</span>
          <h1>Overview</h1>
        </div>
        <div className="meta">
          <span className="chip"><StatusDot status="run" />Pipeline activo</span>
          <span>actualizado <b className="t1">{fmtAgo(latest.timestamp)}</b></span>
        </div>
      </div>

      {/* KPI strip */}
      <div className="grid" style={{gridTemplateColumns: 'repeat(4, 1fr)', marginBottom: 16}}>
        <div className="card kpi">
          <div className="kpi-label">Hoy</div>
          <div className="kpi-val">{today}<span className="unit">runs</span></div>
          <div className="kpi-delta up"><I.arrowUp />+2 vs ayer</div>
          <div className="sparkline"><Sparkline data={runs.map((_,i) => runs.length - i)} color="var(--info)" /></div>
        </div>
        <div className="card kpi">
          <div className="kpi-label">Publicados</div>
          <div className="kpi-val">{published}<span className="unit">/ {today}</span></div>
          <div className="kpi-delta up"><I.arrowUp />+1 vs ayer</div>
          <div className="sparkline"><Sparkline data={pubSparkData.map((v,i) => v + i*0.1)} color="var(--ok)" /></div>
        </div>
        <div className="card kpi">
          <div className="kpi-label">Tasa de éxito</div>
          <div className="kpi-val">{succRate}<span className="unit">%</span></div>
          <div className="kpi-delta flat">— sin cambio</div>
          <div className="sparkline"><Sparkline data={[70,80,72,80,85,78,82,90,85,succRate]} color="var(--gold)" /></div>
        </div>
        <div className="card kpi">
          <div className="kpi-label">Duración media</div>
          <div className="kpi-val">{fmtDuration(avgDur)}<span className="unit">min</span></div>
          <div className="kpi-delta down"><I.arrowDown />+{((avgDur/avgDurY - 1)*100).toFixed(0)}% vs ayer</div>
          <div className="sparkline"><Sparkline data={durSparkData} color="var(--warn)" /></div>
        </div>
      </div>

      {/* Funnel + Live status */}
      <div className="grid" style={{gridTemplateColumns: '7fr 5fr', marginBottom: 16}}>
        <div className="card">
          <div className="card-head">
            <div><span className="kicker">FLOW</span><span className="title">Pipeline funnel</span></div>
            <div className="meta">últimas {runs.length} ejecuciones · agregado</div>
          </div>
          <div className="card-body"><Funnel stages={stages} /></div>
        </div>

        <div className="card">
          <div className="card-head">
            <div><span className="kicker">LIVE</span><span className="title">Pipeline status</span></div>
            <div className="meta mono"><span className="statusdot run" style={{marginRight:6}} />{fmtClock(latest.timestamp)}</div>
          </div>
          <div className="card-body" style={{paddingTop: 6}}>
            <div className="plist">
              {stageList.map(s => (
                <div className="pitem" key={s.name} data-tip={`Modelo: ${s.model}`}>
                  <StatusDot status={s.status} />
                  <div>
                    <div className="name">{s.name}</div>
                    <div className="model">{s.model}</div>
                  </div>
                  <div className="meta">{s.dur}</div>
                </div>
              ))}
            </div>
          </div>
        </div>
      </div>

      {/* Recent runs + Anomaly */}
      <div className="grid" style={{gridTemplateColumns: '8fr 4fr'}}>
        <div className="card flush">
          <div className="card-head">
            <div><span className="kicker">HISTORY</span><span className="title">Últimos 10 ciclos</span></div>
            <div className="meta">click para abrir</div>
          </div>
          <table className="dt">
            <thead>
              <tr>
                <th>Hora</th>
                <th>Estado</th>
                <th>Titular seleccionado</th>
                <th>Fuente</th>
                <th>Writer</th>
                <th style={{textAlign:'right'}}>Att.</th>
                <th style={{textAlign:'right'}}>Duración</th>
              </tr>
            </thead>
            <tbody>
              {runs.map(r => (
                <tr key={r.id} className="row-link" onClick={() => nav(`/runs/${r.id}`)}>
                  <td className="num" style={{textAlign:'left', fontSize: 11.5, color:'var(--text3)'}}>{fmtClock(r.timestamp)}</td>
                  <td><StatusChip status={r.status} /></td>
                  <td className="headline"><div className="truncate" title={r.editor.selected_titulo}>{r.editor.selected_titulo}</div></td>
                  <td><Chip>{r.editor.selected_fuente}</Chip></td>
                  <td><span className="mono" style={{fontSize: 11, color: 'var(--text2)'}}>{r.writer.winner_model}</span></td>
                  <td className="num" style={{color: r.writer.attempts > 1 ? 'var(--warn)' : 'var(--text2)'}}>{r.writer.attempts}</td>
                  <td className="num">{fmtDuration(r.duration_seconds)}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>

        <div className="grid" style={{gridTemplateColumns: '1fr', alignContent: 'start'}}>
          {anomalous && (
            <div className="anomaly">
              <div className="top"><I.alert />Anomalía detectada</div>
              <div className="head">{anomalous.editor.selected_titulo.slice(0, 70)}…</div>
              <div className="body">
                Run a las <b className="mono">{fmtClock(anomalous.timestamp)}</b> tardó <b>{fmtDuration(anomalous.duration_seconds)}</b> — un <b>{Math.round((anomalous.duration_seconds/avgDur - 1)*100)}%</b> sobre la media. Cuello de botella: <b>Writer ({anomalous.writer.winner_model})</b> con <b>{anomalous.writer.attempts} intentos</b>.
                <div style={{marginTop: 10}}>
                  <button className="chip masthead" onClick={() => nav(`/runs/${anomalous.id}/visor`)} style={{cursor: 'pointer'}}>
                    abrir visor de reintentos →
                  </button>
                </div>
              </div>
            </div>
          )}

          <div className="card pad">
            <div style={{fontFamily:'var(--mono)', fontSize: 9.5, letterSpacing:'0.18em', textTransform:'uppercase', color:'var(--text3)', marginBottom: 10}}>Lo más leído (mock)</div>
            <ol style={{margin: 0, paddingLeft: 18, color: 'var(--text1)', fontFamily: 'var(--serif)', fontSize: 13.5, lineHeight: 1.5}}>
              <li style={{marginBottom: 6}}>El BCE recorta los tipos al 2,5%</li>
              <li style={{marginBottom: 6}}>Real Madrid, decimosexta Champions</li>
              <li style={{marginBottom: 6}}>OpenAI presenta GPT-6</li>
              <li style={{marginBottom: 6}}>El alquiler en España, récord</li>
            </ol>
          </div>

          <div className="card pad">
            <div style={{fontFamily:'var(--mono)', fontSize: 9.5, letterSpacing:'0.18em', textTransform:'uppercase', color:'var(--text3)', marginBottom: 10}}>Sistema</div>
            <div style={{display: 'grid', gridTemplateColumns: '1fr auto', gap: '8px 18px', fontSize: 12, fontFamily: 'var(--mono)'}}>
              <span className="t3">Ollama bridge</span><b className="t1">localhost:11434</b>
              <span className="t3">Modelos cargados</span><b className="t1">3 / 3</b>
              <span className="t3">vRAM GPU</span><b className="t1">14.8 / 24 GiB</b>
              <span className="t3">Last heartbeat</span><b className="t1">2s ago</b>
              <span className="t3">Bridge WS</span><b style={{color: 'var(--ok)'}}>CONNECTED</b>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ------------------------- PIPELINE ------------------------- */
function PipelineView({ runs, nav }) {
  const latest = runs[0];
  const totalDur = latest.duration_seconds;
  // build a fake but plausible gantt for the latest run
  const lanes = [
    { name: 'Collector',     bars: [{start: 0,    duration: 8,  cls: 'collector', label: '8.1s',  tip: 'qwen2.5:14b · 9 fuentes RSS'}] },
    { name: 'Cooldown',      bars: [{start: 8,    duration: 0.4, cls: 'cooldown',  label: '0.4s',  tip: '6 candidatos penalizados'}] },
    { name: 'Trends',        bars: [{start: 8.4,  duration: 5.2, cls: 'trends',    label: '5.2s',  tip: 'Gemini hints + Google trends'}] },
    { name: 'Editor',        bars: [{start: 13.6, duration: 7.4, cls: 'editor',    label: '7.4s',  tip: 'qwen2.5:14b · 73 cand.'}] },
    { name: 'Extractor',     bars: [{start: 21,   duration: 2.1, cls: 'extractor', label: '2.1s',  tip: 'http scrape · 562 palabras'}] },
    { name: 'Writer A',      bars: [{start: 23.1, duration: 11.4, cls: 'writer-a', label: 'mistral 11.4s', tip: 'mistral-nemo:12b · 1.342 tokens'}] },
    { name: 'Writer B',      bars: [{start: 23.1, duration: 9.8,  cls: 'writer-b', label: 'gemma 9.8s',    tip: 'gemma4:e4b · 1.188 tokens'}] },
    { name: 'Selector',      bars: [{start: 34.5, duration: 4.0,  cls: 'selector', label: '4.0s',  tip: 'A wins (0.78 vs 0.61)'}] },
    { name: 'Fact-checker',  bars: [{start: 38.5, duration: 6.2,  cls: 'fact',     label: '6.2s',  tip: '17/17 claims ✓'}] },
    { name: 'Quality',       bars: [{start: 44.7, duration: 1.8,  cls: 'quality',  label: '1.8s',  tip: latest.writer.attempts > 1 ? `${latest.writer.attempts} intentos` : 'OK first try'}] },
    { name: 'SEO',           bars: [{start: 46.5, duration: 3.4,  cls: 'seo',      label: '3.4s',  tip: 'gemma4:e4b'}] },
    { name: 'Publisher',     bars: [{start: 49.9, duration: 2.6,  cls: latest.publisher.success ? 'publisher' : 'failed',  label: '2.6s',  tip: latest.publisher.success ? `WP 201 → ${latest.publisher.post_id}` : 'WP 504'}] },
  ];
  const ganttTotal = 53;

  // heatmap: topics x last 15 runs
  const allRuns = runs.slice(0, 15);
  const topicRows = window.TOPICS;
  const maxCellVal = 10;

  // Funnel from latest run
  const lr = latest;
  const stages = [
    { name: 'Collected',       value: lr.collector.total_found, tip: 'RSS scrape' },
    { name: 'Deduplicated',    value: lr.collector.candidates_after_dedup, tip: 'Jaccard 0.78' },
    { name: 'Post-cooldown',   value: lr.cooldown.candidates_after_cooldown, tip: 'Factor 0.65' },
    { name: 'Editor-selected', value: 1, tip: 'Top-1' },
    { name: 'Published',       value: lr.publisher.success ? 1 : 0, tip: 'WP REST' },
  ];

  return (
    <div className="page">
      <div className="page-header">
        <div>
          <span className="kicker">Diagnóstico</span>
          <h1>Pipeline detail</h1>
        </div>
        <div className="meta">
          <span>Run actual: <b className="t1 mono">{lr.id}</b></span>
          <span className="mono">{fmtClock(lr.timestamp)}</span>
        </div>
      </div>

      <div className="grid" style={{gridTemplateColumns: '8fr 4fr', marginBottom: 16}}>
        <div className="card">
          <div className="card-head"><div><span className="kicker">FUNNEL</span><span className="title">Run actual — drop-off por etapa</span></div></div>
          <div className="card-body"><Funnel stages={stages} /></div>
        </div>
        <div className="card pad">
          <div style={{fontFamily:'var(--mono)', fontSize: 9.5, letterSpacing:'0.18em', textTransform:'uppercase', color:'var(--text3)', marginBottom: 16}}>Top razones de descarte</div>
          <div style={{display: 'flex', flexDirection: 'column', gap: 12}}>
            {[
              { label: 'Duplicado (Jaccard ≥ 0.78)', count: 12, color: '#5b8def' },
              { label: 'Penalización cooldown', count: 9, color: '#ad7eea' },
              { label: 'Fuente fuera de catálogo', count: 4, color: 'var(--warn)' },
              { label: 'Idioma no detectado', count: 2, color: 'var(--text3)' },
              { label: 'Sin texto extraíble', count: 1, color: 'var(--err)' },
            ].map(r => (
              <div key={r.label}>
                <div style={{display: 'flex', justifyContent: 'space-between', fontSize: 12, marginBottom: 4}}>
                  <span className="t2">{r.label}</span>
                  <span className="mono t1">{r.count}</span>
                </div>
                <div style={{height: 6, background: 'var(--surface2)', borderRadius: 2, overflow: 'hidden'}}>
                  <div style={{width: `${(r.count/14)*100}%`, height: '100%', background: r.color}} />
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>

      <div className="card" style={{marginBottom: 16}}>
        <div className="card-head">
          <div><span className="kicker">GANTT</span><span className="title">Cronograma — etapas del último run</span></div>
          <div className="meta">duración total <b className="t1 mono">{fmtDuration(lr.duration_seconds)}</b></div>
        </div>
        <div className="card-body"><Gantt lanes={lanes} totalDuration={ganttTotal} /></div>
      </div>

      <div className="card">
        <div className="card-head">
          <div><span className="kicker">HEATMAP</span><span className="title">Penalización por cooldown — temas × runs recientes</span></div>
          <div className="meta">color más intenso = más penalización</div>
        </div>
        <div className="card-body">
          <div className="heatmap" style={{gridTemplateColumns: `180px repeat(${allRuns.length}, 1fr)`}}>
            <div />
            {allRuns.map((r,i) => <div key={i} className="hm-row-label" style={{textAlign:'center', padding:0}}>{String(i+1).padStart(2,'0')}</div>)}
            {topicRows.map(t => (
              <Fragment key={t}>
                <div className="hm-row-label">{t.replace(/_/g, ' ')}</div>
                {allRuns.map((r,i) => {
                  const v = r.cooldown.penalized_topics[t] || 0;
                  const op = v === 0 ? 0.08 : 0.18 + (v/maxCellVal) * 0.82;
                  return (
                    <div key={i} className="hm-cell"
                         data-tip={`${t} · run ${i+1} · penalized ${v}x`}
                         style={{background: `rgba(200, 16, 46, ${op})`}} />
                  );
                })}
              </Fragment>
            ))}
          </div>
          <div className="thin-rule" />
          <div className="legend">
            <span className="lg"><span className="sw" style={{background: 'rgba(200,16,46,0.18)'}} />bajo</span>
            <span className="lg"><span className="sw" style={{background: 'rgba(200,16,46,0.5)'}} />medio</span>
            <span className="lg"><span className="sw" style={{background: 'rgba(200,16,46,0.92)'}} />alto</span>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ------------------------- RUNS HISTORY ------------------------- */
function RunsView({ runs, nav }) {
  const [filter, setFilter] = useState1('all');
  const [search, setSearch] = useState1('');

  const filtered = runs.filter(r => {
    if (filter !== 'all' && r.status !== filter) return false;
    if (search && !r.editor.selected_titulo.toLowerCase().includes(search.toLowerCase())) return false;
    return true;
  });

  return (
    <div className="page">
      <div className="page-header">
        <div>
          <span className="kicker">Archivo</span>
          <h1>Historial de runs</h1>
        </div>
        <div className="meta"><span>{filtered.length} de {runs.length} runs</span></div>
      </div>

      <div className="card pad-sm" style={{marginBottom: 16, display: 'flex', alignItems: 'center', gap: 12}}>
        <div style={{display: 'flex', gap: 6}}>
          {['all', 'success', 'partial', 'error'].map(s => (
            <button key={s}
              className="chip"
              onClick={() => setFilter(s)}
              style={{
                cursor: 'pointer',
                background: filter === s ? 'var(--surface3)' : 'var(--surface2)',
                color: filter === s ? 'var(--text1)' : 'var(--text3)',
                borderColor: filter === s ? 'var(--border-strong)' : 'var(--border)',
              }}>
              {s}
            </button>
          ))}
        </div>
        <div style={{flex: 1}} />
        <div style={{display: 'flex', alignItems: 'center', gap: 8, background: 'var(--surface2)', border: '1px solid var(--border)', borderRadius: 5, padding: '5px 10px', width: 280}}>
          <I.search style={{color: 'var(--text3)'}} />
          <input
            value={search}
            onChange={e => setSearch(e.target.value)}
            placeholder="buscar por titular…"
            style={{background:'transparent', border:'none', outline:'none', color:'var(--text1)', fontSize: 12.5, width:'100%', fontFamily:'var(--sans)'}}
          />
        </div>
      </div>

      <div className="card flush">
        <table className="dt">
          <thead>
            <tr>
              <th>Timestamp</th>
              <th>Estado</th>
              <th>Titular</th>
              <th>Fuente</th>
              <th>Writer</th>
              <th style={{textAlign:'right'}}>Att.</th>
              <th style={{textAlign:'right'}}>Duración</th>
              <th>Fact</th>
              <th>Quality</th>
              <th>WP</th>
            </tr>
          </thead>
          <tbody>
            {filtered.map(r => (
              <tr key={r.id} className="row-link" onClick={() => nav(`/runs/${r.id}`)}>
                <td className="num" style={{textAlign:'left', fontSize: 11.5}}>{fmtClock(r.timestamp)} <span className="t4">·</span> <span className="t3">{fmtAgo(r.timestamp)}</span></td>
                <td><StatusChip status={r.status} /></td>
                <td className="headline"><div className="truncate" title={r.editor.selected_titulo}>{r.editor.selected_titulo}</div></td>
                <td><Chip>{r.editor.selected_fuente}</Chip></td>
                <td><span className="mono" style={{fontSize: 11, color: 'var(--text2)'}}>{r.writer.winner_model}</span></td>
                <td className="num" style={{color: r.writer.attempts > 1 ? 'var(--warn)' : 'var(--text2)'}}>{r.writer.attempts}</td>
                <td className="num">{fmtDuration(r.duration_seconds)}</td>
                <td>{r.fact_checker.passed ? <I.check style={{color: 'var(--ok)'}} /> : <I.x style={{color: 'var(--err)'}} />}</td>
                <td>{r.quality.passed ? <I.check style={{color: 'var(--ok)'}} /> : <I.x style={{color: 'var(--err)'}} />}</td>
                <td>
                  {r.publisher.success
                    ? <a className="chip ok" href={r.publisher.post_url} target="_blank" onClick={e => e.stopPropagation()} style={{textDecoration:'none'}}><I.link /> #{r.publisher.post_id}</a>
                    : <span className="chip err">no pub.</span>}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

/* ------------------------- RUN DETAIL ------------------------- */
function RunDetailView({ run, nav }) {
  const writerAttempt = run.writer.attempts_history[run.writer.attempts_history.length - 1];

  const breakdown = run.editor.breakdown;
  const donutData = [
    { label: 'Source weight', value: breakdown.source,    color: '#5b8def' },
    { label: 'Freshness',     value: breakdown.freshness, color: 'var(--gold)' },
    { label: 'Relevance',     value: breakdown.relevance, color: '#4ec9b0' },
    { label: 'Trends boost',  value: breakdown.trends,    color: '#d685c3' },
    { label: 'Gemini boost',  value: breakdown.gemini,    color: '#c98c4e' },
  ];

  const totalDur = run.duration_seconds;
  const compactLanes = [
    { name: 'Collector', bars: [{start: 0, duration: 10, cls: 'collector', label: '', tip: 'qwen2.5:14b'}] },
    { name: 'Editor',    bars: [{start: 12, duration: 9, cls: 'editor', label: '', tip: 'qwen2.5:14b'}] },
    { name: 'Writer',    bars: [{start: 22, duration: 22, cls: 'writer-a', label: `${run.writer.attempts}×`, tip: `${run.writer.winner_model}`}] },
    { name: 'Quality',   bars: [{start: 45, duration: 4, cls: run.quality.passed ? 'quality' : 'failed', label: '', tip: run.quality.passed ? 'OK' : 'failed'}] },
    { name: 'Publisher', bars: [{start: 50, duration: 3, cls: run.publisher.success ? 'publisher' : 'failed', label: '', tip: run.publisher.success ? `#${run.publisher.post_id}` : 'failed'}] },
  ];

  // parse the article html with regex placeholders into a clean version (no flaws)
  const articleHtml = renderArticleHtmlClean(run);

  return (
    <div className="page">
      <div className="page-header">
        <div style={{display:'flex', alignItems:'center', gap: 12}}>
          <button className="chip" onClick={() => nav('/runs')} style={{cursor:'pointer'}}><I.back /> Runs</button>
          <div>
            <span className="kicker">Run {run.id}</span>
            <h1 style={{fontSize: 22}}>{run.editor.selected_titulo}</h1>
          </div>
        </div>
        <div className="meta">
          <StatusChip status={run.status} />
          <span className="mono">{fmtClock(run.timestamp)}</span>
          <span>•</span>
          <span>{fmtDuration(run.duration_seconds)}</span>
        </div>
      </div>

      <div className="grid" style={{gridTemplateColumns: '6fr 4fr'}}>
        {/* LEFT: SEO + article */}
        <div className="grid" style={{gridTemplateColumns: '1fr', alignContent: 'start'}}>
          <div className="card pad">
            <div style={{display: 'grid', gridTemplateColumns: 'auto 1fr auto 1fr', gap: '6px 18px', fontSize: 11.5, alignItems: 'baseline'}}>
              <span className="italic-label">slug</span>
              <span className="mono t1" style={{wordBreak:'break-all'}}>{run.seo.slug}</span>
              <span className="italic-label">categoría</span>
              <span><Chip kind="masthead">{run.seo.category}</Chip></span>

              <span className="italic-label">focus keyword</span>
              <span><Chip>{run.seo.focus_keyword}</Chip></span>
              <span className="italic-label">tags</span>
              <span style={{display: 'flex', flexWrap: 'wrap', gap: 4}}>
                {run.seo.tags.map(t => <Chip key={t}>{t}</Chip>)}
              </span>
            </div>
          </div>

          <div className="newspaper">
            <div className="nameplate">
              <div className="title">El Cronista Automático</div>
              <div className="sub">
                <span>Edición digital</span>
                <span>{new Date(run.timestamp).toLocaleDateString('es-ES', {weekday:'long', day:'numeric', month:'long', year:'numeric'})}</span>
                <span>Sección · {run.seo.category}</span>
              </div>
            </div>
            <h1 className="headline">{run.editor.selected_titulo}</h1>
            <div className="deck">{getStoryDeck(run.editor.selected_titulo)}</div>
            <div className="byline">Por la redacción automatizada · Fuente original: {run.editor.selected_fuente}</div>
            <div className="body-cols" dangerouslySetInnerHTML={{__html: articleHtml}} />
          </div>

          <div className="card pad-sm" style={{display: 'flex', alignItems:'center', justifyContent:'space-between'}}>
            <div style={{display:'flex', gap: 10, alignItems:'center', fontSize: 12}}>
              <span className="italic-label">Publicado en</span>
              {run.publisher.success
                ? <a className="chip ok" href={run.publisher.post_url} target="_blank"><I.link /> {run.publisher.post_url} (#{run.publisher.post_id})</a>
                : <Chip kind="err">no publicado</Chip>}
            </div>
            <div style={{display:'flex', gap: 8}}>
              <button className="chip" onClick={() => nav(`/runs/${run.id}/visor`)} style={{cursor:'pointer'}}>
                <I.retry /> abrir visor de reintentos
              </button>
            </div>
          </div>
        </div>

        {/* RIGHT: editor reasoning */}
        <div className="grid" style={{gridTemplateColumns: '1fr', alignContent: 'start'}}>
          <div className="card pad">
            <div style={{fontFamily:'var(--mono)', fontSize: 9.5, letterSpacing:'0.18em', textTransform:'uppercase', color:'var(--text3)', marginBottom: 14}}>Desglose del editorial score</div>
            <Donut data={donutData} />
          </div>

          <div className="card">
            <div className="card-head"><div><span className="kicker">EDITOR</span><span className="title">Top 3 candidatos considerados</span></div></div>
            <div className="card-body" style={{display: 'flex', flexDirection: 'column', gap: 14}}>
              {run.editor.top_3_candidates.map((c, i) => {
                const isSelected = c.titulo === run.editor.selected_titulo;
                return (
                  <div key={i} className={isSelected ? 'bordered-left-gold' : ''} style={{paddingLeft: isSelected ? 14 : 17}}>
                    <div style={{display:'flex', justifyContent:'space-between', gap: 10, marginBottom: 4}}>
                      <span className="italic-label">{isSelected ? <span style={{color:'var(--gold)'}}>seleccionado</span> : `candidato #${i+1}`}</span>
                      <span className="mono" style={{color:'var(--text1)', fontSize: 12}}>{c.editorial_score.toFixed(3)}</span>
                    </div>
                    <div className="serif" style={{fontSize: 13.5, lineHeight: 1.35, color: 'var(--text1)', marginBottom: 4}}>{c.titulo}</div>
                    <div style={{fontSize: 11, color:'var(--text3)'}}><Chip>{c.fuente}</Chip></div>
                  </div>
                );
              })}
            </div>
          </div>

          <div className="card">
            <div className="card-head"><div><span className="kicker">TIMELINE</span><span className="title">Cronograma compacto</span></div></div>
            <div className="card-body"><Gantt lanes={compactLanes} totalDuration={55} /></div>
          </div>

          {run.errors.length > 0 && (
            <div className="card pad" style={{borderColor: 'var(--err)'}}>
              <div style={{fontFamily:'var(--mono)', fontSize: 9.5, letterSpacing:'0.18em', textTransform:'uppercase', color:'var(--err)', marginBottom: 10}}>Errores</div>
              {run.errors.map((e, i) => (
                <div key={i} style={{padding: '8px 0', borderTop: i > 0 ? '1px dashed var(--border)' : 'none', fontSize: 12}}>
                  <div style={{display:'flex', alignItems:'center', gap: 8, marginBottom: 4}}>
                    <Chip kind="err">{e.stage}</Chip>
                    <span className="mono t3" style={{fontSize: 10.5}}>{fmtClock(e.timestamp)}</span>
                  </div>
                  <div className="t2 mono" style={{fontSize: 11.5}}>{e.message}</div>
                </div>
              ))}
            </div>
          )}

          <div className="card pad">
            <div style={{fontFamily:'var(--mono)', fontSize: 9.5, letterSpacing:'0.18em', textTransform:'uppercase', color:'var(--text3)', marginBottom: 10}}>Métricas de ejecución</div>
            <div style={{display: 'grid', gridTemplateColumns: '1fr auto', gap: '6px 16px', fontSize: 12, fontFamily: 'var(--mono)'}}>
              <span className="t3">LLM calls</span><b className="t1">{run.llm_calls}</b>
              <span className="t3">Input chars</span><b className="t1">{run.input_chars.toLocaleString()}</b>
              <span className="t3">Writer attempts</span><b className="t1">{run.writer.attempts}</b>
              <span className="t3">Final length</span><b className="t1">{run.writer.final_length} palabras</b>
              <span className="t3">Fact claims</span><b className="t1">{run.fact_checker.claim_count} ({run.fact_checker.uncertain_claims.length} dudosas)</b>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

/* helpers reused */
function getStoryDeck(titulo) {
  const story = window.STORY_BANK.find(s => s.titulo === titulo);
  return story ? story.deck : '';
}
function renderArticleHtmlClean(run) {
  // rebuild the article body without flaws (final approved version)
  const story = window.STORY_BANK.find(s => s.titulo === run.editor.selected_titulo);
  if (!story) return '<p>Article body not available.</p>';
  const parts = [];
  parts.push(`<p>${story.body[0]}</p>`);
  parts.push(`<p>${story.body[1]}</p>`);
  parts.push(`<p>${story.body[2]}</p>`);
  parts.push(`<h2>${story.h2}</h2>`);
  for (const p of story.body2) parts.push(`<p>${p}</p>`);
  return parts.join('\n');
}

Object.assign(window, {
  OverviewView, PipelineView, RunsView, RunDetailView,
  getStoryDeck, renderArticleHtmlClean,
});
