/* global React */
const { useState: useStateFx, useEffect: useEffectFx, useRef: useRefFx } = React;

/* ============================================================
   <ScrollProgress> — ultra-thin top bar that fills with scroll
============================================================ */
function ScrollProgress() {
  const [p, setP] = useStateFx(0);
  useEffectFx(() => {
    const onScroll = () => {
      const h = document.documentElement.scrollHeight - window.innerHeight;
      setP(h > 0 ? window.scrollY / h : 0);
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll, { passive: true });
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
    };
  }, []);
  return <div className="scroll-progress" style={{ transform: `scaleX(${p})` }} aria-hidden="true" />;
}

/* ============================================================
   <ScrollHUD> — (unused) fixed corner readout that "scans" the page.
   Shows current section, drifting fake coordinates, and a vertical
   progress glyph. Kept for reference; not rendered.
============================================================ */
function ScrollHUD() {
  const [data, setData] = useStateFx({
    pct: 0,
    lat: 10.520,
    lng: 7.440,
    section: "HERO",
    sectionNum: "00",
  });

  useEffectFx(() => {
    let raf = 0;
    const labels = () => Array.from(document.querySelectorAll("[data-screen-label]"));

    const compute = () => {
      const h = document.documentElement.scrollHeight - window.innerHeight;
      const pct = h > 0 ? Math.min(1, Math.max(0, window.scrollY / h)) : 0;

      // Interpolate between Kaduna (10.520°N, 7.440°E) and South Africa (-26.200°S, 28.050°E)
      const lat = 10.520 + (-26.200 - 10.520) * pct;
      const lng =  7.440 + ( 28.050 -  7.440) * pct;

      // Which section is closest to viewport mid?
      const mid = window.innerHeight * 0.4;
      let current = "HERO", num = "00";
      for (const el of labels()) {
        const r = el.getBoundingClientRect();
        if (r.top <= mid && r.bottom > mid) {
          const raw = el.getAttribute("data-screen-label") || "";
          // labels are "01 Hero" — split number / name
          const m = raw.match(/^(\d+)\s+(.+)$/);
          if (m) { num = m[1]; current = m[2].toUpperCase(); }
          else { current = raw.toUpperCase(); }
        }
      }

      setData({ pct, lat, lng, section: current, sectionNum: num });
    };

    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => { compute(); raf = 0; });
    };
    compute();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll, { passive: true });
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);

  const latLabel = data.lat >= 0 ? "N" : "S";
  const lngLabel = data.lng >= 0 ? "E" : "W";
  const pctStr = (data.pct * 100).toFixed(1).padStart(5, "0");

  return (
    <div className="scroll-hud" aria-hidden="true">
      <div className="scroll-hud-bracket scroll-hud-bracket--tl" />
      <div className="scroll-hud-bracket scroll-hud-bracket--tr" />
      <div className="scroll-hud-bracket scroll-hud-bracket--bl" />
      <div className="scroll-hud-bracket scroll-hud-bracket--br" />

      <div className="scroll-hud-row scroll-hud-row--head">
        <span className="scroll-hud-lbl">§ {data.sectionNum}</span>
        <span className="scroll-hud-val">{data.section}</span>
      </div>
      <div className="scroll-hud-row">
        <span className="scroll-hud-lbl">LAT</span>
        <span className="scroll-hud-val">{Math.abs(data.lat).toFixed(3)}°{latLabel}</span>
      </div>
      <div className="scroll-hud-row">
        <span className="scroll-hud-lbl">LNG</span>
        <span className="scroll-hud-val">{Math.abs(data.lng).toFixed(3)}°{lngLabel}</span>
      </div>
      <div className="scroll-hud-bar">
        <div className="scroll-hud-bar-fill" style={{ width: pctStr + "%" }} />
      </div>
      <div className="scroll-hud-row scroll-hud-row--pct">
        <span className="scroll-hud-lbl">DEPTH</span>
        <span className="scroll-hud-val">{pctStr}%</span>
      </div>
    </div>
  );
}

/* ============================================================
   useCountUp — animate a number from 0→target when in view,
   resets and replays when the element scrolls back out then in
============================================================ */
function useCountUp(target, opts = {}) {
  const ref = useRefFx(null);
  const [val, setVal] = useStateFx(0);
  const rafRef = useRefFx(null);

  useEffectFx(() => {
    const node = ref.current;
    if (!node) return;
    const dur = opts.dur || 1500;
    const io = new IntersectionObserver((ents) => {
      ents.forEach(e => {
        if (e.isIntersecting) {
          if (rafRef.current) cancelAnimationFrame(rafRef.current);
          const start = performance.now();
          const tick = (t) => {
            const p = Math.min(1, (t - start) / dur);
            const eased = 1 - Math.pow(1 - p, 3);
            setVal(target * eased);
            if (p < 1) rafRef.current = requestAnimationFrame(tick);
            else rafRef.current = null;
          };
          rafRef.current = requestAnimationFrame(tick);
        } else {
          if (rafRef.current) { cancelAnimationFrame(rafRef.current); rafRef.current = null; }
          setVal(0);
        }
      });
    }, { threshold: opts.threshold ?? 0.3 });
    io.observe(node);
    return () => { io.disconnect(); if (rafRef.current) cancelAnimationFrame(rafRef.current); };
  }, [target]);

  return [ref, val];
}

/* ============================================================
   <Counter> — wraps useCountUp with formatting
============================================================ */
function Counter({ to, format, suffix, prefix, decimals = 0 }) {
  const [ref, v] = useCountUp(to);
  const formatted = format ? format(v) : v.toLocaleString("en-US", {
    maximumFractionDigits: decimals,
    minimumFractionDigits: decimals,
  });
  return <span ref={ref}>{prefix}{formatted}{suffix}</span>;
}

/* ============================================================
   useWipeReveal — adds .in to .wipe / .reveal elements
   (replaces the simpler useReveal so headlines clip-wipe in)
============================================================ */
function useScrollReveals() {
  useEffectFx(() => {
    let io, stepIo;

    // Delay by one rAF so the browser paints the initial hidden state
    // (opacity:0 / clip-path) before the observer fires for already-visible
    // elements. Without this, desktop browsers (which render faster) can skip
    // the transition entirely — the observer fires in the same frame as mount.
    const raf = requestAnimationFrame(() => {
      io = new IntersectionObserver((entries) => {
        entries.forEach(e => {
          if (e.isIntersecting) {
            e.target.classList.add("in");
          } else {
            e.target.classList.remove("in");
          }
        });
      }, { threshold: 0.12, rootMargin: "0px 0px -8% 0px" });
      document.querySelectorAll(".reveal, .wipe, .wipe-stagger > *").forEach(el => io.observe(el));

      // process steps want a later trigger so the animation plays while they're
      // visible, not before the user reaches them
      stepIo = new IntersectionObserver((entries) => {
        entries.forEach(e => {
          if (e.isIntersecting) {
            e.target.classList.add("in");
          } else {
            e.target.classList.remove("in");
          }
        });
      }, { threshold: 0.25, rootMargin: "0px 0px -20% 0px" });
      document.querySelectorAll(".process-step").forEach(el => stepIo.observe(el));
    });

    return () => {
      cancelAnimationFrame(raf);
      if (io) io.disconnect();
      if (stepIo) stepIo.disconnect();
    };
  }, []);
}

window.ScrollProgress  = ScrollProgress;
window.ScrollHUD       = ScrollHUD;
window.Counter         = Counter;
window.useCountUp      = useCountUp;
window.useScrollReveals = useScrollReveals;

/* ============================================================
   <TypeReveal> — character-by-character timed reveal.
   Used for headlines that should "type in" instead of wipe.
============================================================ */
function TypeReveal({ text, speed = 28, startDelay = 0 }) {
  const parts = text.split(" ");
  let charIndex = 0;
  return (
    <span className="type-reveal">
      {parts.map((word, wi) => (
        <React.Fragment key={wi}>
          <span className="type-word">
            {word.split("").map((c, i) => {
              const delay = startDelay + charIndex * speed;
              charIndex++;
              return (
                <span
                  key={i}
                  className="type-char"
                  style={{ animationDelay: delay + "ms" }}
                >{c}</span>
              );
            })}
          </span>
          {wi < parts.length - 1 && (() => { charIndex++; return <span className="type-space">&nbsp;</span>; })()}
        </React.Fragment>
      ))}
    </span>
  );
}
/* ============================================================
   useProcessProgress — sets --process-progress on each .process-step
   individually, based on how far that step has scrolled into view.
   The CSS ::after underline reads this variable from its own ancestor.
============================================================ */
function useProcessProgress() {
  useEffectFx(() => {
    let raf = 0;
    const update = () => {
      const steps = document.querySelectorAll(".process-step");
      const vh = window.innerHeight;
      steps.forEach(step => {
        const r = step.getBoundingClientRect();
        // Start ramping once the step's top crosses 90% down the viewport;
        // reach 1 when it has traveled another 55% of the viewport height.
        const traveled = vh * 0.9 - r.top;
        const span = vh * 0.55;
        const p = Math.max(0, Math.min(1, traveled / span));
        step.style.setProperty("--process-progress", p.toFixed(3));
      });
    };
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => { update(); raf = 0; });
    };
    update();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll, { passive: true });
    return () => {
      window.removeEventListener("scroll", onScroll);
      window.removeEventListener("resize", onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);
}
window.useProcessProgress = useProcessProgress;
