/* ============================================================
   Cosmic Nisi — Atmosphere components
   Grain, moonlight, drifting nebula, ambient parallax stars,
   ambient audio toggle, scroll-driven sky color.
   ============================================================ */

const { useState, useEffect, useRef } = React;

/* ---------- Generate procedural grain & wavy rule once ---------- */
(function setupTextures() {
  // Grain via tiny SVG noise, base64
  const grain = `<svg xmlns='http://www.w3.org/2000/svg' width='220' height='220'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.95 0 0 0 0 0.92 0 0 0 0 0.84 0 0 0 0.85 0'/></filter><rect width='100%' height='100%' filter='url(%23n)' opacity='0.55'/></svg>`;
  // Hand-drawn wavy rule
  const wavy = `<svg xmlns='http://www.w3.org/2000/svg' width='120' height='6' viewBox='0 0 120 6'><path d='M 0 3 Q 10 1 20 3 T 40 3 T 60 3 T 80 3 T 100 3 T 120 3' fill='none' stroke='%23F26E9A' stroke-width='1' stroke-linecap='round'/></svg>`;
  document.documentElement.style.setProperty('--grain-image', `url("data:image/svg+xml;utf8,${encodeURIComponent(grain)}")`);
  document.documentElement.style.setProperty('--wavy-rule', `url("data:image/svg+xml;utf8,${encodeURIComponent(wavy)}")`);
})();

/* ---------- Cursor moonlight ---------- */
function Moonlight() {
  const ref = useRef(null);
  useEffect(() => {
    let raf, tx = window.innerWidth / 2, ty = window.innerHeight / 2;
    let cx = tx, cy = ty;
    const onMove = (e) => { tx = e.clientX; ty = e.clientY; };
    const tick = () => {
      cx += (tx - cx) * 0.12;
      cy += (ty - cy) * 0.12;
      if (ref.current) {
        ref.current.style.setProperty('--mx', cx + 'px');
        ref.current.style.setProperty('--my', cy + 'px');
      }
      raf = requestAnimationFrame(tick);
    };
    window.addEventListener('pointermove', onMove);
    tick();
    return () => { cancelAnimationFrame(raf); window.removeEventListener('pointermove', onMove); };
  }, []);
  return <div ref={ref} className="cn-moonlight" aria-hidden="true"/>;
}

/* ---------- Drifting nebula ---------- */
function Nebula() { return <div className="cn-nebula" aria-hidden="true"/>; }

/* ---------- Grain overlay ---------- */
function Grain() { return <div className="cn-grain" aria-hidden="true"/>; }

/* ---------- Ambient parallax starfield ---------- */
function Starfield({ density = 1, motion = 1 }) {
  const ref = useRef(null);
  useEffect(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    let stars = [], w = 0, h = 0, dpr = Math.min(window.devicePixelRatio || 1, 2);
    let mouseX = 0, mouseY = 0, scrollY = window.scrollY;
    let raf;

    const resize = () => {
      w = canvas.clientWidth; h = canvas.clientHeight;
      canvas.width = w * dpr; canvas.height = h * dpr;
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      const count = Math.floor((w * h) / 9000 * density);
      stars = Array.from({ length: count }, () => ({
        x: Math.random() * w,
        y: Math.random() * h * 1.4, // extra height so they don't all bunch on resize
        z: Math.random() * 3 + 0.4,           // depth (parallax)
        r: Math.random() * 1.1 + 0.3,         // radius
        a: Math.random() * 0.5 + 0.3,         // base alpha
        tw: Math.random() * Math.PI * 2,      // twinkle phase
        ts: 0.4 + Math.random() * 0.9,        // twinkle speed
        hue: Math.random() < 0.12 ? 'bronze' : 'white',
      }));
    };
    resize();
    window.addEventListener('resize', resize);

    const onMove = (e) => {
      mouseX = (e.clientX / window.innerWidth - 0.5) * 2;
      mouseY = (e.clientY / window.innerHeight - 0.5) * 2;
    };
    const onScroll = () => { scrollY = window.scrollY; };
    window.addEventListener('pointermove', onMove);
    window.addEventListener('scroll', onScroll, { passive: true });

    let t0 = performance.now();
    const draw = (t) => {
      const dt = (t - t0) / 1000; t0 = t;
      ctx.clearRect(0, 0, w, h);
      for (const s of stars) {
        s.tw += dt * s.ts;
        const flick = (Math.sin(s.tw) * 0.5 + 0.5) * 0.6 + 0.4;
        const px = (mouseX * s.z * 18 * motion);
        const py = (mouseY * s.z * 12 * motion) - (scrollY * 0.05 * (s.z / 3));
        const x = s.x + px;
        const y = ((s.y + py) % h + h) % h;
        const a = s.a * flick;
        const color = s.hue === 'bronze'
          ? `rgba(242, 110, 154, ${a})`
          : `rgba(251, 237, 235, ${a})`;
        ctx.beginPath();
        ctx.fillStyle = color;
        ctx.arc(x, y, s.r, 0, Math.PI * 2);
        ctx.fill();
        // subtle cross-glow on bigger stars
        if (s.r > 1) {
          ctx.globalAlpha = a * 0.25;
          ctx.fillRect(x - 4, y - 0.4, 8, 0.8);
          ctx.fillRect(x - 0.4, y - 4, 0.8, 8);
          ctx.globalAlpha = 1;
        }
      }
      raf = requestAnimationFrame(draw);
    };
    raf = requestAnimationFrame(draw);

    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('resize', resize);
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('scroll', onScroll);
    };
  }, [density, motion]);
  return <canvas ref={ref} className="cn-starfield" aria-hidden="true"/>;
}

/* ---------- Scroll-driven sky color ---------- */
/* Shifts the page-bg radial-glow color from midnight blue to dawn rose
   as the user scrolls through the document. Subtle. */
function SkyShift() {
  useEffect(() => {
    const palette = [
      // [scrollFraction, sky-glow color, sky-y position]
      [0.00, '#2B0F23', '-20%'],
      [0.25, '#20091A', '-10%'],
      [0.50, '#3A1426', '0%'],
      [0.75, '#2A0E20', '10%'],
      [1.00, '#3A1426', '15%'],
    ];
    let raf;
    const lerpHex = (a, b, t) => {
      const ah = a.replace('#',''), bh = b.replace('#','');
      const ar = parseInt(ah.slice(0,2),16), ag = parseInt(ah.slice(2,4),16), ab = parseInt(ah.slice(4,6),16);
      const br = parseInt(bh.slice(0,2),16), bg = parseInt(bh.slice(2,4),16), bb = parseInt(bh.slice(4,6),16);
      const r = Math.round(ar + (br - ar) * t);
      const g = Math.round(ag + (bg - ag) * t);
      const bl = Math.round(ab + (bb - ab) * t);
      return `rgb(${r},${g},${bl})`;
    };
    const onScroll = () => {
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => {
        const max = document.documentElement.scrollHeight - window.innerHeight;
        const f = Math.min(1, Math.max(0, max ? window.scrollY / max : 0));
        // find palette segment
        let i = 0;
        while (i < palette.length - 1 && f > palette[i+1][0]) i++;
        const [f0, c0, y0] = palette[i];
        const [f1, c1, y1] = palette[Math.min(i+1, palette.length-1)];
        const t = (f - f0) / Math.max(0.0001, (f1 - f0));
        const color = lerpHex(c0, c1, t);
        const y = parseFloat(y0) + (parseFloat(y1) - parseFloat(y0)) * t;
        document.documentElement.style.setProperty('--sky-glow', color);
        document.documentElement.style.setProperty('--sky-y', y + '%');
      });
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener('scroll', onScroll);
  }, []);
  return null;
}

/* ---------- Reveal on scroll ---------- */
function Reveal({ children, as: Tag = 'div', className = '', ...rest }) {
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current; if (!el) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((en) => { if (en.isIntersecting) el.classList.add('is-in'); });
    }, { threshold: 0.18 });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return <Tag ref={ref} className={`cn-reveal ${className}`} {...rest}>{children}</Tag>;
}

/* ---------- Audio toggle (web-audio drone, generated, no asset) ---------- */
function AudioToggle() {
  const [on, setOn] = useState(false);
  const [light, setLight] = useState(false);
  const ctxRef = useRef(null);

  useEffect(() => {
    const onScroll = () => setLight(window.scrollY > window.innerHeight * 0.85);
    window.addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener('scroll', onScroll);
  }, []);

  const toggle = async () => {
    if (!on) {
      try {
        const Ctx = window.AudioContext || window.webkitAudioContext;
        if (!Ctx) return;
        const ctx = new Ctx();
        const master = ctx.createGain();
        master.gain.value = 0;
        master.connect(ctx.destination);
        // Two slightly detuned sines + a low filtered noise for breath
        const o1 = ctx.createOscillator(); o1.frequency.value = 110; o1.type = 'sine';
        const o2 = ctx.createOscillator(); o2.frequency.value = 165.5; o2.type = 'sine';
        const o3 = ctx.createOscillator(); o3.frequency.value = 220.7; o3.type = 'sine';
        const g1 = ctx.createGain(); g1.gain.value = 0.10;
        const g2 = ctx.createGain(); g2.gain.value = 0.06;
        const g3 = ctx.createGain(); g3.gain.value = 0.04;
        // gentle LFO on g2
        const lfo = ctx.createOscillator(); lfo.frequency.value = 0.08;
        const lfoG = ctx.createGain(); lfoG.gain.value = 0.04;
        lfo.connect(lfoG); lfoG.connect(g2.gain);
        // low-pass filter
        const filt = ctx.createBiquadFilter();
        filt.type = 'lowpass'; filt.frequency.value = 600; filt.Q.value = 0.6;
        o1.connect(g1); o2.connect(g2); o3.connect(g3);
        g1.connect(filt); g2.connect(filt); g3.connect(filt);
        filt.connect(master);
        o1.start(); o2.start(); o3.start(); lfo.start();
        // fade in
        master.gain.linearRampToValueAtTime(0.06, ctx.currentTime + 1.4);
        ctxRef.current = { ctx, master };
        setOn(true);
      } catch (e) { /* ignore */ }
    } else {
      const r = ctxRef.current;
      if (r) {
        r.master.gain.cancelScheduledValues(r.ctx.currentTime);
        r.master.gain.linearRampToValueAtTime(0, r.ctx.currentTime + 0.6);
        setTimeout(() => { try { r.ctx.close(); } catch(e){} }, 700);
      }
      ctxRef.current = null;
      setOn(false);
    }
  };

  return (
    <button
      className={`cn-audio ${on ? 'is-on' : ''} ${light ? 'is-light' : ''}`}
      onClick={toggle}
      aria-label={on ? 'Ambient sound aus' : 'Ambient sound an'}
      title={on ? 'Ambient aus' : 'Ambient an'}
    >
      {on ? (
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
          <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" strokeLinejoin="round"/>
          <circle cx="17" cy="7" r="0.6" fill="currentColor"/>
          <circle cx="14" cy="4.5" r="0.4" fill="currentColor"/>
        </svg>
      ) : (
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
          <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" strokeLinejoin="round"/>
          <line x1="4" y1="20" x2="20" y2="4"/>
        </svg>
      )}
      <span className="cn-audio__pulse"/>
    </button>
  );
}

Object.assign(window, { Moonlight, Nebula, Grain, Starfield, SkyShift, Reveal, AudioToggle });
