// useScramble — sequential letter-by-letter glitch reveal with stable layout.
const SCRAMBLE_CHARS = "!<>-_\\/[]{}—=+*^?#0123456789";

function activeGlyph(ch) {
  if (ch === " ") return " ";
  return SCRAMBLE_CHARS[Math.floor(Math.random() * SCRAMBLE_CHARS.length)];
}

function charStates(full, elapsed, { delay, perCharDuration, charGap }) {
  const step = perCharDuration + charGap;
  let letterIndex = 0;
  return full.split("").map((ch) => {
    if (ch === " ") return { ch, done: true, display: " " };
    const start = delay + letterIndex * step;
    const end = start + perCharDuration;
    letterIndex += 1;
    if (elapsed >= end) return { ch, done: true, display: ch };
    if (elapsed < start) return { ch, done: false, display: "" };
    return { ch, done: false, display: activeGlyph(ch) };
  });
}

function segmentAccentAt(index, segments) {
  let offset = 0;
  for (let i = 0; i < segments.length; i++) {
    const len = segments[i].text.length;
    if (index < offset + len) return !!segments[i].accent;
    offset += len;
  }
  return false;
}

function groupStatesIntoWords(states, segments) {
  const words = [];
  let bucket = [];

  const flush = () => {
    if (bucket.length) {
      words.push({ type: "word", chars: bucket });
      bucket = [];
    }
  };

  states.forEach((st, i) => {
    if (st.ch === " ") {
      flush();
      words.push({ type: "space" });
    } else {
      bucket.push({ st, i, accent: segmentAccentAt(i, segments) });
    }
  });
  flush();
  return words;
}

function ScrambleGlyph({ display, accent }) {
  return (
    <span style={{ color: accent ? "var(--site-accent, var(--flame))" : "inherit" }}>
      {display}
    </span>
  );
}

function ScrambleWord({ chars }) {
  const wordText = chars.map((c) => c.st.ch).join("");
  return (
    <span style={{
      position: "relative",
      display: "inline-block",
      whiteSpace: "nowrap",
      verticalAlign: "top",
    }}>
      <span aria-hidden="true" style={{ visibility: "hidden" }}>{wordText}</span>
      <span style={{ position: "absolute", left: 0, top: 0, whiteSpace: "nowrap" }}>
        {chars.map(({ st, i, accent }) => (
          <ScrambleGlyph key={i} display={st.display} accent={accent} />
        ))}
      </span>
    </span>
  );
}

function useScrambleRaf({ full, delay, perCharDuration, charGap, waitForReveal, revealDelay }) {
  const [states, setStates] = React.useState(() =>
    charStates(full, 0, { delay, perCharDuration, charGap })
  );
  const rafRef = React.useRef();

  React.useEffect(() => {
    const opts = { delay, perCharDuration, charGap };
    const letterCount = full.split("").filter((ch) => ch !== " ").length;
    const total = delay + letterCount * (perCharDuration + charGap) + perCharDuration;
    let startTimer = null;
    let fallbackTimer = null;
    let started = false;

    const run = () => {
      const t0 = performance.now();
      const tick = (t) => {
        const elapsed = t - t0;
        setStates(charStates(full, elapsed, opts));
        if (elapsed < total) rafRef.current = requestAnimationFrame(tick);
      };
      rafRef.current = requestAnimationFrame(tick);
    };

    const onReveal = () => start();

    const start = () => {
      if (started) return;
      started = true;
      clearTimeout(fallbackTimer);
      window.removeEventListener("om-pt-revealed", onReveal);
      cancelAnimationFrame(rafRef.current);
      if (revealDelay) startTimer = setTimeout(run, revealDelay);
      else run();
    };

    if (!waitForReveal) {
      start();
    } else if (typeof window !== "undefined" && window.__omPtRevealed) {
      start();
    } else {
      window.addEventListener("om-pt-revealed", onReveal);
      fallbackTimer = setTimeout(start, 2600);
    }

    return () => {
      started = true;
      cancelAnimationFrame(rafRef.current);
      clearTimeout(startTimer);
      clearTimeout(fallbackTimer);
      window.removeEventListener("om-pt-revealed", onReveal);
    };
  }, [full, delay, perCharDuration, charGap, waitForReveal, revealDelay]);

  return states;
}

// ScrambleText — stable-width per-character sequential reveal.
function ScrambleText({
  segments,
  perCharDuration = 75,
  charGap = 28,
  delay = 0,
  waitForReveal = false,
  revealDelay = 0,
  as: Tag = "span",
  style,
}) {
  const full = segments.map((s) => s.text).join("");
  const states = useScrambleRaf({
    full,
    delay,
    perCharDuration,
    charGap,
    waitForReveal,
    revealDelay,
  });

  const words = groupStatesIntoWords(states, segments);

  return (
    <Tag style={style}>
      {words.map((word, wi) => {
        if (word.type === "space") return <span key={wi}> </span>;
        return <ScrambleWord key={wi} chars={word.chars} />;
      })}
    </Tag>
  );
}

// useScramble — lightweight hook (string output). Prefer ScrambleText when layout must not shift.
function useScramble(target, {
  duration = 700,
  delay = 0,
  trigger = true,
  perCharDuration,
  charGap = 28,
} = {}) {
  const perChar = perCharDuration || Math.max(40, Math.floor(duration / Math.max(target.length, 1)));
  const [out, setOut] = React.useState(target);
  const rafRef = React.useRef();

  React.useEffect(() => {
    if (!trigger) { setOut(target); return; }
    const opts = { delay, perCharDuration: perChar, charGap };
    const letterCount = target.split("").filter((ch) => ch !== " ").length;
    const total = delay + letterCount * (perChar + charGap) + perChar;

    const t0 = performance.now();
    const tick = (t) => {
      const elapsed = t - t0;
      const states = charStates(target, elapsed, opts);
      setOut(states.map((s) => s.display).join(""));
      if (elapsed < total) rafRef.current = requestAnimationFrame(tick);
      else setOut(target);
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [target, delay, perChar, charGap, trigger]);

  return out;
}

// ScrambleHeading — multi-segment heading; waits for page-transition reveal.
function ScrambleHeading({
  segments,
  duration = 1100,
  delay = 80,
  perCharDuration,
  charGap = 28,
}) {
  const perChar = perCharDuration || Math.max(45, Math.floor(duration / Math.max(
    segments.reduce((n, s) => n + s.text.length, 0),
    1
  )));

  return (
    <ScrambleText
      segments={segments}
      perCharDuration={perChar}
      charGap={charGap}
      delay={delay}
      waitForReveal
    />
  );
}

Object.assign(window, { useScramble, ScrambleHeading, ScrambleText });
