// useScanReplay — single rAF-driven playback engine.
//
// Exposes: { terminalLines, graphState, counters, isPlaying, elapsedMs,
//            restart, setSpeed, togglePause }
//
// - Drives ALL events off a single requestAnimationFrame loop
// - Character-by-character typing is computed from a perLineTypeMs setting;
//   each line has a `typedChars` field the terminal uses for the caret.
// - Pauses when document.hidden (Page Visibility API)
// - Respects prefers-reduced-motion: jumps to final state, no animation
// - First mount starts ~30% through the loop (per spec acceptance)

function useScanReplay(opts = {}) {
  const { React } = window;
  const { useState, useEffect, useRef, useMemo } = React;

  const script   = window.SCAN_REPLAY;
  const LOOP_MS  = window.SCAN_REPLAY_DURATION;
  const reduce   = useMemo(() =>
    window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches,
    []
  );

  const startOffset = opts.startOffsetMs ?? Math.floor(LOOP_MS * 0.30);
  const maxLines    = opts.maxLines ?? 26;
  const typeCharsPerSec = opts.typeCharsPerSec ?? 260;

  const [tick, setTick]   = useState(0);
  const [speed, setSpeed] = useState(1);
  const [paused, setPaused] = useState(false);

  const startWallRef = useRef(performance.now() - startOffset);
  const elapsedRef   = useRef(startOffset);
  const rafRef       = useRef(0);
  const lastVisibleRef = useRef(true);

  // ----- derived state, recomputed every tick from elapsedMs -----
  const { terminalLines, graphState, counters, elapsedMs } = useMemo(() => {
    const now = elapsedRef.current % LOOP_MS;

    // Graph nodes (static layout — see AttackGraphPane)
    const nodeIds = ['api.gateway','auth.svc','payments.api','users.api',
                     'orders.api','db.postgres','redis.cache','s3.receipts'];
    const nodes = Object.fromEntries(nodeIds.map(id => [id, 'idle']));
    const edges = [];
    let findings = 0, exploited = 0;

    // Terminal lines: all log events with t <= now
    const logs = [];
    for (const ev of script) {
      if (ev.t > now) break;
      if (ev.kind === 'log') logs.push(ev);
      else if (ev.kind === 'node_state') nodes[ev.nodeId] = ev.state;
      else if (ev.kind === 'edge') edges.push({ ...ev, tAdded: ev.t });
      else if (ev.kind === 'counter') { findings = ev.findings; exploited = ev.exploited; }
    }

    // Typing progress: only the last log line types; earlier lines are whole.
    const lineSlice = logs.slice(-maxLines);
    const lineCount = lineSlice.length;
    const withTyping = lineSlice.map((ev, i) => {
      const isLast = i === lineCount - 1;
      if (reduce || !isLast) {
        return { ...ev, typedChars: ev.text.length, typing: false };
      }
      const ageMs = now - ev.t;
      const chars = Math.max(0, Math.min(ev.text.length,
        Math.floor((ageMs / 1000) * typeCharsPerSec)));
      return { ...ev, typedChars: chars, typing: chars < ev.text.length };
    });

    return {
      terminalLines: withTyping,
      graphState: { nodes, edges, now },
      counters: { findings, exploited },
      elapsedMs: now,
    };
    // eslint-disable-next-line
  }, [tick, reduce, maxLines, typeCharsPerSec]);

  // ----- rAF loop -----
  useEffect(() => {
    if (reduce) {
      elapsedRef.current = LOOP_MS - 200; // near-final snapshot
      setTick(t => t + 1);
      return;
    }
    let last = performance.now();
    const loop = (t) => {
      const dt = t - last;
      last = t;
      if (!paused && !document.hidden) {
        elapsedRef.current = (elapsedRef.current + dt * speed) % LOOP_MS;
      }
      setTick(x => (x + 1) & 0xffff);
      rafRef.current = requestAnimationFrame(loop);
    };
    rafRef.current = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(rafRef.current);
  }, [paused, speed, reduce]);

  // visibility handling: reset `last` timestamp when returning so we don't jump
  useEffect(() => {
    const onVis = () => {
      if (!document.hidden && !lastVisibleRef.current) {
        // nothing to do — rAF resumes; elapsedRef frozen while hidden
      }
      lastVisibleRef.current = !document.hidden;
    };
    document.addEventListener('visibilitychange', onVis);
    return () => document.removeEventListener('visibilitychange', onVis);
  }, []);

  const restart     = () => { elapsedRef.current = 0; setTick(t => t + 1); };
  const togglePause = () => setPaused(p => !p);

  return {
    terminalLines, graphState, counters, elapsedMs,
    isPlaying: !paused && !reduce,
    restart, setSpeed, togglePause, speed, paused, reduceMotion: reduce,
    loopMs: LOOP_MS,
  };
}

window.useScanReplay = useScanReplay;
