// Kevin Ortiz — Portfolio · Primitives
// Mechanical, monochrome interaction components. No Framer Motion — plain React + rAF
// so nothing depends on a finicky UMD build. K.O. motion: fade + small translate,
// cubic-bezier(0.2,0.8,0.2,1), short durations. Marquee + magnet are scroll/mouse-driven.

const { useRef, useEffect, useState, useContext, createContext } = React;

// Shared tweak context so primitives can honor live toggles without prop-drilling.
const KevCtx = createContext({ reveal: true, magnet: true, headline: "white", corners: true, reelSpeed: 0.3 });

const KEV_EASE = "cubic-bezier(0.2,0.8,0.2,1)";

/* ---- Reveal: fade + translate when scrolled into view ---- */
function Reveal({ as = "div", children, delay = 0, y = 12, x = 0, duration = 0.6, className, style, once = true, ...rest }) {
  const ref = useRef(null);
  const [shown, setShown] = useState(false);
  const ctx = useContext(KevCtx);
  const animate = ctx.reveal !== false;

  useEffect(() => {
    if (!animate) { setShown(true); return; }
    const el = ref.current;
    if (!el) return;
    // Already in view at mount → reveal on the next frame so the fade plays.
    const r = el.getBoundingClientRect();
    if (r.top < window.innerHeight * 0.95 && r.bottom > 0) {
      const id = requestAnimationFrame(() => setShown(true));
      return () => cancelAnimationFrame(id);
    }
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting) { setShown(true); if (once) io.disconnect(); }
        else if (!once) setShown(false);
      });
    }, { rootMargin: "0px 0px -8% 0px", threshold: 0 });
    io.observe(el);
    return () => io.disconnect();
  }, [animate, once]);

  const Tag = as;
  return (
    <Tag
      ref={ref}
      className={className}
      style={{
        ...style,
        opacity: shown ? 1 : 0,
        transform: shown ? "none" : `translate(${x}px, ${y}px)`,
        transition: animate
          ? `opacity ${duration}s ${KEV_EASE} ${delay}s, transform ${duration}s ${KEV_EASE} ${delay}s`
          : "none",
        willChange: "opacity, transform",
      }}
      {...rest}
    >
      {children}
    </Tag>
  );
}

/* ---- Magnet: cursor-following translate within a padding radius ---- */
function Magnet({ children, padding = 120, strength = 3, className, style }) {
  const ref = useRef(null);
  const ctx = useContext(KevCtx);
  const disabled = ctx.magnet === false;
  const [pos, setPos] = useState({ x: 0, y: 0 });
  const [active, setActive] = useState(false);

  useEffect(() => {
    if (disabled) { setPos({ x: 0, y: 0 }); setActive(false); return; }
    let raf;
    const handle = (e) => {
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => {
        const el = ref.current;
        if (!el) return;
        const r = el.getBoundingClientRect();
        const cx = r.left + r.width / 2;
        const cy = r.top + r.height / 2;
        const dx = e.clientX - cx;
        const dy = e.clientY - cy;
        const within = Math.abs(dx) < r.width / 2 + padding && Math.abs(dy) < r.height / 2 + padding;
        if (within) { setActive(true); setPos({ x: dx / strength, y: dy / strength }); }
        else { setActive(false); setPos({ x: 0, y: 0 }); }
      });
    };
    window.addEventListener("mousemove", handle, { passive: true });
    return () => { window.removeEventListener("mousemove", handle); cancelAnimationFrame(raf); };
  }, [disabled, padding, strength]);

  return (
    <div
      ref={ref}
      className={className}
      style={{
        ...style,
        transform: `translate3d(${pos.x}px, ${pos.y}px, 0)`,
        transition: active ? "transform 0.3s ease-out" : "transform 0.6s ease-in-out",
        willChange: "transform",
      }}
    >
      {children}
    </div>
  );
}

/* ---- AnimatedText: char-by-char opacity sweep tied to scroll progress ---- */
function AnimatedText({ text, className, style }) {
  const containerRef = useRef(null);
  const spansRef = useRef([]);
  const ctx = useContext(KevCtx);
  const animate = ctx.reveal !== false;

  useEffect(() => {
    const chars = spansRef.current;
    if (!animate) { chars.forEach((s) => s && (s.style.opacity = 1)); return; }
    const el = containerRef.current;
    if (!el) return;
    let raf;
    const update = () => {
      const r = el.getBoundingClientRect();
      const vh = window.innerHeight;
      const denom = 0.6 * vh + r.height;            // offset ['start 0.8','end 0.2']
      let p = (0.8 * vh - r.top) / denom;
      p = Math.min(1, Math.max(0, p));
      const pn = p * chars.length;
      for (let i = 0; i < chars.length; i++) {
        const s = chars[i];
        if (!s) continue;
        s.style.opacity = (0.2 + 0.8 * Math.min(1, Math.max(0, pn - i))).toFixed(3);
      }
    };
    const onScroll = () => { cancelAnimationFrame(raf); raf = requestAnimationFrame(update); };
    window.addEventListener("scroll", onScroll, { passive: true });
    window.addEventListener("resize", onScroll);
    update();
    return () => { window.removeEventListener("scroll", onScroll); window.removeEventListener("resize", onScroll); cancelAnimationFrame(raf); };
  }, [animate, text]);

  spansRef.current = [];
  return (
    <p ref={containerRef} className={className} style={style}>
      {text.split("").map((ch, i) => (
        <span key={i} ref={(el) => (spansRef.current[i] = el)} style={{ opacity: 0.2, transition: "opacity 0.12s linear" }}>
          {ch === " " ? " " : ch}
        </span>
      ))}
    </p>
  );
}

/* ---- Buttons ---- */
function ContactButton({ label = "Contact me", className = "", style }) {
  return (
    <a
      href="#contact"
      className={`ko-btn ko-btn-primary ko-btn-pill ${className}`}
      style={{
        padding: "clamp(12px,1.4vw,16px) clamp(28px,3vw,44px)",
        fontSize: "clamp(12px,1vw,15px)",
        letterSpacing: "var(--ko-track-caps)",
        textTransform: "uppercase",
        fontWeight: 600,
        ...style,
      }}
    >
      {label}
    </a>
  );
}

function GhostButton({ label = "View project", href = "#", className = "", style }) {
  return (
    <a
      href={href}
      data-glow
      className={`ko-btn ko-btn-secondary ko-btn-pill ${className}`}
      style={{
        "--glow-radius": 999,
        padding: "10px 22px",
        fontSize: "clamp(11px,0.9vw,14px)",
        letterSpacing: "var(--ko-track-spec)",
        textTransform: "uppercase",
        fontWeight: 600,
        ...style,
      }}
    >
      {label}
    </a>
  );
}

/* ---- Corner ticks (blueprint frame marks) ---- */
function CornerTicks({ size = 12, inset = 0, show = true }) {
  if (!show) return null;
  const base = { position: "absolute", width: size, height: size, borderColor: "var(--ko-fg-white)", borderStyle: "solid", pointerEvents: "none" };
  return (
    <>
      <span style={{ ...base, top: inset, left: inset, borderWidth: "1px 0 0 1px" }} />
      <span style={{ ...base, top: inset, right: inset, borderWidth: "1px 1px 0 0" }} />
      <span style={{ ...base, bottom: inset, left: inset, borderWidth: "0 0 1px 1px" }} />
      <span style={{ ...base, bottom: inset, right: inset, borderWidth: "0 1px 1px 0" }} />
    </>
  );
}

/* ---- Spec annotation helper ---- */
function Spec({ children, style, className = "" }) {
  return <span className={`ko-spec ${className}`} style={style}>{children}</span>;
}

Object.assign(window, { KevCtx, KEV_EASE, Reveal, Magnet, AnimatedText, ContactButton, GhostButton, CornerTicks, Spec });
