/* The Benchmark tab: what the AI engines actually said, measured.

   Rendered for the client (from the summary the server adds to the
   unlocked view) and for the team (same summary, in admin). Every figure
   here was computed from stored answers; each one shows the count it came
   from and the range across repetitions. When there is no completed run
   the tab says so instead of showing zeros. */

const ENGINE_SHORT = { openai: 'ChatGPT', perplexity: 'Perplexity', anthropic: 'Claude',
  gemini: 'Gemini', serpapi: 'Google AI Overviews' };

/* A small status chip. Shared: the admin list, the raw answers and the
   snapshot cards all use it, on every page. */
function Pill({ tone, children }) {
  return (
    <span className="mono" style={{ fontSize: 10.5, padding: '3px 8px', borderRadius: 'var(--r-pill)',
      border: '1px solid color-mix(in oklab, var(--' + tone + ') 40%, transparent)',
      color: `var(--${tone})`, whiteSpace: 'nowrap' }}>{children}</span>);
}

function pct(x) { return (x === null || x === undefined) ? '—' : Math.round(x * 100) + '%'; }
function pctRange(r) {
  if (!r || r.value === null || r.value === undefined) return '—';
  if (r.low === null || r.low === undefined || r.low === r.high) return pct(r.value);
  return `${pct(r.value)} (${pct(r.low)}–${pct(r.high)})`;
}
function shortDate(s) { return s ? String(s).slice(0, 10) : ''; }

function methodologyLine(b) {
  const m = b.methodology || {};
  const engines = Object.values(m.engines || {}).join(', ');
  return `Answers via each engine's API with web search enabled, ${shortDate(b.finished_at)}, ` +
    `${m.reps || '?'} run${m.reps === 1 ? '' : 's'} per prompt. Engines: ${engines || 'none'}.`;
}

function RangeNote() {
  return (
    <div style={{ fontSize: 12.5, color: 'var(--fg-subtle)', lineHeight: 1.6 }}>
      Rates are over every stored answer; the range in brackets is the lowest and highest rate seen
      when counting one repetition at a time. A wide range means the engine answers differently each
      time, which is itself a finding.
    </div>);
}

function BenchTable({ head, rows }) {
  return (
    <div style={{ overflowX: 'auto', border: '1px solid var(--line)', borderRadius: 'var(--r-md)' }}>
      <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13.5 }}>
        <thead>
          <tr>{head.map((h, i) => <th key={i} className="mono" style={{ textAlign: i ? 'right' : 'left', fontSize: 10.5,
            letterSpacing: '0.08em', textTransform: 'uppercase', color: 'var(--fg-subtle)', padding: '10px 12px',
            borderBottom: '1px solid var(--line)', fontWeight: 500, whiteSpace: 'nowrap' }}>{h}</th>)}</tr>
        </thead>
        <tbody>
          {rows.map((r, i) =>
          <tr key={i}>{r.map((c, j) => <td key={j} style={{ textAlign: j ? 'right' : 'left', padding: '9px 12px',
            borderBottom: i < rows.length - 1 ? '1px solid var(--line)' : 'none', color: j ? 'var(--fg-muted)' : 'var(--fg)',
            whiteSpace: j ? 'nowrap' : 'normal' }}>{c}</td>)}</tr>)}
        </tbody>
      </table>
    </div>);
}

function BenchmarkNotRun({ admin }) {
  return (
    <div style={{ padding: '28px 24px', borderRadius: 'var(--r-lg)', background: 'var(--navy-2)',
      border: '1px solid var(--line)', color: 'var(--fg-muted)', fontSize: 14.5, lineHeight: 1.7, maxWidth: 720 }}>
      <div style={{ color: 'var(--fg)', fontWeight: 600, marginBottom: 6 }}>Not run yet.</div>
      The benchmark asks the AI engines the questions a buyer in this category asks, several times each,
      and measures how often this business is named and cited. Nothing is shown until that has been done.
      {admin ? ' Start a run from the Benchmark panel above.' : ' The Lojo team runs it as part of the full engagement.'}
    </div>);
}

function BenchmarkResults({ b, client }) {
  const o = b.overall || {};
  const cf = b.computed_from || {};
  const perEngine = b.per_engine || {};
  const byStage = b.by_stage || {};
  const coverage = b.coverage || {};
  const failed = b.failed_engines || [];
  const models = (b.methodology || {}).models || {};
  const sent = o.sentiment || {};
  const tiles = [
    { label: 'Named in answers', value: pct((o.mention_rate || {}).value), note: `range ${pctRange(o.mention_rate).replace(/^[^(]*\(?/, '').replace(')', '') || 'flat'} · ${o.answered || 0} answers` },
    { label: 'Site cited', value: pct((o.citation_rate || {}).value), note: `range ${pctRange(o.citation_rate).replace(/^[^(]*\(?/, '').replace(')', '') || 'flat'}` },
    { label: 'Average position', value: (o.avg_position || {}).value === null || (o.avg_position || {}).value === undefined ? '—' : `#${(o.avg_position || {}).value}`, note: `when named (${(o.avg_position || {}).n || 0} answers)` },
    { label: 'Share of voice', value: pct((o.share_of_voice || {}).value), note: `${(o.share_of_voice || {}).brand_mentions || 0} of ${(o.share_of_voice || {}).all_mentions || 0} brand mentions` },
  ];
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 22 }}>
      <div style={{ padding: '12px 16px', borderRadius: 'var(--r-md)', background: 'var(--navy-2)',
        border: '1px solid var(--line)', fontSize: 13, color: 'var(--fg-muted)', lineHeight: 1.6 }}>
        <span className="mono" style={{ fontSize: 10.5, color: 'var(--fg-subtle)', letterSpacing: '0.08em' }}>METHOD · </span>
        {methodologyLine(b)} {cf.prompts || 0} prompts, {cf.units || 0} answers requested, {cf.answered || 0} received.
        {b.status === 'partial' && <span style={{ color: 'var(--warn)' }}> This run is partial: some engines did not answer or the cost cap stopped it.</span>}
        {' '}The consumer apps can answer differently from the APIs measured here.
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: 12 }}>
        {tiles.map((s, i) => <StatTile key={i} s={s} />)}
      </div>
      <RangeNote />
      {failed.length > 0 &&
      <div style={{ fontSize: 13.5, color: 'var(--warn)' }}>
        Not measured: {failed.map((e) => ENGINE_SHORT[e] || e).join(', ')} returned no answers in this run.
      </div>}
      <div>
        <div className="mono" style={{ fontSize: 10.5, letterSpacing: '0.09em', textTransform: 'uppercase', color: 'var(--fg-subtle)', marginBottom: 8 }}>By engine</div>
        <BenchTable head={['Engine', 'Named', 'Cited', 'Avg position', 'Answered']}
          rows={Object.keys(perEngine).map((e) => {
            const v = perEngine[e] || {}; const c = coverage[e] || {};
            const label = (ENGINE_SHORT[e] || e) + (models[e] ? ` · ${models[e]}` : '');
            if (!c.answered) return [label, 'not measured', '—', '—', `0 of ${c.total || 0}`];
            return [label, pctRange(v.mention_rate), pctRange(v.citation_rate),
              (v.avg_position || {}).value ? `#${v.avg_position.value}` : '—', `${c.answered} of ${c.total}`];
          })} />
      </div>
      {Object.keys(byStage).length > 0 &&
      <div>
        <div className="mono" style={{ fontSize: 10.5, letterSpacing: '0.09em', textTransform: 'uppercase', color: 'var(--fg-subtle)', marginBottom: 8 }}>By buying stage</div>
        <BenchTable head={['Stage', 'Named', 'Cited', 'Answers']}
          rows={['awareness', 'consideration', 'decision'].filter((s) => byStage[s]).map((s) => {
            const v = byStage[s];
            return [s[0].toUpperCase() + s.slice(1), pctRange(v.mention_rate), pctRange(v.citation_rate), v.answered];
          })} />
      </div>}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: 14 }}>
        <div>
          <div className="mono" style={{ fontSize: 10.5, letterSpacing: '0.09em', textTransform: 'uppercase', color: 'var(--fg-subtle)', marginBottom: 8 }}>Who else gets named</div>
          {(o.competitors || []).length === 0
            ? <div style={{ fontSize: 13.5, color: 'var(--fg-subtle)' }}>No competitor list was set for this run.</div>
            : <BenchTable head={['Business', 'Named']}
                rows={[[client || 'This business', pct((o.mention_rate || {}).value)],
                  ...(o.competitors || []).map((c) => [c.name, pct(c.mention_rate)])]} />}
        </div>
        <div>
          <div className="mono" style={{ fontSize: 10.5, letterSpacing: '0.09em', textTransform: 'uppercase', color: 'var(--fg-subtle)', marginBottom: 8 }}>Sites the engines cite</div>
          {(o.top_domains || []).length === 0
            ? <div style={{ fontSize: 13.5, color: 'var(--fg-subtle)' }}>No citations were returned.</div>
            : <BenchTable head={['Domain', 'In answers']}
                rows={(o.top_domains || []).slice(0, 8).map((d) => [d.domain, pct(d.share)])} />}
        </div>
      </div>
      {sent.n > 0 &&
      <div style={{ fontSize: 13.5, color: 'var(--fg-muted)', lineHeight: 1.6 }}>
        <span className="mono" style={{ fontSize: 10.5, color: 'var(--fg-subtle)', letterSpacing: '0.08em' }}>TONE · </span>
        When named ({sent.n} answers): {sent.positive} positive, {sent.neutral} neutral, {sent.negative} negative
        {sent.unclassified ? `, ${sent.unclassified} not classified` : ''}.
      </div>}
    </div>);
}

function BenchmarkTab({ report, admin, code }) {
  // The client view carries the summary itself; the admin view carries the
  // whole benchmark block with the summary inside it.
  const raw = report.benchmark || null;
  const b = raw && (raw.overall ? raw : raw.summary);
  const pitch = !admin && code && report.kind === 'agency' && report.pitch_mode;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 22 }}>
      {pitch && <PitchMode report={report} code={code} />}
      {(!b || !b.overall) ? <BenchmarkNotRun admin={admin} /> : <BenchmarkResults b={b} client={report.client} />}
      {b && b.overall && !admin &&
      <div>
        <div className="mono" style={{ fontSize: 10.5, letterSpacing: '0.09em', textTransform: 'uppercase', color: 'var(--fg-subtle)', marginBottom: 8 }}>Where competitors win</div>
        <LossView losses={b.losses} client={report.client} />
      </div>}
    </div>);
}

/* ---- Phase 2: live snapshots and the competitor loss view ---- */

function escapeRe(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }

/* The answer text with the client and each competitor marked. Terms come
   from the server with the snapshot, so the page and the analysis agree
   on what counts as a mention. */
function Highlighted({ text, brand, competitors }) {
  const terms = [...(brand || []).map((t) => ({ t, tone: 'good' })), ...(competitors || []).map((t) => ({ t, tone: 'accent' }))]
    .filter((x) => x.t && x.t.length >= 2);
  if (!terms.length || !text) return <span>{text}</span>;
  const re = new RegExp('(' + terms.map((x) => escapeRe(x.t).replace(/\\?\s+/g, '[\\s\\-]*')).join('|') + ')', 'gi');
  const parts = text.split(re);
  return (
    <span>{parts.map((p, i) => {
      const hit = terms.find((x) => new RegExp('^' + escapeRe(x.t).replace(/\\?\s+/g, '[\\s\\-]*') + '$', 'i').test(p));
      return hit
        ? <mark key={i} style={{ background: `color-mix(in oklab, var(--${hit.tone}) 28%, transparent)`, color: 'var(--fg)',
            borderRadius: 3, padding: '0 2px', fontWeight: 600 }}>{p}</mark>
        : <React.Fragment key={i}>{p}</React.Fragment>;
    })}</span>);
}

function SnapshotView({ snapshot }) {
  const h = snapshot.highlight || {};
  return (
    <div>
      <div style={{ fontSize: 12.5, color: 'var(--fg-subtle)', lineHeight: 1.6, marginBottom: 10 }}>
        <span className="mono" style={{ fontSize: 10.5, letterSpacing: '0.08em' }}>METHOD · </span>{snapshot.method}
        {snapshot.cached && <span style={{ color: 'var(--warn)' }}> Shown from a snapshot taken {String(snapshot.at).slice(0, 16).replace('T', ' ')} (same question, within the cache window).</span>}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: 12 }}>
        {(snapshot.answers || []).map((a) => {
          const r = a.result || {}; const an = a.analysis || {};
          return (
            <div key={a.engine} style={{ padding: '14px 16px', borderRadius: 'var(--r-md)', background: 'var(--navy-2)', border: '1px solid var(--line)', minWidth: 0 }}>
              <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center', marginBottom: 8 }}>
                <span style={{ fontSize: 14, fontWeight: 600 }}>{ENGINE_SHORT[a.engine] || a.engine}</span>
                <span className="mono" style={{ fontSize: 11, color: 'var(--fg-subtle)' }}>{r.model}</span>
                {r.error ? <Pill tone="crit">no answer</Pill>
                  : <React.Fragment>
                      <Pill tone={an.brand_mentioned ? 'good' : 'warn'}>{an.brand_mentioned ? `named #${an.position || '?'}` : 'not named'}</Pill>
                      <Pill tone={an.brand_cited ? 'good' : 'fg-subtle'}>{an.brand_cited ? 'cited' : 'not cited'}</Pill>
                    </React.Fragment>}
              </div>
              {r.error
                ? <div className="mono" style={{ fontSize: 12, color: 'var(--crit)', whiteSpace: 'pre-wrap' }}>{r.error}</div>
                : <div style={{ fontSize: 13.5, color: 'var(--fg-muted)', lineHeight: 1.65, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
                    <Highlighted text={r.answer || '(empty answer)'} brand={h.brand} competitors={h.competitors} />
                  </div>}
              {(an.cited_domains || []).length > 0 &&
              <div className="mono" style={{ fontSize: 11, color: 'var(--fg-subtle)', marginTop: 8, wordBreak: 'break-word' }}>cites: {an.cited_domains.join(', ')}</div>}
            </div>);
        })}
      </div>
    </div>);
}

/* Ask one question of every engine, now. Used by admin (with the session
   token) and by an agency in pitch mode (with the client's access code). */
function SnapshotBox({ slug, token, code, suggestions }) {
  const [prompt, setPrompt] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [snapshot, setSnapshot] = React.useState(null);
  const [message, setMessage] = React.useState('');
  const [history, setHistory] = React.useState(null);
  const auth = token ? { Authorization: 'Bearer ' + token } : {};
  const body = (extra) => JSON.stringify({ slug, ...(code ? { code } : {}), ...extra });
  const post = (extra) => apiCall('/api/engines', { method: 'POST', headers: { 'Content-Type': 'application/json', ...auth }, body: body(extra) });
  const ask = async (text) => {
    const q = (text || prompt).trim();
    if (!q || busy) return;
    setBusy(true); setMessage(''); setPrompt(q);
    try {
      const d = await post({ action: 'snapshot', prompt: q });
      if (d.ok) { setSnapshot(d.snapshot); setHistory(null); }
      else setMessage(d.message || d.error || 'That did not work.');
    } catch (e) { setMessage('Could not reach the server.'); }
    setBusy(false);
  };
  const loadHistory = async () => {
    const d = await post({ action: 'snapshots' });
    setHistory(d.ok ? d.snapshots : []);
  };
  const openSaved = async (id) => {
    const d = await post({ action: 'snapshots', snapshot_id: id });
    if (d.ok) setSnapshot(d.snapshot);
  };
  return (
    <div>
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
        <input value={prompt} onChange={(e) => setPrompt(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') ask(); }}
          placeholder="Ask what a buyer would ask, e.g. best sleep supplement for new parents"
          style={{ flex: '1 1 320px', padding: '10px 12px', borderRadius: 'var(--r-md)', fontSize: 14, fontFamily: 'var(--font-sans)',
            color: 'var(--fg)', background: 'var(--navy)', border: '1px solid var(--line-strong)', outline: 'none' }} />
        <button className="btn btn-primary" disabled={busy || !prompt.trim()} onClick={() => ask()} style={{ padding: '10px 18px' }}>
          {busy ? 'Asking every engine…' : 'Ask the engines now'}
        </button>
        <button onClick={loadHistory} style={{ background: 'transparent', border: '1px solid var(--line-strong)', borderRadius: 'var(--r-pill)',
          color: 'var(--fg-muted)', fontFamily: 'var(--font-sans)', fontSize: 13, padding: '7px 14px', cursor: 'pointer' }}>Saved snapshots</button>
      </div>
      {suggestions && suggestions.length > 0 && !snapshot &&
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 8 }}>
        {suggestions.slice(0, 6).map((s, i) =>
        <button key={i} onClick={() => ask(s)} style={{ background: 'var(--navy-2)', border: '1px solid var(--line)', borderRadius: 'var(--r-pill)',
          color: 'var(--fg-muted)', fontFamily: 'var(--font-sans)', fontSize: 12.5, padding: '5px 11px', cursor: 'pointer' }}>{s}</button>)}
      </div>}
      {message && <div style={{ marginTop: 10, fontSize: 13.5, color: 'var(--warn)' }}>{message}</div>}
      {busy && <div style={{ marginTop: 12, fontSize: 13, color: 'var(--fg-subtle)' }}>Each engine searches the web and answers; this takes up to a minute.</div>}
      {history && !busy &&
      <div style={{ marginTop: 12 }}>
        {history.length === 0 && <div style={{ fontSize: 13, color: 'var(--fg-subtle)' }}>No snapshots saved for this report yet.</div>}
        {history.map((h) =>
        <div key={h.id} style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', fontSize: 13, padding: '6px 0', borderBottom: '1px solid var(--line)' }}>
          <span className="mono" style={{ color: 'var(--fg-subtle)' }}>{String(h.at).slice(0, 16).replace('T', ' ')}</span>
          <span style={{ flex: '1 1 200px', color: 'var(--fg)' }}>{h.prompt}</span>
          <span style={{ color: 'var(--fg-muted)' }}>named in {h.named} of {h.answered}</span>
          <button onClick={() => openSaved(h.id)} style={{ background: 'transparent', border: '1px solid var(--line-strong)', borderRadius: 'var(--r-pill)',
            color: 'var(--fg-muted)', fontFamily: 'var(--font-sans)', fontSize: 12.5, padding: '5px 12px', cursor: 'pointer' }}>Open</button>
        </div>)}
      </div>}
      {snapshot && !busy && <div style={{ marginTop: 14 }}>
        <div style={{ fontSize: 15, fontWeight: 600, marginBottom: 8 }}>“{snapshot.prompt}”</div>
        <SnapshotView snapshot={snapshot} />
      </div>}
    </div>);
}

/* Prompts where an engine named a rival and not the client. */
function LossView({ losses, client }) {
  if (!losses || !losses.length) {
    return <div style={{ fontSize: 13.5, color: 'var(--fg-subtle)' }}>No prompt in the latest run had a competitor named without {client || 'the client'}.</div>;
  }
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      {losses.map((l, i) =>
      <div key={i} style={{ padding: '12px 14px', borderRadius: 'var(--r-md)', background: 'var(--navy-2)', border: '1px solid var(--line)' }}>
        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
          <span style={{ fontSize: 14, fontWeight: 600, flex: '1 1 240px' }}>{l.prompt}</span>
          <Pill tone="fg-subtle">{l.stage}</Pill>
          <Pill tone="warn">lost {l.losses} of {l.answers}</Pill>
          {(l.competitors || []).map((c) => <Pill key={c.name} tone="accent">{c.name} ×{c.count}</Pill>)}
        </div>
        {l.example &&
        <div style={{ fontSize: 13, color: 'var(--fg-muted)', lineHeight: 1.6, marginTop: 8 }}>
          <span className="mono" style={{ fontSize: 10.5, color: 'var(--fg-subtle)' }}>{ENGINE_SHORT[l.example.engine] || l.example.engine} · run {l.example.rep} · </span>
          {l.example.excerpt}{(l.example.excerpt || '').length >= 300 ? '…' : ''}
        </div>}
      </div>)}
    </div>);
}

/* Pitch mode on the client report: an agency with the code asks live. */
function PitchMode({ report, code }) {
  const [on, setOn] = React.useState(false);
  const suggestions = ((report.benchmark && (report.benchmark.overall ? report.benchmark : report.benchmark.summary)) || {}).losses || [];
  return (
    <div style={{ padding: '18px 20px', borderRadius: 'var(--r-lg)', background: 'var(--navy-2)', border: '1px solid color-mix(in oklab, var(--accent) 40%, transparent)' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
        <div>
          <div className="mono" style={{ fontSize: 10.5, letterSpacing: '0.09em', textTransform: 'uppercase', color: 'var(--accent)' }}>Pitch mode</div>
          <div style={{ fontSize: 13.5, color: 'var(--fg-muted)', marginTop: 4 }}>Ask the engines a buyer's question live, in front of the client. Every snapshot is saved to this report.</div>
        </div>
        <button className="btn btn-primary" onClick={() => setOn((o) => !o)} style={{ padding: '9px 16px' }}>{on ? 'Close' : 'Open pitch mode'}</button>
      </div>
      {on && <div style={{ marginTop: 14 }}><SnapshotBox slug={report.slug} code={code} suggestions={suggestions.map((l) => l.prompt)} /></div>}
    </div>);
}
