/* WPSiteBeam Scanner — AI Readiness Tab (SEO-003 / api #84)
   ─────────────────────────────────────────────────────────────────────
   Surfaces the site-level `ai_readiness` payload (schema.org structured
   data → how well AI search engines / LLM crawlers / AI Overviews can
   understand the site) that /brain/scan returns alongside scan results.
   Sibling tab to the other scan-result tabs. Reads ONLY what /brain/scan
   returns (no second data source — Provenance, locked rule #14):
     data._raw.ai_readiness  — site-level { score, band, complete,
       partial_reason, score_basis, present_types, formats_found,
       readable_formats, breakdown, type_report, recommendations,
       jsonld_parse_errors, weights, pages_analyzed, note }
     data._raw.pages[].schema — per-page { formats, types, detected,
       jsonld_parse_errors } (Task C drilldown, site-level otherwise)

   Bands: high ≥75 / medium ≥40 / low. `score` MAY be null (SPA/blocked).

   ── Radical Transparency (locked rule #13 — NO false zeros) ──
   When score === null OR complete === false we render the honest-incomplete
   state from partial_reason, NEVER a 0 or a fabricated ring:
     · client_rendered + score_basis json_ld_only → partial score, labelled
       "head-only" (JSON-LD in the served HTML read; body-rendered schema not)
     · blocked / unreachable / unreadable → no score, honest message
   Recommendations are rendered as guidance, never guarantees.

   ── Role / plan ──
   No new entitlement, no NAV/ALLOWED widening — renders within the existing
   scan-results surface gating (the API attaches ai_readiness to the scan
   response it already authorised). If the running API predates #84 the field
   is absent and we render the honest "not in this scan yet" state.

   ── Analytics (locked rule #32) ──
   `seo.ai_readiness.viewed` is now RESERVED server-side in analytics.js
   KNOWN_EVENT_TYPES (alongside seo.rank.viewed / seo.keyword.opened — api
   #86), so the deferred emit is wired: a fire-and-forget POST to the
   customer-scoped /client/events/track (requireJWT('wpsb-app'); account_id +
   user_id come FROM the JWT, never the body; unknown keys dropped; 202, never
   blocks render). Emitted once on mount when the tab is viewed. Honest props
   (`scored`/`band`) so an open of the not-served/no-score state isn't counted
   as a real score view. NOT the SA-only /events/track (would drop a customer
   JWT's event).

   v1.1.0 — 2026-06-25. CSS var() tokens only; WCAG 2.1 AA. IIFE, JSX.
   Registers window.ScannerAiReadinessTab. */
(function () {
  'use strict';
  const Icon = window.Icon;
  const { useEffect } = React;

  function apiBase() { return (window.WPSBD && window.WPSBD.apiBase) || 'https://api.wpsitebeam.io'; }
  function authToken() {
    return (window.WPSB && window.WPSB.getToken && window.WPSB.getToken())
        || (function () { try { return localStorage.getItem('wpsb-auth-token'); } catch (e) { return null; } })();
  }
  /* Customer-scoped client analytics ingest. account_id + user_id come from the
     JWT server-side — we NEVER send account_id in the body. Fire-and-forget:
     any failure is swallowed so analytics can never block or break the tab.
     Targets /client/events/track (NOT the SA-only /events/track, which would
     silently drop a customer JWT's event). */
  function emitClientEvent(eventType, props) {
    try {
      const token = authToken(); if (!token) return;
      fetch(apiBase() + '/client/events/track', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token },
        body: JSON.stringify({ event_type: eventType, props: props || {} }),
      }).catch(function () {});
    } catch (_) { /* swallow — analytics never blocks the tab */ }
  }

  /* Band → semantic color (high ≥75 / medium ≥40 / low). Mirrors the API
     bands, not ScoreRing's own green≥80 default — pass the colour through. */
  function bandColor(band) {
    return band === 'high' ? 'var(--green)' : band === 'medium' ? 'var(--warn)' : 'var(--red)';
  }
  function bandLabel(band) {
    return band === 'high' ? 'High' : band === 'medium' ? 'Medium' : band === 'low' ? 'Low' : 'Unknown';
  }
  /* Human labels for the deterministic breakdown components. */
  const COMPONENT_LABELS = {
    identity: 'Entity identity',
    site_definition: 'Site definition',
    navigation: 'Navigation context',
    primary_content: 'Primary content',
    format_quality: 'Format quality',
    completeness: 'Schema completeness',
  };

  function Chip({ ok, label, title }) {
    const tone = ok ? 'var(--green)' : 'var(--dim)';
    return (
      <span title={title} style={{ display: 'inline-flex', alignItems: 'center', gap: 5,
        padding: '4px 9px', borderRadius: 999, fontSize: '.72rem', fontWeight: 600,
        background: ok ? 'var(--green-dim)' : 'rgba(0,0,0,.15)', color: tone,
        border: '1px solid ' + (ok ? 'var(--green-dim)' : 'var(--border)') }}>
        {Icon ? <Icon name={ok ? 'check' : 'x'} size={11} /> : null}{label}
      </span>
    );
  }

  /* Honest "no score" panel — rendered when score === null (blocked /
     unreachable / unreadable). NEVER a 0 ring. */
  function NoScorePanel({ ar }) {
    const reason = ar.partial_reason || 'unknown';
    const headline = reason === 'blocked'
      ? 'The page blocked our public crawler'
      : reason === 'unreachable'
        ? 'The page wasn’t reachable'
        : 'Structured data couldn’t be read';
    const readable = Array.isArray(ar.readable_formats) ? ar.readable_formats : [];
    return (
      <div className="card" style={{ borderColor: 'var(--orange-dim)' }}>
        <div className="card-body" style={{ display: 'flex', gap: 14, alignItems: 'flex-start' }}>
          <div style={{ width: 38, height: 38, borderRadius: 10, flexShrink: 0, display: 'flex',
            alignItems: 'center', justifyContent: 'center', background: 'var(--orange-dim)', color: 'var(--warn)' }}>
            {Icon ? <Icon name="warn" size={18} /> : '!'}
          </div>
          <div style={{ minWidth: 0 }}>
            <div style={{ fontSize: '.92rem', fontWeight: 700, color: 'var(--text)' }}>{headline}</div>
            <p style={{ margin: '6px 0 0', fontSize: '.8rem', color: 'var(--dim)', lineHeight: 1.6 }}>
              {ar.note || 'No score is shown rather than a misleading 0.'}
            </p>
            {readable.length ? (
              <p style={{ margin: '8px 0 0', fontSize: '.74rem', color: 'var(--dim)', lineHeight: 1.6 }}>
                We could still read: {readable.join(', ')}.
              </p>
            ) : null}
            <div style={{ marginTop: 10, fontSize: '.68rem', color: 'var(--dim)', fontFamily: 'var(--font-mono)' }}>
              status: {reason}{ar.score_basis ? ' · basis: ' + ar.score_basis : ''}
            </div>
          </div>
        </div>
      </div>
    );
  }

  /* Hero — score ring (band-coloured) + band + partial/head-only disclosure. */
  function Hero({ ar }) {
    const score = ar.score;
    const color = bandColor(ar.band);
    const headOnly = ar.complete === false && ar.score_basis === 'json_ld_only';
    return (
      <div className="card">
        <div className="card-body" style={{ display: 'flex', gap: 18, alignItems: 'center', flexWrap: 'wrap' }}>
          <div style={{ flexShrink: 0 }}>
            {window.ScoreRing
              ? <window.ScoreRing value={score} label="AI Readiness" size={92} color={color} />
              : <div style={{ fontSize: '1.6rem', fontWeight: 800, color: color, fontFamily: 'var(--font-mono)' }}>{score}<span style={{ fontSize: '.8rem', color: 'var(--dim)' }}>/100</span></div>}
          </div>
          <div style={{ flex: 1, minWidth: 220 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
              <span style={{ fontSize: '1.05rem', fontWeight: 700, color: 'var(--text)' }}>AI Readiness</span>
              <span style={{ padding: '3px 10px', borderRadius: 999, fontSize: '.72rem', fontWeight: 700,
                background: color, color: '#fff', letterSpacing: '.02em' }}>{bandLabel(ar.band)}</span>
              {headOnly ? (
                <span title="Score is based on JSON-LD in the served HTML only — body-rendered schema (microdata/RDFa) couldn’t be read on this client-rendered page."
                  style={{ padding: '3px 10px', borderRadius: 999, fontSize: '.7rem', fontWeight: 600,
                  background: 'var(--orange-dim)', color: 'var(--warn)', border: '1px solid var(--orange-dim)' }}>
                  Partial · head-only
                </span>
              ) : null}
            </div>
            <p style={{ margin: '8px 0 0', fontSize: '.8rem', color: 'var(--dim)', lineHeight: 1.6, maxWidth: 560 }}>
              How well AI search engines (LLM crawlers, AI Overviews) can understand this site from its
              public structured data. Deterministic, AI-free — read straight from the served HTML.
            </p>
            {headOnly ? (
              <p style={{ margin: '8px 0 0', fontSize: '.74rem', color: 'var(--warn)', lineHeight: 1.6, maxWidth: 560 }}>
                This page is client-rendered. We read the JSON-LD in the served HTML; body-rendered
                microdata/RDFa couldn’t be read, so this score reflects readable JSON-LD only.
              </p>
            ) : null}
          </div>
        </div>
      </div>
    );
  }

  /* Deterministic score breakdown — earned/max per weighted component. */
  function Breakdown({ ar }) {
    const rows = Array.isArray(ar.breakdown) ? ar.breakdown : [];
    if (!rows.length) return null;
    return (
      <div className="card">
        <div className="card-body">
          <div style={{ fontSize: '.86rem', fontWeight: 700, color: 'var(--text)', marginBottom: 4 }}>Score breakdown</div>
          <div style={{ fontSize: '.72rem', color: 'var(--dim)', marginBottom: 12 }}>How the 0–100 score is composed (weighted categories).</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
            {rows.map(function (b, i) {
              const max = b.max || 0;
              const earned = b.earned || 0;
              const pct = max ? Math.round((earned / max) * 100) : 0;
              const tone = pct >= 75 ? 'var(--green)' : pct >= 40 ? 'var(--warn)' : 'var(--red)';
              return (
                <div key={b.component || i}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: 10, marginBottom: 5 }}>
                    <span style={{ fontSize: '.78rem', fontWeight: 600, color: 'var(--text)' }}>
                      {COMPONENT_LABELS[b.component] || b.component}
                    </span>
                    <span style={{ fontSize: '.72rem', fontFamily: 'var(--font-mono)', color: 'var(--dim)' }}>
                      {earned}<span style={{ opacity: .6 }}> / {max}</span>
                    </span>
                  </div>
                  <div style={{ height: 7, borderRadius: 4, background: 'rgba(0,0,0,.15)', overflow: 'hidden' }}>
                    <div style={{ width: pct + '%', height: '100%', background: tone, borderRadius: 4, transition: 'width .5s ease' }} />
                  </div>
                  {b.why ? <div style={{ fontSize: '.7rem', color: 'var(--dim)', marginTop: 5, lineHeight: 1.5 }}>{b.why}</div> : null}
                </div>
              );
            })}
          </div>
        </div>
      </div>
    );
  }

  /* Formats found + schema types present (the two read-from-payload lists). */
  function FormatsAndTypes({ ar }) {
    const f = ar.formats_found || {};
    const types = Array.isArray(ar.present_types) ? ar.present_types : [];
    const parseErrors = ar.jsonld_parse_errors || 0;
    return (
      <div className="card">
        <div className="card-body">
          <div style={{ fontSize: '.86rem', fontWeight: 700, color: 'var(--text)', marginBottom: 12 }}>Structured-data formats</div>
          <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: parseErrors ? 10 : 0 }}>
            <Chip ok={!!f.json_ld} label="JSON-LD" title="JSON-LD — the format AI crawlers parse most reliably" />
            <Chip ok={!!f.microdata} label="Microdata" title="Microdata (itemscope/itemtype)" />
            <Chip ok={!!f.rdfa} label="RDFa" title="RDFa (typeof/property)" />
          </div>
          {parseErrors ? (
            <div style={{ display: 'flex', gap: 8, alignItems: 'center', fontSize: '.74rem', color: 'var(--warn)' }}>
              {Icon ? <Icon name="warn" size={13} /> : null}
              {parseErrors} JSON-LD block{parseErrors !== 1 ? 's' : ''} couldn’t be parsed (invalid JSON).
            </div>
          ) : null}

          <div style={{ fontSize: '.86rem', fontWeight: 700, color: 'var(--text)', margin: '18px 0 10px' }}>
            Schema types detected{types.length ? ' (' + types.length + ')' : ''}
          </div>
          {types.length ? (
            <div style={{ display: 'flex', gap: 7, flexWrap: 'wrap' }}>
              {types.map(function (t) {
                return (
                  <span key={t} style={{ padding: '4px 10px', borderRadius: 6, fontSize: '.74rem', fontWeight: 600,
                    background: 'var(--beam-dim)', color: 'var(--beam)', border: '1px solid var(--beam-dim)', fontFamily: 'var(--font-mono)' }}>{t}</span>
                );
              })}
            </div>
          ) : (
            <div style={{ fontSize: '.78rem', color: 'var(--dim)', lineHeight: 1.6 }}>
              No schema.org types were detected in the served HTML.
            </div>
          )}
        </div>
      </div>
    );
  }

  /* Per-type completeness (type_report) — missing required/recommended props. */
  function TypeReport({ ar }) {
    const rows = (Array.isArray(ar.type_report) ? ar.type_report : []).filter(function (t) { return t && t.known; });
    if (!rows.length) return null;
    return (
      <div className="card">
        <div className="card-body">
          <div style={{ fontSize: '.86rem', fontWeight: 700, color: 'var(--text)', marginBottom: 4 }}>Completeness by type</div>
          <div style={{ fontSize: '.72rem', color: 'var(--dim)', marginBottom: 12 }}>Required + recommended properties present, per detected type.</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {rows.map(function (t, i) {
              const c = typeof t.completeness === 'number' ? t.completeness : null;
              const tone = c == null ? 'var(--dim)' : c >= 75 ? 'var(--green)' : c >= 40 ? 'var(--warn)' : 'var(--red)';
              const missReq = Array.isArray(t.missing_required) ? t.missing_required : [];
              const missRec = Array.isArray(t.missing_recommended) ? t.missing_recommended : [];
              return (
                <div key={t.type || i} style={{ borderBottom: i < rows.length - 1 ? '1px solid var(--border)' : 'none', paddingBottom: 10 }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 10 }}>
                    <span style={{ fontSize: '.8rem', fontWeight: 600, color: 'var(--text)', fontFamily: 'var(--font-mono)' }}>{t.type}</span>
                    <span style={{ fontSize: '.74rem', fontWeight: 700, color: tone }}>{c == null ? '—' : c + '%'}</span>
                  </div>
                  {missReq.length ? (
                    <div style={{ fontSize: '.7rem', color: 'var(--red)', marginTop: 4, lineHeight: 1.5 }}>
                      Missing required: {missReq.join(', ')}
                    </div>
                  ) : null}
                  {missRec.length ? (
                    <div style={{ fontSize: '.7rem', color: 'var(--dim)', marginTop: 3, lineHeight: 1.5 }}>
                      Missing recommended: {missRec.join(', ')}
                    </div>
                  ) : null}
                </div>
              );
            })}
          </div>
        </div>
      </div>
    );
  }

  /* Recommendations — guidance, NOT guarantees (Radical Transparency). */
  function Recommendations({ ar }) {
    const recs = Array.isArray(ar.recommendations) ? ar.recommendations : [];
    if (!recs.length) return null;
    return (
      <div className="card">
        <div className="card-body">
          <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 4 }}>
            {Icon ? <Icon name="sparkles" size={15} style={{ color: 'var(--beam)' }} /> : null}
            <span style={{ fontSize: '.86rem', fontWeight: 700, color: 'var(--text)' }}>Recommendations</span>
          </div>
          <div style={{ fontSize: '.72rem', color: 'var(--dim)', marginBottom: 12, lineHeight: 1.5 }}>
            Guidance to improve AI comprehension — not a guarantee of ranking or inclusion.
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {recs.map(function (r, i) {
              return (
                <div key={(r.type || '') + i} style={{ display: 'flex', gap: 10, alignItems: 'flex-start',
                  padding: '10px 12px', borderRadius: 8, background: 'rgba(0,0,0,.15)', border: '1px solid var(--border)' }}>
                  <span style={{ padding: '2px 8px', borderRadius: 5, fontSize: '.68rem', fontWeight: 700, flexShrink: 0,
                    background: 'var(--beam-dim)', color: 'var(--beam)', fontFamily: 'var(--font-mono)' }}>{r.type}</span>
                  <span style={{ fontSize: '.76rem', color: 'var(--text)', lineHeight: 1.55 }}>{r.reason}</span>
                </div>
              );
            })}
          </div>
        </div>
      </div>
    );
  }

  /* Per-page schema drilldown (Task C) — reads pages[].schema directly. */
  function PerPageSchema({ pages }) {
    const rows = (pages || []).filter(function (p) { return p && p.schema; });
    if (!rows.length) return null;
    return (
      <div className="card">
        <div className="card-body">
          <div style={{ fontSize: '.86rem', fontWeight: 700, color: 'var(--text)', marginBottom: 4 }}>Schema by page</div>
          <div style={{ fontSize: '.72rem', color: 'var(--dim)', marginBottom: 12 }}>Structured-data types found on each crawled page.</div>
          <div style={{ overflowX: 'auto' }}>
            <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '.76rem' }}>
              <thead>
                <tr style={{ textAlign: 'left', color: 'var(--dim)', fontSize: '.68rem', textTransform: 'uppercase', letterSpacing: '.04em' }}>
                  <th style={{ padding: '6px 8px', fontWeight: 600 }}>Page</th>
                  <th style={{ padding: '6px 8px', fontWeight: 600 }}>Formats</th>
                  <th style={{ padding: '6px 8px', fontWeight: 600 }}>Types</th>
                </tr>
              </thead>
              <tbody>
                {rows.map(function (p, i) {
                  const s = p.schema || {};
                  const f = s.formats || {};
                  const fmts = [f.json_ld && 'JSON-LD', f.microdata && 'Microdata', f.rdfa && 'RDFa'].filter(Boolean);
                  const types = Array.isArray(s.types) ? s.types : [];
                  let path = p.url || '';
                  try { path = new URL(p.url).pathname || p.url; } catch (e) {}
                  return (
                    <tr key={(p.url || '') + i} style={{ borderTop: '1px solid var(--border)' }}>
                      <td style={{ padding: '7px 8px', color: 'var(--text)', maxWidth: 280, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontFamily: 'var(--font-mono)' }} title={p.url}>{path}</td>
                      <td style={{ padding: '7px 8px', color: fmts.length ? 'var(--text)' : 'var(--dim)' }}>{fmts.length ? fmts.join(' · ') : '—'}</td>
                      <td style={{ padding: '7px 8px', color: types.length ? 'var(--text)' : 'var(--dim)' }}>{types.length ? types.join(', ') : '—'}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        </div>
      </div>
    );
  }

  function ScannerAiReadinessTab({ data }) {
    const ar = (data && data.aiReadiness) || (data && data._raw && data._raw.ai_readiness) || null;
    const pages = (data && data._raw && Array.isArray(data._raw.pages)) ? data._raw.pages : [];

    // seo.ai_readiness.viewed — AI Readiness tab opened/viewed. Fire-and-forget,
    // fail-soft, once on mount. Honest props: `scored` reflects whether a real
    // score was present (so opening the not-served / no-score state is not
    // counted as a score view — no false signal). Key reserved server-side in
    // analytics.js KNOWN_EVENT_TYPES; a typo would be dropped silently.
    useEffect(function () {
      emitClientEvent('seo.ai_readiness.viewed', {
        surface: 'scanner_ai_readiness',
        scored: typeof (ar && ar.score) === 'number',
        band: (ar && ar.band) || null,
      });
    }, []);

    /* Not-served-yet — the running API predates #84 (field absent). Honest. */
    if (!ar) {
      return (
        <div style={{ padding: '36px 24px', maxWidth: 600, margin: '0 auto', textAlign: 'center' }}>
          <div style={{ width: 48, height: 48, borderRadius: 12, margin: '0 auto 14px', display: 'flex',
            alignItems: 'center', justifyContent: 'center', background: 'var(--beam-dim)', color: 'var(--beam)' }}>
            {Icon ? <Icon name="sparkles" size={22} /> : '✦'}
          </div>
          <div style={{ fontWeight: 700, color: 'var(--text)', marginBottom: 8, fontSize: '.95rem' }}>AI Readiness isn’t in this scan yet</div>
          <div style={{ fontSize: '.8rem', color: 'var(--dim)', lineHeight: 1.6 }}>
            The AI Readiness score (schema.org structured data → how AI search engines understand your site)
            arrives with the latest scan engine. Run a fresh full scan to populate it.
          </div>
        </div>
      );
    }

    const hasScore = typeof ar.score === 'number';

    return (
      <div style={{ display: 'flex', flexDirection: 'column', gap: 14, padding: '4px 2px' }}>
        {hasScore ? <Hero ar={ar} /> : <NoScorePanel ar={ar} />}
        {hasScore ? <Breakdown ar={ar} /> : null}
        <FormatsAndTypes ar={ar} />
        {hasScore ? <TypeReport ar={ar} /> : null}
        <Recommendations ar={ar} />
        <PerPageSchema pages={pages} />
        {/* Honest footer — what this is, and what it isn't. */}
        <div style={{ fontSize: '.7rem', color: 'var(--dim)', lineHeight: 1.6, padding: '0 2px' }}>
          {ar.note ? <span>{ar.note} </span> : null}
          Deterministic, AI-free — based on the public structured data in the served HTML
          {typeof ar.pages_analyzed === 'number' ? ' across ' + ar.pages_analyzed + ' page' + (ar.pages_analyzed !== 1 ? 's' : '') : ''}.
          Recommendations are guidance, not guarantees.
        </div>
      </div>
    );
  }

  window.ScannerAiReadinessTab = ScannerAiReadinessTab;
  console.log('[WPSB] ScannerAiReadinessTab v1.1.0 loaded (seo.ai_readiness.viewed client emit wired)');
})();
