// engine.jsx — RD8 explainer engine
// WebCodecs MP4 decode → ImageBitmap frames → canvas player, plus on-brand
// window chrome and a small monoline icon set. Loads React (global) + MP4Box (global).
// Exported to window: loadClip, ClipCanvas, AppWindow, Icon, DotGrid, ScanLines

// ── MP4 demux + WebCodecs decode → array of downscaled ImageBitmaps ──────────
// Decodes sequentially (H.264 deltas) but only keeps `targetFrames` evenly
// sampled frames, downscaled to maxW. Returns { frames:[ImageBitmap], w, h }.
async function loadClip(path, { maxW = 760, targetFrames = 150, onProgress } = {}) {
  const ab = await fetch(path).then((r) => r.arrayBuffer());
  const bytes = new Uint8Array(ab);
  const dv = new DataView(ab);
  // locate the avcC box (codec config) by fourcc scan
  let ai = -1;
  for (let i = 8; i < bytes.length - 4; i++) {
    if (bytes[i] === 0x61 && bytes[i + 1] === 0x76 && bytes[i + 2] === 0x63 && bytes[i + 3] === 0x43) { ai = i; break; }
  }
  if (ai < 0) throw new Error('avcC not found in ' + path);
  const boxSize = dv.getUint32(ai - 4);
  const desc = bytes.slice(ai + 4, ai - 4 + boxSize);

  const buf = ab.slice(0); buf.fileStart = 0;
  const mp4 = MP4Box.createFile();
  const info = await new Promise((res, rej) => {
    mp4.onError = rej;
    mp4.onReady = res;
    mp4.appendBuffer(buf);
    mp4.flush();
  });
  const track = info.videoTracks[0];
  const N = track.nb_samples;
  const samples = await new Promise((res) => {
    let acc = [];
    mp4.onSamples = (id, user, s) => { acc = acc.concat(s); if (acc.length >= N) res(acc); };
    mp4.setExtractionOptions(track.id, null, { nbSamples: N });
    mp4.start();
    setTimeout(() => res(acc), 8000);
  });

  const srcW = track.video.width, srcH = track.video.height;
  const w = Math.round(maxW);
  const h = Math.round(maxW * srcH / srcW);

  const tf = Math.min(targetFrames, samples.length);
  const want = new Map(); // sample index -> output order
  for (let k = 0; k < tf; k++) {
    const idx = Math.round((k / (tf - 1)) * (samples.length - 1));
    if (!want.has(idx)) want.set(idx, want.size);
  }
  const maxIdx = Math.max(...want.keys());
  const frames = new Array(want.size);
  const tasks = [];

  await new Promise((res, rej) => {
    let idx = 0;
    let settled = false;
    const finish = () => { if (!settled) { settled = true; res(); } };
    // hard ceiling: never let a stalled decoder hang the loader
    const guard = setTimeout(finish, 12000);
    const dec = new VideoDecoder({
      output: (frame) => {
        if (want.has(idx)) {
          const order = want.get(idx);
          tasks.push(
            createImageBitmap(frame, { resizeWidth: w, resizeHeight: h, resizeQuality: 'high' })
              .then((bmp) => { frames[order] = bmp; frame.close(); })
              .catch(() => frame.close())
          );
        } else {
          frame.close();
        }
        idx++;
        if (onProgress && (idx % 12 === 0)) onProgress(Math.min(1, idx / (maxIdx + 1)));
        if (idx > maxIdx) { clearTimeout(guard); finish(); }
      },
      error: (e) => { clearTimeout(guard); if (!settled) { settled = true; rej(e); } },
    });
    dec.configure({ codec: track.codec, description: desc, codedWidth: srcW, codedHeight: srcH });

    // Feed chunks but THROTTLE: if the decoder's queue grows too large it can
    // stall or run out of memory on weaker hardware. Yield whenever the queue
    // is deep so the decoder can drain.
    (async () => {
      for (let i = 0; i <= maxIdx; i++) {
        if (settled) return;
        while (dec.decodeQueueSize > 24) {
          await new Promise((r) => setTimeout(r, 8));
          if (settled) return;
        }
        try {
          dec.decode(new EncodedVideoChunk({ type: i === 0 ? 'key' : 'delta', timestamp: (i * 1e6) / 30, data: samples[i].data }));
        } catch (e) { /* decoder closed mid-feed */ return; }
      }
      try { await dec.flush(); } catch (e) { /* flushed after close */ }
      clearTimeout(guard);
      finish();
    })();
  });

  await Promise.all(tasks);
  if (onProgress) onProgress(1);
  return { frames: frames.filter(Boolean), w, h, srcW, srcH };
}

// ── ClipCanvas — draws the right frame for the current sprite localTime ──────
// playSeconds = wall-clock time to play through all frames once.
function ClipCanvas({ clip, width, height, playSeconds = 12, loop = true, radius = 0, style = {} }) {
  const ref = React.useRef(null);
  const { localTime } = window.useSprite();
  const N = clip ? clip.frames.length : 0;

  React.useEffect(() => {
    const cv = ref.current;
    if (!cv || !clip || N === 0) return;
    const ctx = cv.getContext('2d');
    let u = playSeconds > 0 ? localTime / playSeconds : 0;
    u = loop ? ((u % 1) + 1) % 1 : window.clamp(u, 0, 1);
    let i = Math.floor(u * N);
    if (i >= N) i = N - 1;
    if (i < 0) i = 0;
    const bmp = clip.frames[i];
    if (!bmp) return;
    // object-fit: cover
    const cw = cv.width, ch = cv.height;
    const sRatio = bmp.width / bmp.height, dRatio = cw / ch;
    let sw, sh, sx, sy;
    if (sRatio > dRatio) { sh = bmp.height; sw = sh * dRatio; sx = (bmp.width - sw) / 2; sy = 0; }
    else { sw = bmp.width; sh = sw / dRatio; sx = 0; sy = (bmp.height - sh) / 2; }
    ctx.drawImage(bmp, sx, sy, sw, sh, 0, 0, cw, ch);
  });

  // device-pixel sharp
  const dpr = Math.min(2, window.devicePixelRatio || 1);
  return (
    <canvas
      ref={ref}
      width={Math.round(width * dpr)}
      height={Math.round(height * dpr)}
      style={{ width, height, display: 'block', borderRadius: radius, ...style }}
    />
  );
}

// ── AppWindow — on-brand dark application/browser chrome around media ────────
function AppWindow({
  width, height, variant = 'app', title = 'RD8.SOFTWARE',
  tabs = ['OVERVIEW', 'PARTS', 'INTERFACES', 'CALCULATIONS'], activeTab = 0,
  url = 'rd8.ai/screening', glow = true, children, style = {},
}) {
  const barH = 46;
  return (
    <div style={{
      width, height: height + barH,
      borderRadius: 12,
      overflow: 'hidden',
      border: '1px solid var(--border-strong)',
      backgroundImage: 'var(--surface-gradient-panel)',
      boxShadow: glow ? 'var(--glow-green), 0 30px 80px rgba(0,0,0,.6)' : '0 30px 80px rgba(0,0,0,.6)',
      ...style,
    }}>
      <div style={{
        height: barH, display: 'flex', alignItems: 'center', gap: 14,
        padding: '0 16px', borderBottom: '1px solid var(--border-subtle)',
        backgroundImage: 'var(--surface-gradient-deep)',
      }}>
        <div style={{ display: 'flex', gap: 7 }}>
          {['#3a3a3a', '#3a3a3a', '#3a3a3a'].map((c, i) => (
            <span key={i} style={{ width: 11, height: 11, borderRadius: '50%', background: c }} />
          ))}
        </div>
        {variant === 'app' ? (
          <>
            <span style={{ fontFamily: 'var(--font-mono)', fontSize: 12, letterSpacing: '0.12em', color: 'var(--text-secondary)' }}>{title}</span>
            <div style={{ display: 'flex', gap: 18, marginLeft: 8 }}>
              {tabs.map((t, i) => (
                <span key={t} style={{
                  fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '0.1em',
                  color: i === activeTab ? 'var(--rd8-green-bright)' : 'var(--text-tertiary)',
                  borderBottom: i === activeTab ? '2px solid var(--rd8-green-bright)' : '2px solid transparent',
                  paddingBottom: 2,
                }}>{t}</span>
              ))}
            </div>
          </>
        ) : (
          <div style={{
            flex: 1, height: 26, borderRadius: 6, display: 'flex', alignItems: 'center', gap: 8,
            padding: '0 12px', background: 'var(--surface-canvas)', border: '1px solid var(--border-subtle)',
          }}>
            <Icon name="lock" size={13} color="var(--rd8-green-bright)" />
            <span style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--text-secondary)' }}>{url}</span>
          </div>
        )}
      </div>
      <div style={{ width, height, overflow: 'hidden', background: '#000' }}>{children}</div>
    </div>
  );
}

// ── Icon — thin monoline (1.5px) set matching RD8's product icons ────────────
const ICON_PATHS = {
  lock: '<rect x="5" y="11" width="14" height="9" rx="1.5"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/>',
  shield: '<path d="M12 3l7 3v5c0 4.5-3 7.5-7 9-4-1.5-7-4.5-7-9V6z"/><path d="M9 12l2 2 4-4"/>',
  file: '<path d="M14 3H7a1.5 1.5 0 0 0-1.5 1.5v15A1.5 1.5 0 0 0 7 21h10a1.5 1.5 0 0 0 1.5-1.5V7.5z"/><path d="M14 3v4.5h4.5"/>',
  cube: '<path d="M12 2.8l8 4.4v9.6l-8 4.4-8-4.4V7.2z"/><path d="M4 7.2l8 4.4 8-4.4"/><path d="M12 11.6V21"/>',
  upload: '<path d="M12 16V5"/><path d="M7 10l5-5 5 5"/><path d="M5 19h14"/>',
  clock: '<circle cx="12" cy="12" r="8.5"/><path d="M12 7v5l3.5 2"/>',
  check: '<path d="M5 12.5l4.5 4.5L19 7"/>',
  arrowRight: '<path d="M5 12h14"/><path d="M13 6l6 6-6 6"/>',
  pen: '<path d="M4 20l4-1 11-11-3-3L5 16z"/><path d="M13 5l3 3"/>',
  spark: '<path d="M12 3v4M12 17v4M3 12h4M17 12h4M6 6l2.5 2.5M15.5 15.5L18 18M18 6l-2.5 2.5M8.5 15.5L6 18"/>',
  layers: '<path d="M12 3l9 5-9 5-9-5z"/><path d="M3 13l9 5 9-5"/>',
  target: '<circle cx="12" cy="12" r="8.5"/><circle cx="12" cy="12" r="4"/><circle cx="12" cy="12" r="0.6" fill="currentColor" stroke="none"/>',
  ruler: '<rect x="3" y="8" width="18" height="8" rx="1.2"/><path d="M7 8v3M11 8v4M15 8v3M19 8v4"/>',
  download: '<path d="M12 4v11"/><path d="M7 11l5 5 5-5"/><path d="M5 20h14"/>',
};
function Icon({ name, size = 22, color = 'currentColor', stroke = 1.6, style = {} }) {
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none"
      stroke={color} strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"
      style={{ display: 'block', ...style }}
      dangerouslySetInnerHTML={{ __html: ICON_PATHS[name] || '' }} />
  );
}

// ── Decorative: faint engineering dot-grid background layer ──────────────────
function DotGrid({ opacity = 0.5, color = 'rgba(255,255,255,0.05)', size = 46 }) {
  return (
    <div style={{
      position: 'absolute', inset: 0, opacity,
      backgroundImage: `radial-gradient(${color} 1px, transparent 1px)`,
      backgroundSize: `${size}px ${size}px`,
      pointerEvents: 'none',
    }} />
  );
}

Object.assign(window, { loadClip, ClipCanvas, AppWindow, Icon, DotGrid });
