/* eslint-disable */
/* ============================================================
   AsciiPortrait v3 — Kevin's ASCII face with face-reveal hover
   ------------------------------------------------------------
   v3 changes vs v2:
   - More violent explosion: higher force, larger radius, faster
     spring-back, slight angular jitter.
   - REAL HEADSHOT peeks through under the ASCII layer.
     The headshot is hidden by default (opacity 0). On pointer
     enter, a radial mask reveals it ONLY around the cursor —
     so as ASCII chars are pushed away, the photo is visible
     in the gap they leave.
   - Mouse-out: glyphs spring back, mask shrinks, photo fades.
   ------------------------------------------------------------
   Usage:
     <window.AsciiPortrait
       art={KEVIN_ASCII}
       photo="assets/headshot.jpg"
       fontSize={11}
       lineHeight={1.05}
       letterSpacing={0.05}
       force={140}            // v3: was 70
       radius={130}           // v3: was 90
       returnSpring={0.20}    // v3: snappier
       jitter={0.4}           // v3: angular noise on the push
       revealRadius={120}     // photo mask radius (px)
       label="ID · 0001 / Kevin Zicherman"
     />
   ============================================================ */

const { useEffect: _useEffectAP, useRef: _useRefAP, useMemo: _useMemoAP, useState: _useStateAP } = React;

const KEVIN_ASCII_DEFAULT = (typeof window !== "undefined" && window.KEVIN_ASCII) ? window.KEVIN_ASCII : "";

function AsciiPortrait({
  art = KEVIN_ASCII_DEFAULT,
  photo = "assets/headshot.jpg",
  fontSize = 11,
  lineHeight = 1.05,
  letterSpacing = 0.05,
  force = 140,           // v3 default — much more violent
  radius = 130,
  returnSpring = 0.20,
  damping = 0.74,
  jitter = 0.4,
  revealRadius = 120,
  glow = true,
  label = "ID · 0001 / Kevin Zicherman",
  className = "",
  style = {}
}) {
  const wrapRef = _useRefAP(null);
  const stageRef = _useRefAP(null);
  const photoRef = _useRefAP(null);
  const charsRef = _useRefAP([]);     // [{el, ox, oy, x, y, vx, vy, jx, jy}]
  const mouseRef = _useRefAP({ x: -9999, y: -9999, active: false, t: 0 });
  const rafRef = _useRefAP(0);
  const [stageDims, setStageDims] = _useStateAP({ w: 0, h: 0 });

  const lines = _useMemoAP(() => art.split("\n"), [art]);

  /* mount: build positioned spans */
  _useEffectAP(() => {
    const stage = stageRef.current;
    if (!stage) return;
    stage.innerHTML = "";
    const chars = [];

    /* measure actual char width with a probe */
    const probe = document.createElement("span");
    probe.textContent = "M";
    probe.style.cssText = `position:absolute;visibility:hidden;font:${fontSize}px var(--mono);letter-spacing:${letterSpacing}em;`;
    stage.appendChild(probe);
    const cw = probe.getBoundingClientRect().width || (fontSize * 0.6);
    stage.removeChild(probe);
    const ch = fontSize * lineHeight;

    let maxCols = 0;
    lines.forEach((line, row) => {
      maxCols = Math.max(maxCols, line.length);
      for (let col = 0; col < line.length; col++) {
        const c = line[col];
        if (c === " " || c === "\t") continue;
        const span = document.createElement("span");
        span.textContent = c;
        const ox = col * cw;
        const oy = row * ch;
        span.style.cssText =
          `position:absolute;left:0;top:0;` +
          `font:${fontSize}px var(--mono);` +
          `letter-spacing:${letterSpacing}em;` +
          `line-height:1;` +
          `color:var(--fg);` +
          `transform:translate3d(${ox}px,${oy}px,0);` +
          `will-change:transform,opacity;` +
          `pointer-events:none;` +
          `transition:color 240ms ease;` +
          `z-index:2;`;
        stage.appendChild(span);
        chars.push({
          el: span,
          ox, oy,
          x: ox, y: oy,
          vx: 0, vy: 0,
          /* per-char angular jitter seed for non-uniform explosion */
          jx: (Math.sin(row * 12.9898 + col * 78.233) * 43758.5453) % 1,
          jy: (Math.cos(row * 39.346 + col * 11.135) * 23421.6311) % 1,
        });
      }
    });

    const w = maxCols * cw;
    const h = lines.length * ch;
    stage.style.width = w + "px";
    stage.style.height = h + "px";
    setStageDims({ w, h });
    charsRef.current = chars;
  }, [lines, fontSize, lineHeight, letterSpacing]);

  /* pointer + RAF loop */
  _useEffectAP(() => {
    const wrap = wrapRef.current;
    const stage = stageRef.current;
    const photoEl = photoRef.current;
    if (!wrap || !stage) return;

    let revealAlpha = 0;     // 0..1, drives photo mask softness
    const REVEAL_IN = 0.12;
    const REVEAL_OUT = 0.45;  // snappy fade-out so no ghost glow lingers

    const onMove = (e) => {
      const rect = stage.getBoundingClientRect();
      mouseRef.current.x = e.clientX - rect.left;
      mouseRef.current.y = e.clientY - rect.top;
      mouseRef.current.active = true;
      const wrapRect = wrap.getBoundingClientRect();
      wrap.style.setProperty("--mx", (e.clientX - wrapRect.left) + "px");
      wrap.style.setProperty("--my", (e.clientY - wrapRect.top) + "px");
    };
    const onEnter = (e) => {
      onMove(e);
      // Tell the page to dim the global cursor spotlight while the portrait
      // owns the focus — prevents double-glow on the face and a lingering
      // amber blob right where the face is when you move out.
      document.body.setAttribute("data-portrait-hover", "1");
    };
    const onLeave = () => {
      mouseRef.current.active = false;
      mouseRef.current.x = -9999;
      mouseRef.current.y = -9999;
      // park the CSS pointer vars off-screen so any --mx/--my-driven
      // overlays don't leave a lingering glow over the face
      wrap.style.setProperty("--mx", "-9999px");
      wrap.style.setProperty("--my", "-9999px");
      document.body.removeAttribute("data-portrait-hover");
    };

    wrap.addEventListener("pointermove", onMove);
    wrap.addEventListener("pointerleave", onLeave);
    wrap.addEventListener("pointerenter", onEnter);

    const tick = () => {
      const chars = charsRef.current;
      const mx = mouseRef.current.x;
      const my = mouseRef.current.y;
      const active = mouseRef.current.active;
      const r2 = radius * radius;
      mouseRef.current.t += 1;
      const t = mouseRef.current.t;

      for (let i = 0; i < chars.length; i++) {
        const c = chars[i];
        let fx = 0, fy = 0;

        if (active) {
          const dx = c.x - mx;
          const dy = c.y - my;
          const d2 = dx * dx + dy * dy;
          if (d2 < r2 && d2 > 0.01) {
            const d = Math.sqrt(d2);
            const falloff = 1 - d / radius;
            /* v3: punchier curve (cubic falloff -> sharper near cursor) */
            const f = force * falloff * falloff * falloff;
            const nx = dx / d;
            const ny = dy / d;
            /* angular jitter: rotate the push vector by a small per-char angle
               that wobbles over time, so chars don't all fly along radial lines */
            const angle = (c.jx + Math.sin(t * 0.04 + c.jy * 6.28) * 0.3) * jitter;
            const cs = Math.cos(angle), sn = Math.sin(angle);
            const rx = nx * cs - ny * sn;
            const ry = nx * sn + ny * cs;
            fx += rx * f * 0.08;
            fy += ry * f * 0.08;
          }
        }

        /* spring back */
        const sx = (c.ox - c.x) * returnSpring;
        const sy = (c.oy - c.y) * returnSpring;

        c.vx = (c.vx + fx + sx) * damping;
        c.vy = (c.vy + fy + sy) * damping;
        c.x += c.vx;
        c.y += c.vy;

        c.el.style.transform = `translate3d(${c.x.toFixed(2)}px,${c.y.toFixed(2)}px,0)`;

        /* fade out chars that have wandered far (so the face peek isn't
           crowded by displaced chars hovering over the photo) */
        const dist = Math.hypot(c.x - c.ox, c.y - c.oy);
        const fade = Math.max(0, 1 - dist / 60);
        c.el.style.opacity = fade.toFixed(2);
      }

      /* photo reveal mask — eased in/out */
      if (photoEl) {
        const target = active ? 1 : 0;
        revealAlpha += (target - revealAlpha) * (target > revealAlpha ? REVEAL_IN : REVEAL_OUT);
        photoEl.style.opacity = revealAlpha.toFixed(3);
        const px = active ? mx : stageDims.w / 2;
        const py = active ? my : stageDims.h / 2;
        photoEl.style.webkitMaskImage =
          `radial-gradient(${revealRadius}px ${revealRadius}px at ${px}px ${py}px, ` +
          `rgba(0,0,0,1) 0%, rgba(0,0,0,0.85) 35%, rgba(0,0,0,0) 70%)`;
        photoEl.style.maskImage = photoEl.style.webkitMaskImage;
      }

      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);

    return () => {
      cancelAnimationFrame(rafRef.current);
      wrap.removeEventListener("pointermove", onMove);
      wrap.removeEventListener("pointerleave", onLeave);
      wrap.removeEventListener("pointerenter", onEnter);
    };
  }, [force, radius, returnSpring, damping, jitter, revealRadius, stageDims.w, stageDims.h]);

  return (
    <div
      ref={wrapRef}
      className={"ascii-portrait " + className}
      data-glow={glow ? "on" : "off"}
      style={{
        position: "relative",
        border: "1px solid var(--line-strong)",
        padding: 14,
        background: "var(--bg-1)",
        cursor: "crosshair",
        ...style
      }}>
      {/* hidden REAL photo, revealed by mask under cursor */}
      <div
        ref={photoRef}
        aria-hidden="true"
        style={{
          position: "absolute",
          left: 14, top: 14,
          width: stageDims.w || "calc(100% - 28px)",
          height: stageDims.h || "calc(100% - 28px)",
          backgroundImage: `url("${photo}")`,
          backgroundSize: "cover",
          backgroundPosition: "center 30%",
          opacity: 0,
          filter: "contrast(1.05) saturate(0.9) brightness(1.05)",
          pointerEvents: "none",
          zIndex: 1,
          transition: "filter 200ms ease"
        }}
      />
      <div ref={stageRef} className="ap-stage" style={{ position: "relative", zIndex: 2 }} />
      {/* corner brackets + REC label */}
      <span style={{ position: "absolute", top: 6, right: 12, fontSize: 9, color: "var(--red)", letterSpacing: "0.2em", animation: "blink 1.6s steps(2,start) infinite", zIndex: 4 }}>● REC</span>
      {label && (
        <div style={{ marginTop: 8, display: "flex", justifyContent: "space-between", fontSize: 9, color: "var(--fg-4)", letterSpacing: "0.15em", textTransform: "uppercase", position: "relative", zIndex: 4 }}>
          <span>{label.split("/")[0]?.trim()}</span>
          <span>{label.split("/")[1]?.trim() || ""}</span>
        </div>
      )}
    </div>
  );
}

window.AsciiPortrait = AsciiPortrait;
