/* Illuma — shared icons & primitives (global, no JSX export modules) */

// Brand "up-right" arrow (exact path from the UI kit)
function ArrowUR({ size = 12, color = "currentColor", className = "ill-arrow" }) {
  return (
    <svg className={className} width={size} height={size} viewBox="0 0 9.804 9.829" fill={color}
      style={{ display: "block", flex: "none" }} aria-hidden="true">
      <path d="M 8.194 9.635 L 8.194 2.963 L 1.327 9.829 L 0 8.502 L 6.893 1.61 L 0.193 1.61 L 1.804 0 L 9.804 0.013 L 9.804 8.026 L 8.194 9.635 Z" />
    </svg>
  );
}

function Chevron({ color = "currentColor", style }) {
  return (
    <svg className="ill-nav__chev" viewBox="0 0 11 6" fill="none" aria-hidden="true" style={style}>
      <path d="M1 1L5.5 5L10 1" stroke={color} strokeWidth="1.1" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

const ASSET = "assets";

function Label({ children, style }) {
  return <span className="ill-label" style={style}>{children}</span>;
}

function Button({ children, variant = "berry", arrow = true, as = "button", href, onClick, style, className }) {
  const cls = `ill-btn ill-btn--${variant}${className ? " " + className : ""}`;
  const inner = (<>{children}{arrow && <ArrowUR color="currentColor" />}</>);
  const buttonStyle = { ...style, width: "fit-content" };
  if (as === "a") return <a className={cls} href={href} onClick={onClick} style={buttonStyle}>{inner}</a>;
  return <button className={cls} onClick={onClick} style={buttonStyle}>{inner}</button>;
}

function TextLink({ children, href = "#", onClick }) {
  return <a className="ill-link" href={href} onClick={onClick}>{children}<ArrowUR /></a>;
}

// Count-up stat. Matches the Project Detail stats animation exactly: parses the
// first number out of the value (preserving any prefix/suffix like "MW+", "GW+",
// "+"), then animates it 0 → target with easeOutQuart over 1800ms when the stat
// first scrolls into view. Honours prefers-reduced-motion (shows final value).
function Stat({ value, label, light }) {
  const ref = React.useRef(null);
  const raw = String(value);
  const match = raw.match(/[\d.,]*\d/);

  const reduce = typeof window !== "undefined" &&
    window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;

  // No number to animate — render as-is.
  if (!match) {
    return (
      <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
        <span className="ill-stat" style={{ color: light ? "#fff" : "var(--color-black)" }}>{raw}</span>
        <span className="ill-small" style={{ opacity: 0.6, color: light ? "#fff" : "var(--color-black)" }}>{label}</span>
      </div>
    );
  }

  const numStr = match[0];
  const idx = match.index;
  const prefix = raw.slice(0, idx);
  const suffix = raw.slice(idx + numStr.length);
  const decimals = numStr.includes(".") ? numStr.split(".")[1].length : 0;
  const grouped = numStr.includes(",") || parseFloat(numStr.replace(/,/g, "")) >= 1000;
  const target = parseFloat(numStr.replace(/,/g, ""));

  const [val, setVal] = React.useState(reduce ? target : 0);

  React.useEffect(() => {
    const el = ref.current;
    if (!el || reduce) { setVal(target); return; }
    let raf, t0, started = false;
    const dur = 1800;
    const ease = (t) => 1 - Math.pow(1 - t, 4); // easeOutQuart — matches Project Detail
    const run = () => {
      const step = (ts) => {
        if (t0 == null) t0 = ts;
        const p = Math.min((ts - t0) / dur, 1);
        setVal(target * ease(p));
        if (p < 1) raf = requestAnimationFrame(step);
        else setVal(target);
      };
      raf = requestAnimationFrame(step);
    };
    if (!("IntersectionObserver" in window)) { run(); return; }
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting && !started) { started = true; run(); io.disconnect(); }
      });
    }, { threshold: 0.3 });
    io.observe(el);
    return () => { io.disconnect(); if (raf) cancelAnimationFrame(raf); };
  }, []);

  const fmt = (n) => {
    const fixed = decimals ? Number(n).toFixed(decimals) : String(Math.round(n));
    if (!grouped) return fixed;
    const parts = fixed.split(".");
    parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    return parts.join(".");
  };

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
      <span ref={ref} className="ill-stat" style={{ color: light ? "#fff" : "var(--color-black)" }}>{prefix}{fmt(val)}{suffix}</span>
      <span className="ill-small" style={{ opacity: 0.6, color: light ? "#fff" : "var(--color-black)" }}>{label}</span>
    </div>
  );
}

/* ---- Hero parallax hook ------------------------------------------------- */
// Attaches a scroll listener that moves the hero background image at a slower
// speed than the page, creating a subtle depth/parallax effect.
// • strength: total vertical travel of the image in px (default 70, boosted 25%)
// • scale(1.2) ensures no blank edges appear at the extremes of travel
// • Respects prefers-reduced-motion; disabled on mobile, halved on tablet
function useHeroParallax(strength) {
  var sectionRef = React.useRef(null);
  var imgRef    = React.useRef(null);

  React.useEffect(function () {
    // base travel, boosted ~25% so the parallax reads as intentional (was 70)
    var s = (typeof strength === "number" ? strength : 70) * 1.25;

    // Accessibility: honour reduced-motion preference
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;

    // Mobile: skip — parallax can cause cropping / jank at small sizes
    if (window.matchMedia("(max-width: 767px)").matches) return;

    // Tablet: keep the effect but halve the travel
    if (window.matchMedia("(max-width: 1279px)").matches) s = Math.round(s * 0.5);

    var section = sectionRef.current;
    var img     = imgRef.current;
    if (!section || !img) return;

    img.style.willChange = "transform";

    var raf = 0;

    function update() {
      var rect     = section.getBoundingClientRect();
      var vh       = window.innerHeight;
      // progress: 0 when section enters from bottom → 1 when it exits from top
      var progress = (vh - rect.top) / (vh + rect.height);
      var clamped  = Math.max(0, Math.min(1, progress));
      // ty: positive when section is low (image pushed down), negative when high
      var ty = (0.5 - clamped) * s * 2;
      img.style.transform = "translateY(" + ty.toFixed(2) + "px) scale(1.2)";
      raf = 0;
    }

    function onScroll() {
      if (!raf) raf = requestAnimationFrame(update);
    }

    window.addEventListener("scroll", onScroll, { passive: true });
    update(); // set initial position before first scroll

    return function () {
      window.removeEventListener("scroll", onScroll);
      if (raf) cancelAnimationFrame(raf);
      if (img) img.style.willChange = "";
    };
  }, []);

  return { sectionRef: sectionRef, imgRef: imgRef };
}

// ---- RevealH1: masked line-by-line hero heading reveal ----------------------
// The site-wide hero H1 animation (CSS in kit.css under ".h1-reveal"). The
// heading is split into VISUAL lines at runtime — words are rendered in a
// measurement pass and grouped by their rendered row — so responsive wrapping
// is always respected. Each visual line is wrapped in an overflow:hidden mask
// (.h1-mask) and the line text (.h1-inner) slides up from translateY(100%) → 0
// with a fade: 1.2s, cubic-bezier(0.16, 1, 0.3, 1). Line 1 starts at 0.35s;
// each following line starts 0.15s after the previous.
//   • `lines` — array of strings: forced line breaks (homepage headline)
//   • `text`  — single string: wraps naturally; reveal grouped per rendered line
// Re-splits on viewport width changes; once the reveal has played, rebuilt
// lines render directly in their final static state (no replay).
// prefers-reduced-motion is handled in kit.css (static heading, no animation).
function RevealH1({ text, lines, className = "ill-h1", style }) {
  const ref = React.useRef(null);
  const segs = lines || [text || ""];
  const segsKey = segs.join("\n");

  React.useLayoutEffect(() => {
    const el = ref.current;
    if (!el) return;

    function build() {
      // Measurement pass: plain inline word spans separated by real spaces
      // (wraps identically to raw text); forced segments are block-level.
      el.textContent = "";
      const words = [];
      segs.forEach((seg) => {
        const block = document.createElement("span");
        block.style.display = "block";
        String(seg).split(" ").filter(Boolean).forEach((w, i) => {
          if (i) block.appendChild(document.createTextNode(" "));
          const ws = document.createElement("span");
          ws.textContent = w;
          block.appendChild(ws);
          words.push(ws);
        });
        el.appendChild(block);
      });
      // Group words into visual lines by their rendered top offset.
      const lineTexts = [];
      let top = null;
      words.forEach((ws) => {
        const t = ws.offsetTop;
        if (top !== null && Math.abs(t - top) < 3) {
          lineTexts[lineTexts.length - 1] += " " + ws.textContent;
        } else {
          lineTexts.push(ws.textContent);
        }
        top = t;
      });
      // Final structure: one mask per visual line, inner slides up into place.
      el.textContent = "";
      lineTexts.forEach((lineText, i) => {
        const mask = document.createElement("span");
        mask.className = "h1-mask";
        const inner = document.createElement("span");
        inner.className = "h1-inner";
        inner.style.transitionDelay = (0.35 + i * 0.15).toFixed(2) + "s";
        inner.textContent = lineText;
        mask.appendChild(inner);
        el.appendChild(mask);
      });
    }

    build();
    // Double rAF: guarantee one painted frame in the hidden state so the
    // transition reliably runs from translateY(100%) on first load.
    let raf2 = 0;
    const raf1 = requestAnimationFrame(() => {
      raf2 = requestAnimationFrame(() => el.classList.add("is-in"));
    });

    // Re-split when the viewport WIDTH changes (ignore mobile URL-bar height
    // changes). Rebuilt lines render static because .is-in is already set.
    let timer = 0;
    let lastW = window.innerWidth;
    const onResize = () => {
      if (window.innerWidth === lastW) return;
      lastW = window.innerWidth;
      clearTimeout(timer);
      timer = setTimeout(build, 150);
    };
    window.addEventListener("resize", onResize);
    return () => {
      cancelAnimationFrame(raf1);
      cancelAnimationFrame(raf2);
      clearTimeout(timer);
      window.removeEventListener("resize", onResize);
    };
  }, [segsKey]);

  return (
    <h1 ref={ref} className={(className ? className + " " : "") + "h1-reveal"} style={style}>
      {segs.map((s, i) => <span key={i} style={{ display: "block" }}>{s}</span>)}
    </h1>
  );
}

Object.assign(window, { ArrowUR, Chevron, Label, Button, TextLink, Stat, ASSET, useHeroParallax, RevealH1 });
