/* ============================================================
   BRITO · NOESIS — reusable components (window globals)
   ============================================================ */
const { useState, useEffect, useRef, useCallback } = React;

const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const isTouch = window.matchMedia('(pointer: coarse)').matches;

/* ---------- The Observer Mark (faithful reproduction of the logo) ---------- */
function PhiMark({ className = "", stroke = "var(--gold)", w = 40 }) {
  const h = +(w * (166 / 120)).toFixed(2);
  return (
    <svg className={className} width={w} height={h} viewBox="0 0 120 166" fill="none" aria-hidden="true">
      {/* detached dot */}
      <circle cx="60" cy="13.5" r="9" fill={stroke} />
      {/* central bar — extends below the circle */}
      <line x1="60" y1="25" x2="60" y2="158" stroke={stroke} strokeWidth="10" strokeLinecap="butt" />
      {/* right arc — incomplete circle, open at top & bottom */}
      <path d="M74.21 34.25 A46 46 0 0 1 74.21 121.75" stroke={stroke} strokeWidth="12" fill="none" strokeLinecap="butt" />
      {/* left arc */}
      <path d="M45.79 34.25 A46 46 0 0 0 45.79 121.75" stroke={stroke} strokeWidth="12" fill="none" strokeLinecap="butt" />
    </svg>
  );
}

/* ---------- Corner frame wrapper ---------- */
function CornerFrame({ children, className = "" }) {
  return (
    <div className={"frame " + className}>
      <span className="c tl"></span><span className="c tr"></span>
      <span className="c bl"></span><span className="c br"></span>
      {children}
    </div>
  );
}

/* ---------- Section head ---------- */
function SectionHead({ eyebrow, title }) {
  return (
    <div className="sec-head reveal">
      <span className="eyebrow">{eyebrow}</span>
      <div className="rule"><span className="tick"></span>{title}</div>
    </div>
  );
}

/* ---------- Reveal on scroll (robust: IO + scroll fallback + safety net) ---------- */
function useRevealRoot() {
  useEffect(() => {
    if (prefersReduced) {
      document.querySelectorAll('.reveal').forEach(el => el.classList.add('in'));
      return;
    }
    // scroll/load fallback — reveals anything within (or above) the viewport.
    const revealNear = () => {
      const vh = window.innerHeight || document.documentElement.clientHeight;
      document.querySelectorAll('.reveal:not(.in)').forEach(el => {
        const r = el.getBoundingClientRect();
        if (r.top < vh - 8 && r.bottom > -60) el.classList.add('in');
      });
    };

    let io = null;
    try {
      io = new IntersectionObserver((entries) => {
        entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } });
      }, { threshold: 0.12, rootMargin: "0px 0px -8% 0px" });
      document.querySelectorAll('.reveal:not(.in)').forEach(el => io.observe(el));
    } catch (err) { io = null; }

    revealNear();
    window.addEventListener('scroll', revealNear, { passive: true });
    window.addEventListener('resize', revealNear, { passive: true });
    const t1 = setTimeout(revealNear, 300);
    // safety net: guarantee the first viewport-worth of content is visible.
    const t2 = setTimeout(() => {
      const vh = window.innerHeight || 800;
      document.querySelectorAll('.reveal:not(.in)').forEach(el => {
        if (el.getBoundingClientRect().top < vh * 1.15) el.classList.add('in');
      });
    }, 700);
    // last resort: if transitions aren't progressing (non-painting capture
    // context), an already-revealed element still computes ~0 opacity — snap
    // everything to its final state so content is never stuck invisible.
    const t3 = setTimeout(() => {
      const sample = document.querySelector('.reveal.in');
      if (sample && parseFloat(getComputedStyle(sample).opacity) < 0.05) {
        document.documentElement.classList.add('reveal-static');
        document.querySelectorAll('.reveal').forEach(el => el.classList.add('in'));
      }
    }, 1400);

    return () => {
      clearTimeout(t1); clearTimeout(t2);
      window.removeEventListener('scroll', revealNear);
      window.removeEventListener('resize', revealNear);
      if (io) io.disconnect();
    };
  }, []);
}

/* ---------- Custom cursor ---------- */
function CustomCursor() {
  const ring = useRef(null), dot = useRef(null);
  useEffect(() => {
    if (isTouch) return;
    document.body.classList.add('has-cursor');
    let rx = innerWidth / 2, ry = innerHeight / 2, dx = rx, dy = ry, tx = rx, ty = ry, raf;
    const move = (e) => { tx = e.clientX; ty = e.clientY; dx = tx; dy = ty;
      if (dot.current) dot.current.style.transform = `translate(${dx}px,${dy}px)`; };
    const hot = (e) => {
      const t = e.target.closest('a,button,.tile,.node,.pcard,.verb,.lang button,[data-hot]');
      if (ring.current) ring.current.classList.toggle('hot', !!t);
    };
    const loop = () => {
      rx += (tx - rx) * 0.16; ry += (ty - ry) * 0.16;
      if (ring.current) ring.current.style.transform = `translate(${rx}px,${ry}px)`;
      raf = requestAnimationFrame(loop);
    };
    window.addEventListener('mousemove', move, { passive: true });
    window.addEventListener('mouseover', hot, { passive: true });
    loop();
    return () => { cancelAnimationFrame(raf); window.removeEventListener('mousemove', move); window.removeEventListener('mouseover', hot); document.body.classList.remove('has-cursor'); };
  }, []);
  if (isTouch) return null;
  return (<><div ref={ring} className="cursor-ring"></div><div ref={dot} className="cursor-dot"></div></>);
}

/* ---------- Constellation background ---------- */
function Constellation() {
  const cv = useRef(null);
  useEffect(() => {
    if (prefersReduced) return;
    const canvas = cv.current, ctx = canvas.getContext('2d');
    let W, H, dpr, pts = [], mouse = { x: -999, y: -999 }, raf;
    const resize = () => {
      dpr = Math.min(devicePixelRatio || 1, 2);
      W = canvas.width = innerWidth * dpr; H = canvas.height = innerHeight * dpr;
      canvas.style.width = innerWidth + 'px'; canvas.style.height = innerHeight + 'px';
      const count = Math.min(120, Math.floor((innerWidth * innerHeight) / 16000));
      pts = Array.from({ length: count }, () => ({
        x: Math.random() * W, y: Math.random() * H,
        vx: (Math.random() - .5) * 0.12 * dpr, vy: (Math.random() - .5) * 0.12 * dpr,
        r: (Math.random() * 1.1 + 0.4) * dpr,
      }));
    };
    const onMove = (e) => { mouse.x = e.clientX * dpr; mouse.y = e.clientY * dpr; };
    const onLeave = () => { mouse.x = -9999; mouse.y = -9999; };
    const LINK = 130;
    const draw = () => {
      ctx.clearRect(0, 0, W, H);
      const link = LINK * dpr, link2 = link * link;
      for (const p of pts) {
        p.x += p.vx; p.y += p.vy;
        if (p.x < 0 || p.x > W) p.vx *= -1;
        if (p.y < 0 || p.y > H) p.vy *= -1;
        const mdx = p.x - mouse.x, mdy = p.y - mouse.y, md2 = mdx * mdx + mdy * mdy;
        if (md2 < (200 * dpr) * (200 * dpr)) {
          const f = (1 - Math.sqrt(md2) / (200 * dpr)) * 0.35;
          p.x += mdx / Math.sqrt(md2 + 1) * f; p.y += mdy / Math.sqrt(md2 + 1) * f;
        }
      }
      for (let i = 0; i < pts.length; i++) {
        const a = pts[i];
        for (let j = i + 1; j < pts.length; j++) {
          const b = pts[j], dx = a.x - b.x, dy = a.y - b.y, d2 = dx * dx + dy * dy;
          if (d2 < link2) {
            const o = (1 - d2 / link2) * 0.16;
            ctx.strokeStyle = `rgba(201,163,94,${o})`;
            ctx.lineWidth = 0.6 * dpr;
            ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke();
          }
        }
        const dmx = a.x - mouse.x, dmy = a.y - mouse.y, dm2 = dmx * dmx + dmy * dmy, ml = (190 * dpr) * (190 * dpr);
        if (dm2 < ml) {
          const o = (1 - dm2 / ml) * 0.4;
          ctx.strokeStyle = `rgba(216,184,120,${o})`;
          ctx.lineWidth = 0.6 * dpr;
          ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(mouse.x, mouse.y); ctx.stroke();
        }
      }
      for (const p of pts) {
        ctx.fillStyle = 'rgba(201,163,94,0.5)';
        ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, 7); ctx.fill();
      }
      raf = requestAnimationFrame(draw);
    };
    resize(); draw();
    window.addEventListener('resize', resize);
    window.addEventListener('mousemove', onMove, { passive: true });
    window.addEventListener('mouseout', onLeave);
    return () => { cancelAnimationFrame(raf); window.removeEventListener('resize', resize); window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseout', onLeave); };
  }, []);
  return <canvas id="constellation" ref={cv}></canvas>;
}

/* ---------- Orbital rings (SVG) for hero ---------- */
function OrbitRings() {
  return (
    <svg className="ring" viewBox="0 0 200 200" fill="none" aria-hidden="true" style={{ position: 'absolute', inset: 0 }}>
      <g className="ring spin" style={{ transformOrigin: '100px 100px' }}>
        <circle cx="100" cy="100" r="92" stroke="var(--line-soft)" strokeWidth="0.5" />
        <circle cx="100" cy="8" r="1.6" fill="var(--gold)" />
        <circle cx="100" cy="100" r="74" stroke="var(--line-faint)" strokeWidth="0.5" strokeDasharray="2 6" />
      </g>
      <g className="ring spin rev" style={{ transformOrigin: '100px 100px' }}>
        <circle cx="100" cy="100" r="82" stroke="var(--line-faint)" strokeWidth="0.5" />
        <circle cx="18" cy="100" r="1.3" fill="var(--gold-deep)" />
      </g>
    </svg>
  );
}

/* ---------- Glyphs (minimal line icons) ---------- */
function Glyph({ name }) {
  const s = { stroke: "var(--gold)", fill: "none", strokeWidth: 1.4, strokeLinecap: "round", strokeLinejoin: "round" };
  const map = {
    learning: <g {...s}><circle cx="12" cy="12" r="5.2" /><path d="M12 2.5v3M12 18.5v3M2.5 12h3M18.5 12h3M5.2 5.2l2 2M16.8 16.8l2 2M18.8 5.2l-2 2M5.2 18.8l2-2" /></g>,
    memory: <g {...s}><ellipse cx="12" cy="6" rx="7" ry="2.7" /><path d="M5 6v5c0 1.5 3.1 2.7 7 2.7s7-1.2 7-2.7V6" /><path d="M5 11v5c0 1.5 3.1 2.7 7 2.7s7-1.2 7-2.7v-5" /></g>,
    experiment: <g {...s}><path d="M9.5 3h5M10.5 3v6L5.5 18.5a1.5 1.5 0 0 0 1.4 2.3h10.2a1.5 1.5 0 0 0 1.4-2.3L13.5 9V3" /><path d="M8 14h8" /></g>,
    synthesis: <g {...s}><circle cx="9" cy="10" r="5" /><circle cx="15" cy="10" r="5" /><circle cx="12" cy="15.5" r="5" /></g>,
    reasoning: <g {...s}><circle cx="12" cy="12" r="2.3" /><circle cx="4" cy="6" r="1.7" /><circle cx="20" cy="6" r="1.7" /><circle cx="5" cy="19" r="1.7" /><circle cx="19" cy="19" r="1.7" /><path d="M10.3 10.6 5.4 7.2M13.7 10.6l4.9-3.4M10.6 13.6 6 17.8M13.4 13.6 18 17.8" /></g>,
    evolution: <g {...s}><path d="M12 12a3 3 0 1 1 3 3 5 5 0 1 1-5-5 7 7 0 1 1 7 7" /></g>,
    eye: <g {...s}><path d="M2.5 12S6 6 12 6s9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6Z" /><circle cx="12" cy="12" r="2.6" /></g>,
    adapt: <g {...s}><path d="M12 21V7M12 7 7 12M12 7l5 5" /><path d="M5 4h14" /><circle cx="12" cy="4" r="0" /></g>,
  };
  return <svg viewBox="0 0 24 24" aria-hidden="true">{map[name] || map.eye}</svg>;
}

/* ---------- NOESIS seal stamp ---------- */
function Seal() {
  return (
    <svg className="stamp" viewBox="0 0 100 100" fill="none" aria-hidden="true">
      <circle cx="50" cy="50" r="47" stroke="#6e5223" strokeWidth="1.2" />
      <circle cx="50" cy="50" r="40" stroke="#6e5223" strokeWidth="0.8" />
      <circle cx="50" cy="50" r="22" stroke="#6e5223" strokeWidth="0.8" />
      <circle cx="50" cy="30.5" r="2.5" fill="#3a2d18" />
      <line x1="50" y1="36" x2="50" y2="72" stroke="#3a2d18" strokeWidth="2.6" strokeLinecap="butt" />
      <path d="M53.86 39.11 A12.5 12.5 0 0 1 53.86 62.89" stroke="#3a2d18" strokeWidth="3" fill="none" strokeLinecap="butt" />
      <path d="M46.14 39.11 A12.5 12.5 0 0 0 46.14 62.89" stroke="#3a2d18" strokeWidth="3" fill="none" strokeLinecap="butt" />
      <path id="sealtop" d="M50 12a38 38 0 0 1 0 76" fill="none" />
      <text fill="#6e5223" fontSize="6.4" letterSpacing="2.4" fontFamily="'JetBrains Mono', monospace">
        <textPath href="#sealtop" startOffset="2%">NOESIS LAB · OBSERVATIO · SYNTHESIS ·</textPath>
      </text>
    </svg>
  );
}

Object.assign(window, { PhiMark, CornerFrame, SectionHead, useRevealRoot, CustomCursor, Constellation, OrbitRings, Glyph, Seal, prefersReduced, isTouch });
