/* Primitives & shared components */
const { useState, useEffect, useRef, useMemo, useCallback, useLayoutEffect } = React;

// Inline thin-line icon set
const Icon = ({ name, size = 16, stroke = 1.25, className = "" }) => {
  const s = size;
  const sw = stroke;
  const props = {
    width: s, height: s, viewBox: "0 0 24 24", fill: "none",
    stroke: "currentColor", strokeWidth: sw, strokeLinecap: "round", strokeLinejoin: "round",
    className,
  };
  switch (name) {
    case "signal": return <svg {...props}><path d="M3 18c2-6 4-9 9-9s7 3 9 9"/><circle cx="12" cy="18" r="0.6" fill="currentColor"/></svg>;
    case "bell": return <svg {...props}><path d="M6 15V11a6 6 0 0 1 12 0v4l2 2H4l2-2Z"/><path d="M10 19a2 2 0 0 0 4 0"/></svg>;
    case "db": return <svg {...props}><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v6c0 1.7 3.6 3 8 3s8-1.3 8-3V6"/><path d="M4 12v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6"/></svg>;
    case "wrench": return <svg {...props}><path d="M14.7 6.3a4 4 0 0 0-5.3 5.3l-6 6 2 2 6-6a4 4 0 0 0 5.3-5.3L14 10l-2-2 2.7-1.7Z"/></svg>;
    case "book": return <svg {...props}><path d="M4 4h7a3 3 0 0 1 3 3v13a2 2 0 0 0-2-2H4Z"/><path d="M20 4h-7a3 3 0 0 0-3 3v13a2 2 0 0 1 2-2h8Z"/></svg>;
    case "shield": return <svg {...props}><path d="M12 3 4 6v6c0 4.5 3.2 8.4 8 9 4.8-.6 8-4.5 8-9V6l-8-3Z"/><path d="m9 12 2 2 4-4"/></svg>;
    case "chart": return <svg {...props}><path d="M4 20V6"/><path d="M4 20h16"/><path d="M8 16v-4M12 16v-7M16 16v-2"/></svg>;
    case "human": return <svg {...props}><circle cx="12" cy="7" r="3"/><path d="M5 21a7 7 0 0 1 14 0"/></svg>;
    case "machine": return <svg {...props}><rect x="3" y="9" width="18" height="11" rx="1"/><path d="M7 9V6h10v3"/><circle cx="9" cy="14" r="1.3"/><path d="M13 13h5M13 16h5"/></svg>;
    case "brain": return <svg {...props}><path d="M9 5a3 3 0 0 0-3 3 3 3 0 0 0-1 5 3 3 0 0 0 1 4 3 3 0 0 0 3 3V5Z"/><path d="M15 5a3 3 0 0 1 3 3 3 3 0 0 1 1 5 3 3 0 0 1-1 4 3 3 0 0 1-3 3V5Z"/><path d="M9 12h6"/></svg>;
    case "loop": return <svg {...props}><path d="M4 12a8 8 0 0 1 14-5"/><path d="M20 12a8 8 0 0 1-14 5"/><path d="M18 4v4h-4M6 20v-4h4"/></svg>;
    case "target": return <svg {...props}><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1.4" fill="currentColor"/></svg>;
    case "doc": return <svg {...props}><path d="M7 3h7l4 4v14H7Z"/><path d="M14 3v4h4"/><path d="M10 13h6M10 17h6"/></svg>;
    case "grid": return <svg {...props}><rect x="3" y="3" width="18" height="18" rx="1"/><path d="M3 9h18M3 15h18M9 3v18M15 3v18"/></svg>;
    case "check": return <svg {...props}><path d="m5 12 4 4 10-10"/></svg>;
    case "arrow": return <svg {...props}><path d="M5 12h14"/><path d="m13 6 6 6-6 6"/></svg>;
    case "plus": return <svg {...props}><path d="M12 5v14M5 12h14"/></svg>;
    case "minus": return <svg {...props}><path d="M5 12h14"/></svg>;
    case "filter": return <svg {...props}><path d="M4 5h16l-6 8v6l-4-2v-4Z"/></svg>;
    case "eye": return <svg {...props}><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z"/><circle cx="12" cy="12" r="2.5"/></svg>;
    case "alert": return <svg {...props}><path d="M12 3 2 21h20Z"/><path d="M12 10v5M12 18h.01"/></svg>;
    case "search": return <svg {...props}><circle cx="11" cy="11" r="7"/><path d="m20 20-4-4"/></svg>;
    case "dl": return <svg {...props}><path d="M12 3v12M7 10l5 5 5-5M4 21h16"/></svg>;
    case "play": return <svg {...props}><path d="M7 5v14l12-7Z" fill="currentColor"/></svg>;
    case "menu": return <svg {...props}><path d="M3 7h18M3 12h18M3 17h18"/></svg>;
    default: return null;
  }
};

// Reveal-on-scroll
const Reveal = ({ children, delay = 0, className = "", from = "up" }) => {
  const ref = useRef(null);
  const [seen, setSeen] = useState(false);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setSeen(true); io.disconnect(); }
    }, { threshold: 0.12 });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return (
    <div ref={ref} className={`reveal reveal-${from} ${seen ? "in" : ""} ${className}`} style={{ transitionDelay: `${delay}ms` }}>
      {children}
    </div>
  );
};

const Eyebrow = ({ children, num, className = "", noRule = false }) => (
  <div className={`eyebrow ${noRule ? "no-rule" : ""} ${className}`}>
    {num != null && <span className="mono" style={{ color: "var(--ink-muted)", letterSpacing: "0.16em" }}>{num.toString().padStart(2,"0")}</span>}
    <span>{children}</span>
  </div>
);

const SectionHead = ({ num, label, title, intro, right }) => (
  <div className="section-head">
    <div style={{ flex: 1 }}>
      <Eyebrow num={num}>{label}</Eyebrow>
      <h2 style={{ marginTop: 18 }}>{title}</h2>
    </div>
    {(intro || right) && (
      <div style={{ flex: "0 1 420px" }}>
        {intro && <p className="section-intro">{intro}</p>}
        {right}
      </div>
    )}
  </div>
);

const Placeholder = ({ label, h = 240, dark = false, children, style = {} }) => (
  <div className={`placeholder-img ${dark ? "dark" : ""}`} style={{ height: h, ...style }}>
    {children || <span className="crop">{label}</span>}
  </div>
);

const StripDivider = ({ items }) => (
  <div style={{ display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap", padding: "20px 0", borderTop: "1px solid var(--line-soft)", borderBottom: "1px solid var(--line-soft)" }}>
    {items.map((t, i) => (
      <React.Fragment key={i}>
        <span className="mono" style={{ fontSize: 11, letterSpacing: ".1em", textTransform: "uppercase", color: i === items.length - 1 ? "var(--green)" : "var(--ink)" }}>{t}</span>
        {i < items.length - 1 && <Icon name="arrow" size={14} className="" />}
      </React.Fragment>
    ))}
  </div>
);

// Scroll progress
const ScrollProgress = () => {
  const [w, setW] = useState(0);
  useEffect(() => {
    const onScroll = () => {
      const sh = document.documentElement.scrollHeight - window.innerHeight;
      const pct = sh > 0 ? (window.scrollY / sh) * 100 : 0;
      setW(pct);
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => { window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", onScroll); };
  }, []);
  return <div className="scroll-progress" style={{ width: `${w}%` }}></div>;
};

// Section rail — slim dot navigator pinned to the left edge.
// One dot per section; current one highlighted; tooltip on hover.
const SECTION_RAIL_MAP = [
  { id: "top",            label: "Practice"    },
  { id: "point-of-view",  label: "Through-line"},
  { id: "services",       label: "How I Work"  },
  { id: "experiments",    label: "Lab"         },
  { id: "about",          label: "About"       },
  { id: "essays",         label: "Writing"     },
  { id: "contact",        label: "Let's Talk"  },
];

const SectionRail = () => {
  const [idx, setIdx] = useState(0);
  useEffect(() => {
    const onScroll = () => {
      const y = window.scrollY + window.innerHeight * 0.4;
      let cur = 0;
      for (let i = 0; i < SECTION_RAIL_MAP.length; i++) {
        const el = document.getElementById(SECTION_RAIL_MAP[i].id);
        if (el && el.offsetTop <= y) cur = i;
      }
      setIdx(cur);
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    return () => { window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", onScroll); };
  }, []);
  const go = (id) => {
    const el = document.getElementById(id); if (!el) return;
    window.scrollTo({ top: el.offsetTop - 60, behavior: "smooth" });
  };
  return (
    <nav className="section-rail" aria-label="Section navigation">
      {SECTION_RAIL_MAP.map((s, i) => {
        const on = i === idx;
        return (
          <button
            key={s.id}
            type="button"
            className={`rail-dot ${on ? "is-on" : ""}`}
            onClick={() => go(s.id)}
            aria-label={`Jump to ${s.label}`}
            data-cursor={s.label}
          >
            <span className="rail-dot-mark"></span>
            <span className="rail-dot-label mono">{s.label}</span>
          </button>
        );
      })}
    </nav>
  );
};

// Magnetic wrapper
const Magnetic = ({ children, strength = 14 }) => {
  const ref = useRef(null);
  const onMove = (e) => {
    const el = ref.current; if (!el) return;
    const r = el.getBoundingClientRect();
    const x = e.clientX - (r.left + r.width / 2);
    const y = e.clientY - (r.top + r.height / 2);
    el.style.setProperty("--mx", `${Math.max(-strength, Math.min(strength, x * 0.25))}px`);
    el.style.setProperty("--my", `${Math.max(-strength, Math.min(strength, y * 0.25))}px`);
  };
  const onLeave = () => {
    const el = ref.current; if (!el) return;
    el.style.setProperty("--mx", `0px`);
    el.style.setProperty("--my", `0px`);
  };
  return (
    <span ref={ref} className="magnetic" onMouseMove={onMove} onMouseLeave={onLeave}>
      {children}
    </span>
  );
};

// Scramble — for stat numbers
const Scramble = ({ value, duration = 900, className = "" }) => {
  const ref = useRef(null);
  const [text, setText] = useState(String(value).replace(/[0-9]/g, "0"));
  const [seen, setSeen] = useState(false);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    const io = new IntersectionObserver(([e]) => {
      if (e.isIntersecting) { setSeen(true); io.disconnect(); }
    }, { threshold: 0.4 });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  useEffect(() => {
    if (!seen) return;
    const target = String(value);
    const start = performance.now();
    const glyphs = "0123456789";
    let raf;
    const step = (now) => {
      const t = Math.min(1, (now - start) / duration);
      const settle = Math.floor(target.length * t);
      let out = "";
      for (let i = 0; i < target.length; i++) {
        if (i < settle || /[^0-9]/.test(target[i])) out += target[i];
        else out += glyphs[Math.floor(Math.random() * 10)];
      }
      setText(out);
      if (t < 1) raf = requestAnimationFrame(step);
      else setText(target);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [seen, value, duration]);
  return <span ref={ref} className={`scramble ${className}`}>{text}</span>;
};

// Parallax
const useParallax = (containerRef, depth = 0.04) => {
  const ref = useRef(null);
  useEffect(() => {
    const c = containerRef.current, t = ref.current;
    if (!c || !t) return;
    const move = (e) => {
      const r = c.getBoundingClientRect();
      const x = (e.clientX - (r.left + r.width / 2)) * depth;
      const y = (e.clientY - (r.top + r.height / 2)) * depth;
      t.style.transform = `translate(${x}px, ${y}px)`;
    };
    const leave = () => { t.style.transform = "translate(0,0)"; };
    c.addEventListener("mousemove", move);
    c.addEventListener("mouseleave", leave);
    return () => { c.removeEventListener("mousemove", move); c.removeEventListener("mouseleave", leave); };
  }, [containerRef, depth]);
  return ref;
};

// =========================================================================
// PLAYFUL: Cursor companion — a soft copper dot that lags behind the cursor.
// Grows + reveals a label when hovering anything with [data-cursor].
const CursorCompanion = () => {
  const dotRef = useRef(null);
  const labelRef = useRef(null);
  const [active, setActive] = useState(false);
  const [label, setLabel] = useState("");
  const [variant, setVariant] = useState("");
  useEffect(() => {
    if (window.matchMedia("(hover: none)").matches) return;
    let x = window.innerWidth / 2, y = window.innerHeight / 2;
    let tx = x, ty = y;
    let raf;
    const move = (e) => { x = e.clientX; y = e.clientY; };
    const tick = () => {
      tx += (x - tx) * 0.18;
      ty += (y - ty) * 0.18;
      if (dotRef.current) dotRef.current.style.transform = `translate(${tx}px, ${ty}px)`;
      if (labelRef.current) labelRef.current.style.transform = `translate(${x + 18}px, ${y + 14}px)`;
      raf = requestAnimationFrame(tick);
    };
    const over = (e) => {
      const t = e.target.closest("[data-cursor]");
      if (t) { setActive(true); setLabel(t.dataset.cursor || ""); setVariant(t.dataset.cursorStyle || ""); }
      else { setActive(false); setLabel(""); setVariant(""); }
    };
    window.addEventListener("mousemove", move);
    window.addEventListener("mouseover", over);
    raf = requestAnimationFrame(tick);
    return () => { window.removeEventListener("mousemove", move); window.removeEventListener("mouseover", over); cancelAnimationFrame(raf); };
  }, []);
  return (
    <>
      <div ref={dotRef} className={`cursor-dot ${active ? "on" : ""} ${variant ? `is-${variant}` : ""}`} aria-hidden="true"></div>
      <div ref={labelRef} className={`cursor-label ${label ? "on" : ""} ${variant ? `is-${variant}` : ""}`} aria-hidden="true">{label}</div>
    </>
  );
};

// PLAYFUL: Konami-style easter egg — press "T" + "L" together (Trust Loop) to flip site to night mode.
// Or three fast clicks on the section-rail to do the same.
const useNightMode = () => {
  useEffect(() => {
    const root = document.documentElement;
    const toggle = () => root.classList.toggle("night");
    const onKey = (e) => {
      if ((e.key === "n" || e.key === "N") && (e.altKey || e.metaKey)) { toggle(); }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);
};

// PLAYFUL: Trust-meter — a tiny live "operator trust" gauge in the footer ribbon that ticks toward 84.
const TrustMeter = ({ target = 84, label = "operator trust · pilot result" }) => {
  const ref = useRef(null);
  const [v, setV] = useState(0);
  const [seen, setSeen] = useState(false);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    const io = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setSeen(true); io.disconnect(); } }, { threshold: 0.3 });
    io.observe(el); return () => io.disconnect();
  }, []);
  useEffect(() => {
    if (!seen) return;
    let cur = 0; let raf;
    const start = performance.now();
    const dur = 2400;
    const step = (now) => {
      const t = Math.min(1, (now - start) / dur);
      const eased = 1 - Math.pow(1 - t, 3);
      cur = Math.round(eased * target);
      setV(cur);
      if (t < 1) raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [seen, target]);
  const pct = (v / 100) * 100;
  return (
    <div ref={ref} className="trust-meter" data-cursor="live · self-reported">
      <div className="tm-track"><div className="tm-fill" style={{ width: `${pct}%` }}></div></div>
      <div className="tm-row">
        <span className="mono tm-label">{label}</span>
        <span className="tm-val tn">{v}<span className="tm-unit">/100</span></span>
      </div>
    </div>
  );
};

// PLAYFUL: Counter that "lives" — a small counter that softly ticks once per few seconds (e.g. "alarms suppressed today")
const LiveCounter = ({ start = 1247, jitter = 1, period = 2800, label = "Nuisance alarms suppressed today", color = "var(--copper)" }) => {
  const [n, setN] = useState(start);
  useEffect(() => {
    const id = setInterval(() => setN(v => v + Math.floor(Math.random() * jitter) + 1), period);
    return () => clearInterval(id);
  }, [jitter, period]);
  return (
    <div className="live-counter" data-cursor="ticking · placeholder">
      <span className="live-dot" style={{ background: color }}></span>
      <span className="tn" style={{ fontFamily: "var(--mono)", fontSize: 13 }}>{n.toLocaleString()}</span>
      <span className="mono live-label">{label}</span>
    </div>
  );
};

// PLAYFUL: Marquee — running ticker for case-study facts, hover to pause.
const Marquee = ({ items, speed = 80 }) => (
  <div className="marquee" data-cursor="hover to pause">
    <div className="marquee-track" style={{ animationDuration: `${speed}s` }}>
      {[...items, ...items].map((it, i) => (
        <span key={i} className="marquee-item">
          <span className="mono marquee-pip">●</span> {it}
        </span>
      ))}
    </div>
  </div>
);

// PLAYFUL: Tilt — small 3D tilt on hover (works for cards / artifacts)
const Tilt = ({ children, max = 6, className = "", style = {} }) => {
  const ref = useRef(null);
  const onMove = (e) => {
    const el = ref.current; if (!el) return;
    const r = el.getBoundingClientRect();
    const px = (e.clientX - r.left) / r.width;
    const py = (e.clientY - r.top) / r.height;
    const rx = (py - 0.5) * -max * 2;
    const ry = (px - 0.5) * max * 2;
    el.style.transform = `perspective(1000px) rotateX(${rx}deg) rotateY(${ry}deg) translateZ(0)`;
  };
  const onLeave = () => { if (ref.current) ref.current.style.transform = "perspective(1000px) rotateX(0) rotateY(0)"; };
  return <div ref={ref} className={`tilt ${className}`} style={style} onMouseMove={onMove} onMouseLeave={onLeave}>{children}</div>;
};

// Magazine-style full-bleed photo plate. Parallax on scroll.
const PlateDivider = ({ src, plate = "Plate 01", location = "", date = "", caption = "" }) => {
  const ref = useRef(null);
  const imgRef = useRef(null);
  useEffect(() => {
    const wrap = ref.current, img = imgRef.current;
    if (!wrap || !img) return;
    const onScroll = () => {
      const r = wrap.getBoundingClientRect();
      const vh = window.innerHeight;
      if (r.bottom < 0 || r.top > vh) return;
      const progress = 1 - ((r.top + r.height / 2) / (vh + r.height / 2));
      const y = (progress - 0.5) * 80;
      img.style.transform = `translate3d(0, ${y}px, 0) scale(1.12)`;
    };
    onScroll();
    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  return (
    <section ref={ref} className="plate-divider" aria-label={`${plate} · ${location}`}>
      <div className="plate-frame">
        <img ref={imgRef} src={src} alt="" className="plate-img"/>
        <div className="plate-vignette"></div>
        <div className="plate-grain"></div>
        <div className="plate-meta-top">
          <span className="mono plate-plate">{plate}</span>
          <span className="mono plate-loc">{location}</span>
        </div>
        <div className="plate-meta-bottom">
          <span className="mono plate-date">{date}</span>
          <span className="plate-caption">{caption}</span>
          <span className="mono plate-credit">Photo · field documentation</span>
        </div>
        <span className="plate-corner plate-tl"></span>
        <span className="plate-corner plate-tr"></span>
        <span className="plate-corner plate-bl"></span>
        <span className="plate-corner plate-br"></span>
      </div>
    </section>
  );
};

// PLAYFUL: Hover-split text — letters lift on hover (used in headlines)
// Accepts text via `text` prop OR children; recursively extracts strings to avoid editor-wrapper objects rendering as "[object Object]".
const extractText = (node) => {
  if (node == null || typeof node === "boolean") return "";
  if (typeof node === "string" || typeof node === "number") return String(node);
  if (Array.isArray(node)) return node.map(extractText).join("");
  if (React.isValidElement(node)) return extractText(node.props && node.props.children);
  return "";
};
const SplitText = ({ children, text, as = "span", className = "" }) => {
  const Comp = as;
  const str = text != null ? String(text) : extractText(children);
  return (
    <Comp className={`split ${className}`}>
      {str.split(/(\s+)/).map((word, wi) => {
        if (/^\s+$/.test(word)) return <span key={wi}>{word}</span>;
        return (
          <span key={wi} className="split-word">
            {[...word].map((ch, ci) => (
              <span key={ci} className="split-char" style={{ "--i": ci }}>{ch}</span>
            ))}
          </span>
        );
      })}
    </Comp>
  );
};

// PLAYFUL: Reveal a hidden phrase by dragging a slider — used to "uncover" the operator trust delta
const TrustReveal = () => {
  const [v, setV] = useState(50);
  return (
    <div className="trust-reveal">
      <div className="tr-stage">
        <div className="tr-before" style={{ clipPath: `inset(0 ${100 - v}% 0 0)` }}>
          <span className="mono">Before · Trust 62/100</span>
          <div className="tr-bars">
            {Array.from({ length: 14 }).map((_, i) => (
              <span key={i} style={{ height: `${20 + (i * 7) % 35}%`, background: "var(--signal-rose)" }}></span>
            ))}
          </div>
        </div>
        <div className="tr-after" style={{ clipPath: `inset(0 0 0 ${v}%)` }}>
          <span className="mono">After · Trust 84/100</span>
          <div className="tr-bars">
            {Array.from({ length: 14 }).map((_, i) => (
              <span key={i} style={{ height: `${45 + (i * 11) % 45}%`, background: "var(--green)" }}></span>
            ))}
          </div>
        </div>
        <div className="tr-handle" style={{ left: `${v}%` }}>
          <span className="tr-handle-bar"></span>
          <span className="tr-handle-dot">↔</span>
        </div>
      </div>
      <input type="range" min="0" max="100" value={v} onChange={(e) => setV(+e.target.value)} className="tr-range" aria-label="Drag to compare before and after"/>
      <div className="tr-caption mono">Drag → 4-week pilot · ACTECH BR · Bay 3</div>
    </div>
  );
};

Object.assign(window, { Icon, Reveal, Eyebrow, SectionHead, Placeholder, StripDivider, ScrollProgress, SectionRail, Magnetic, Scramble, useParallax, CursorCompanion, useNightMode, TrustMeter, LiveCounter, Marquee, Tilt, SplitText, TrustReveal, PlateDivider });
