/* ============================================================
   HOOKS — scroll reveal, scramble text, typewriter
   ============================================================ */

const { useState, useEffect, useRef, useMemo, useCallback } = React;

// reveal on scroll
function useInView(opts = {}) {
  const ref = useRef(null);
  const [inView, setInView] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    const el = ref.current;
    const io = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setInView(true);
          io.disconnect();
        }
      },
      { threshold: opts.threshold ?? 0.15, rootMargin: opts.rootMargin ?? "0px 0px -80px 0px" }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return [ref, inView];
}

// scramble/decode text effect
const SCRAMBLE_CHARS = "!<>-_\\/[]{}—=+*^?#01ABCDEFGHIJKLMNOPQRSTUVWXYZ";
function useScramble(target, active, speed = 1) {
  const [text, setText] = useState("");
  const frameRef = useRef(0);
  const rafRef = useRef(0);
  const queueRef = useRef([]);

  useEffect(() => {
    if (!active) { setText(""); return; }
    const oldText = "";
    const newText = target;
    const length = Math.max(oldText.length, newText.length);
    const queue = [];
    for (let i = 0; i < length; i++) {
      const from = oldText[i] || "";
      const to = newText[i] || "";
      const start = Math.floor(Math.random() * 20 / speed);
      const end = start + Math.floor(Math.random() * 20 / speed) + 4;
      queue.push({ from, to, start, end, char: "" });
    }
    queueRef.current = queue;
    frameRef.current = 0;

    const tick = () => {
      let output = "";
      let complete = 0;
      for (let i = 0; i < queueRef.current.length; i++) {
        let { from, to, start, end, char } = queueRef.current[i];
        if (frameRef.current >= end) {
          complete++;
          output += to;
        } else if (frameRef.current >= start) {
          if (!char || Math.random() < 0.28) {
            char = SCRAMBLE_CHARS[Math.floor(Math.random() * SCRAMBLE_CHARS.length)];
            queueRef.current[i].char = char;
          }
          output += char;
        } else {
          output += from;
        }
      }
      setText(output);
      if (complete === queueRef.current.length) return;
      frameRef.current++;
      rafRef.current = requestAnimationFrame(tick);
    };
    tick();
    return () => cancelAnimationFrame(rafRef.current);
  }, [active, target]);
  return text;
}

// typewriter for a sequence of lines
function useTypewriter(lines, opts = {}) {
  const { charDelay = 14, lineDelay = 180, start = true } = opts;
  const [idx, setIdx] = useState(0);
  const [col, setCol] = useState(0);
  const [done, setDone] = useState(false);

  useEffect(() => {
    if (!start) return;
    if (idx >= lines.length) { setDone(true); return; }
    const line = lines[idx];
    if (col < line.text.length) {
      const t = setTimeout(() => setCol(c => c + 1), line.charDelay ?? charDelay);
      return () => clearTimeout(t);
    } else {
      const t = setTimeout(() => { setIdx(i => i + 1); setCol(0); }, line.wait ?? lineDelay);
      return () => clearTimeout(t);
    }
  }, [idx, col, lines, start]);

  const out = lines.slice(0, idx).map((l, i) => ({ ...l, text: l.text, i }));
  if (idx < lines.length) out.push({ ...lines[idx], text: lines[idx].text.slice(0, col), i: idx, partial: true });
  return { lines: out, done };
}

Object.assign(window, { useInView, useScramble, useTypewriter });
