// cms-perf.jsx
// ─────────────────────────────────────────────────────────────────────────
// Media Performance — an admin-only diagnostic that measures how the site's
// real media (hero video, project covers, reel images/videos, headshots,
// service images) actually loads from R2, at whatever connection you open it on.
//
// It does ACTIVE measurement: each asset is fetched fresh with cache-busting +
// no-store, so you get a true COLD-load size and time (not a cached 0ms). It
// runs with small concurrency to mimic a browser, reports per-asset + totals,
// flags heavy/slow items, and can pop out to a standalone window for sharing.
//
// Exposes window.MediaPerf({ doc, onClose }). Load after React, before the app.
// ─────────────────────────────────────────────────────────────────────────

(function () {
  const { useState, useRef, useEffect } = React;

  // ── Collect every external media URL in the document, tagged by role ──
  function collectMedia(doc) {
    const out = [];
    const seen = new Set();
    const push = (url, role, label, kind) => {
      if (!url || typeof url !== "string" || url.startsWith("data:")) return;
      if (seen.has(url)) return;
      seen.add(url);
      out.push({ url, role, label: label || "—", kind });
    };
    (doc && doc.modules || []).forEach((m) => {
      const c = m.content || {};
      if (m.type === "hero") push(c.videoUrl, "Hero video", "Hero", "video");
      if (m.type === "work") (c.projects || []).forEach((p) => {
        push(p.cover && p.cover.url, "Cover", p.title, "image");
        push(p.video, "Project video", p.title, "video");
        (p.gallery || []).forEach((g, i) => push(g.url, g.type === "video" ? "Reel video" : "Reel image", (p.title || "Project") + " · " + (i + 1), g.type === "video" ? "video" : "image"));
      });
      if (m.type === "about") (c.team || []).forEach((t) => push(t.headshot && t.headshot.url, "Headshot", t.name, "image"));
      if (m.type === "services") (c.items || []).forEach((s) => push(s.image && s.image.url, "Service image", s.label, "image"));
    });
    return out;
  }

  // ── Fetch one asset fresh; return real bytes + wall time ──────────
  async function measureAsset(a) {
    const bust = a.url + (a.url.includes("?") ? "&" : "?") + "_perf=" + Date.now();
    const t0 = performance.now();
    try {
      const res = await fetch(bust, { cache: "no-store", mode: "cors" });
      if (!res.ok) return { ...a, ok: false, bytes: 0, ms: performance.now() - t0, note: "HTTP " + res.status };
      const buf = await res.arrayBuffer();
      return { ...a, ok: true, bytes: buf.byteLength, ms: performance.now() - t0 };
    } catch (e) {
      // CORS or network failure — can't measure from the browser.
      return { ...a, ok: false, bytes: 0, ms: performance.now() - t0, note: "blocked" };
    }
  }

  // ── Small concurrency pool ────────────────────────────────────────
  async function runPool(items, worker, concurrency, onProgress) {
    const results = new Array(items.length);
    let idx = 0, done = 0;
    async function next() {
      const i = idx++;
      if (i >= items.length) return;
      results[i] = await worker(items[i]);
      done++; if (onProgress) onProgress(done);
      return next();
    }
    await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, next));
    return results;
  }

  // ── Formatting ────────────────────────────────────────────────────
  const fmtBytes = (b) => {
    if (!b) return "—";
    if (b < 1024) return b + " B";
    if (b < 1024 * 1024) return (b / 1024).toFixed(0) + " KB";
    return (b / 1024 / 1024).toFixed(2) + " MB";
  };
  const fmtMs = (ms) => (ms >= 1000 ? (ms / 1000).toFixed(2) + " s" : Math.round(ms) + " ms");
  const fmtMbps = (bytes, ms) => (!bytes || !ms ? "—" : ((bytes * 8) / (ms / 1000) / 1e6).toFixed(1) + " Mbps");

  // Severity: images judged on weight; anything judged on time.
  function severity(r) {
    if (!r.ok) return "bad";
    if (r.kind === "image" && r.bytes > 1.5 * 1024 * 1024) return "bad";
    if (r.kind === "image" && r.bytes > 500 * 1024) return "warn";
    if (r.kind === "video" && r.bytes > 25 * 1024 * 1024) return "bad";
    if (r.kind === "video" && r.bytes > 10 * 1024 * 1024) return "warn";
    if (r.ms > 2500) return "warn";
    return "ok";
  }

  // ── Pop-out standalone report ─────────────────────────────────────
  function popOut(rows, totals) {
    const w = window.open("", "_blank", "width=820,height=900");
    if (!w) { alert("Pop-out was blocked by the browser. Allow popups for this site."); return; }
    const esc = (s) => String(s).replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" }[c]));
    const tr = rows.map((r) => `<tr class="${severity(r)}"><td>${esc(r.role)}</td><td>${esc(r.label)}</td><td>${r.kind}</td><td class="n">${r.ok ? fmtBytes(r.bytes) : (r.note || "—")}</td><td class="n">${fmtMs(r.ms)}</td><td class="n">${r.ok ? fmtMbps(r.bytes, r.ms) : "—"}</td></tr>`).join("");
    w.document.write(`<!doctype html><meta charset="utf-8"><title>Flairground — Media performance</title>
      <style>
        body{font:14px/1.5 -apple-system,system-ui,sans-serif;margin:0;background:#15120d;color:#efe9dd;padding:28px}
        h1{font-size:19px;margin:0 0 4px} .sub{color:#a59c89;font-size:12px;margin-bottom:20px}
        .cards{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:20px}
        .card{background:#211c14;border:1px solid #36302450;border-radius:10px;padding:12px 16px;min-width:120px}
        .card b{display:block;font-size:22px} .card span{color:#a59c89;font-size:11px;text-transform:uppercase;letter-spacing:.06em}
        table{width:100%;border-collapse:collapse;font-size:13px}
        th,td{text-align:left;padding:7px 10px;border-bottom:1px solid #2a2519}
        th{color:#a59c89;font-size:11px;text-transform:uppercase;letter-spacing:.06em}
        td.n{text-align:right;font-variant-numeric:tabular-nums}
        tr.warn td.n{color:#e0a93c} tr.bad td.n{color:#e8696b}
        .gen{margin-top:18px;color:#6f6856;font-size:11px}
      </style>
      <h1>Media performance</h1>
      <div class="sub">Flairground — cold-load measurement · ${new Date().toLocaleString()}</div>
      <div class="cards">
        <div class="card"><b>${totals.count}</b><span>Assets</span></div>
        <div class="card"><b>${fmtBytes(totals.bytes)}</b><span>Total weight</span></div>
        <div class="card"><b>${fmtMs(totals.wall)}</b><span>Load time</span></div>
        <div class="card"><b>${fmtMbps(totals.bytes, totals.wall)}</b><span>Throughput</span></div>
      </div>
      <table><thead><tr><th>Role</th><th>Label</th><th>Type</th><th>Size</th><th>Time</th><th>Speed</th></tr></thead><tbody>${tr}</tbody></table>
      <div class="gen">Measured from this device's connection. Times are cold (cache-busted). Cross-origin assets without CORS read as “blocked”.</div>`);
    w.document.close();
  }

  // ── The panel ─────────────────────────────────────────────────────
  function MediaPerf({ doc, onClose }) {
    const assets = useRef(collectMedia(doc)).current;
    const [rows, setRows] = useState(null);
    const [running, setRunning] = useState(false);
    const [progress, setProgress] = useState(0);
    const [totals, setTotals] = useState(null);

    useEffect(() => {
      const onKey = (e) => { if (e.key === "Escape" && !running) onClose(); };
      window.addEventListener("keydown", onKey);
      const prev = document.body.style.overflow; document.body.style.overflow = "hidden";
      return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = prev; };
    }, [running, onClose]);

    const run = async () => {
      setRunning(true); setProgress(0); setRows(null); setTotals(null);
      const wall0 = performance.now();
      const res = await runPool(assets, measureAsset, 4, (d) => setProgress(d));
      const wall = performance.now() - wall0;
      res.sort((a, b) => (b.bytes || 0) - (a.bytes || 0));
      const okRows = res.filter((r) => r.ok);
      const bytes = okRows.reduce((s, r) => s + r.bytes, 0);
      setRows(res);
      setTotals({ count: res.length, ok: okRows.length, bytes, wall });
      setRunning(false);
    };

    return (
      <div className="mp-modal" onClick={() => { if (!running) onClose(); }}>
        <style>{MP_CSS}</style>
        <div className="mp-panel" onClick={(e) => e.stopPropagation()}>
          <div className="mp-head">
            <div>
              <b>Media performance</b>
              <span className="mp-sub">{assets.length} asset{assets.length === 1 ? "" : "s"} · cold-load test from this device</span>
            </div>
            <div className="mp-head-btns">
              {totals && <button className="mp-btn ghost" onClick={() => popOut(rows, totals)}>Pop out ↗</button>}
              <button className="mp-x" onClick={() => { if (!running) onClose(); }} aria-label="Close">✕</button>
            </div>
          </div>

          {totals && (
            <div className="mp-cards">
              <div className="mp-card"><b>{totals.count}</b><span>Assets</span></div>
              <div className="mp-card"><b>{fmtBytes(totals.bytes)}</b><span>Total weight</span></div>
              <div className="mp-card"><b>{fmtMs(totals.wall)}</b><span>Load time</span></div>
              <div className="mp-card"><b>{fmtMbps(totals.bytes, totals.wall)}</b><span>Throughput</span></div>
            </div>
          )}

          <div className="mp-body">
            {!rows && !running && (
              <div className="mp-empty">
                <p>Fetches every media file fresh (cache-busted) to measure its real size and load time at your current connection speed.</p>
                <p className="mp-note">Each file is downloaded once — on a slow link a large hero video may take a moment.</p>
              </div>
            )}
            {running && (
              <div className="mp-empty">
                <div className="mp-bar"><div className="mp-bar-fill" style={{ width: `${(progress / Math.max(1, assets.length)) * 100}%` }} /></div>
                <p className="mp-note">Measuring… {progress} / {assets.length}</p>
              </div>
            )}
            {rows && (
              <table className="mp-table">
                <thead><tr><th>Role</th><th>Label</th><th className="n">Size</th><th className="n">Time</th><th className="n">Speed</th></tr></thead>
                <tbody>
                  {rows.map((r, i) => (
                    <tr key={i} className={"mp-" + severity(r)}>
                      <td><span className={"mp-dot mp-" + severity(r)} />{r.role}</td>
                      <td className="mp-label" title={r.url}>{r.label}</td>
                      <td className="n">{r.ok ? fmtBytes(r.bytes) : <span className="mp-fail">{r.note || "—"}</span>}</td>
                      <td className="n">{fmtMs(r.ms)}</td>
                      <td className="n mp-dim">{r.ok ? fmtMbps(r.bytes, r.ms) : "—"}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            )}
          </div>

          <div className="mp-foot">
            <button className="mp-btn" onClick={run} disabled={running || assets.length === 0}>
              {running ? "Measuring…" : rows ? "Run again" : "Run test"}
            </button>
            {rows && totals && (
              <span className="mp-summary">
                {totals.ok < totals.count ? `${totals.count - totals.ok} blocked · ` : ""}
                Heaviest: {rows[0] && rows[0].ok ? `${rows[0].label} (${fmtBytes(rows[0].bytes)})` : "—"}
              </span>
            )}
          </div>
        </div>
      </div>
    );
  }

  const MP_CSS = `
    .mp-modal{position:fixed;inset:0;z-index:2147483600;display:flex;align-items:center;justify-content:center;
      padding:24px;background:rgba(15,12,9,.62);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}
    .mp-panel{width:min(640px,100%);max-height:86vh;display:flex;flex-direction:column;color:#efe9dd;
      background:#1a160f;border:1px solid #ffffff14;border-radius:14px;overflow:hidden;
      box-shadow:0 30px 80px rgba(0,0,0,.55);font-family:'DM Sans',system-ui,sans-serif}
    .mp-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:16px 18px;border-bottom:1px solid #ffffff12}
    .mp-head b{font-size:15px;font-weight:600;letter-spacing:-.01em}
    .mp-sub{display:block;color:#a59c89;font-size:11.5px;margin-top:2px}
    .mp-head-btns{display:flex;align-items:center;gap:8px}
    .mp-x{appearance:none;border:1px solid #ffffff20;background:transparent;color:#cabfa8;width:32px;height:32px;border-radius:50%;cursor:pointer;font-size:14px}
    .mp-x:hover{border-color:#E08E3C;color:#fff}
    .mp-cards{display:flex;gap:10px;padding:14px 18px 4px;flex-wrap:wrap}
    .mp-card{flex:1 1 120px;background:#221c14;border:1px solid #ffffff10;border-radius:10px;padding:11px 13px}
    .mp-card b{display:block;font-size:21px;font-weight:600;letter-spacing:-.02em;font-variant-numeric:tabular-nums}
    .mp-card span{color:#a59c89;font-size:10px;text-transform:uppercase;letter-spacing:.08em}
    .mp-body{overflow-y:auto;padding:14px 18px}
    .mp-empty{padding:24px 8px;text-align:center;color:#bdb39d}
    .mp-empty p{margin:0 0 8px;font-size:13.5px;line-height:1.55}
    .mp-note{color:#8a8170;font-size:12px}
    .mp-bar{height:6px;background:#2a2417;border-radius:3px;overflow:hidden;margin:8px auto 14px;max-width:280px}
    .mp-bar-fill{height:100%;background:#E08E3C;transition:width .2s}
    .mp-table{width:100%;border-collapse:collapse;font-size:13px}
    .mp-table th,.mp-table td{text-align:left;padding:7px 8px;border-bottom:1px solid #ffffff0d}
    .mp-table th{color:#a59c89;font-size:10px;text-transform:uppercase;letter-spacing:.07em;font-weight:600}
    .mp-table td.n,.mp-table th.n{text-align:right;font-variant-numeric:tabular-nums}
    .mp-label{color:#cabfa8;max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .mp-dim{color:#8a8170}
    .mp-fail{color:#e8696b}
    .mp-dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:8px;vertical-align:middle;background:#4a8f5c}
    .mp-dot.mp-warn{background:#e0a93c} .mp-dot.mp-bad{background:#e8696b}
    tr.mp-warn td.n{color:#e0a93c} tr.mp-bad td.n{color:#e8696b}
    .mp-foot{display:flex;align-items:center;gap:12px;padding:14px 18px;border-top:1px solid #ffffff12}
    .mp-summary{color:#8a8170;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .mp-btn{appearance:none;border:0;background:#E08E3C;color:#1a160f;font:inherit;font-weight:600;font-size:13px;
      padding:9px 18px;border-radius:8px;cursor:pointer;flex-shrink:0}
    .mp-btn:hover{opacity:.9} .mp-btn:disabled{opacity:.5;cursor:default}
    .mp-btn.ghost{background:transparent;border:1px solid #ffffff20;color:#cabfa8;padding:6px 12px;font-size:12px}
    .mp-btn.ghost:hover{border-color:#E08E3C;color:#fff;opacity:1}
  `;

  window.MediaPerf = MediaPerf;
})();
