import { useState, useEffect, useRef, useCallback } from "react";
import * as Tone from "tone";

// Pentatonic minor across octaves - nothing sounds wrong
const SCALE = ["C", "Eb", "F", "G", "Bb"];
const OCTAVES = [2, 3, 4, 5, 6];

function noteFromPosition(x, y, width, height) {
  const noteIdx = Math.floor((x / width) * SCALE.length);
  const octIdx = Math.floor((1 - y / height) * OCTAVES.length);
  const note = SCALE[Math.min(noteIdx, SCALE.length - 1)];
  const oct = OCTAVES[Math.min(octIdx, OCTAVES.length - 1)];
  return `${note}${oct}`;
}

function hueFromNote(note) {
  const noteMap = { C: 220, Eb: 260, F: 180, G: 300, Bb: 340 };
  const n = note.replace(/[0-9]/g, "");
  return noteMap[n] || 200;
}

export default function Resonance() {
  const canvasRef = useRef(null);
  const synthRef = useRef(null);
  const padRef = useRef(null);
  const reverbRef = useRef(null);
  const delayRef = useRef(null);
  const ripplesRef = useRef([]);
  const sequencesRef = useRef([]);
  const animRef = useRef(null);
  const startedRef = useRef(false);
  const mouseRef = useRef({ x: -1, y: -1, down: false });
  const [started, setStarted] = useState(false);
  const [rippleCount, setRippleCount] = useState(0);

  const initAudio = useCallback(async () => {
    if (startedRef.current) return;
    await Tone.start();
    startedRef.current = true;

    const reverb = new Tone.Reverb({ decay: 6, wet: 0.5 }).toDestination();
    const delay = new Tone.FeedbackDelay({
      delayTime: "8n.",
      feedback: 0.35,
      wet: 0.25,
    }).connect(reverb);

    const synth = new Tone.PolySynth(Tone.Synth, {
      maxPolyphony: 16,
      voice: Tone.Synth,
      options: {
        oscillator: { type: "triangle8" },
        envelope: { attack: 0.02, decay: 0.3, sustain: 0.1, release: 1.8 },
        volume: -10,
      },
    }).connect(delay);

    const pad = new Tone.Synth({
      oscillator: { type: "sine" },
      envelope: { attack: 0.8, decay: 0.5, sustain: 0.8, release: 2 },
      volume: -22,
    }).connect(reverb);

    synthRef.current = synth;
    padRef.current = pad;
    reverbRef.current = reverb;
    delayRef.current = delay;

    setStarted(true);
  }, []);

  // generate a short melodic phrase from a seed note
  const spawnSequence = useCallback((x, y, width, height) => {
    if (!synthRef.current) return;
    const baseNote = noteFromPosition(x, y, width, height);
    const baseIdx = SCALE.indexOf(baseNote.replace(/[0-9]/g, ""));
    const baseOct = parseInt(baseNote.replace(/[^0-9]/g, ""));
    const len = 3 + Math.floor(Math.random() * 5);
    const notes = [];

    for (let i = 0; i < len; i++) {
      const step = baseIdx + Math.floor(Math.random() * 5) - 2;
      let idx = ((step % SCALE.length) + SCALE.length) % SCALE.length;
      let oct = baseOct + (step < 0 ? -1 : step >= SCALE.length ? 1 : 0);
      oct = Math.max(2, Math.min(6, oct));
      notes.push(`${SCALE[idx]}${oct}`);
    }

    const durations = ["8n", "8n.", "4n", "16n", "8n"];
    let time = Tone.now();

    notes.forEach((note, i) => {
      const dur = durations[Math.floor(Math.random() * durations.length)];
      const vel = 0.3 + Math.random() * 0.4;
      synthRef.current.triggerAttackRelease(note, dur, time, vel);

      // visual ripple for each note
      const offsetX = (Math.random() - 0.5) * 60;
      const offsetY = (Math.random() - 0.5) * 60;
      ripplesRef.current.push({
        x: x + offsetX,
        y: y + offsetY,
        radius: 0,
        maxRadius: 60 + Math.random() * 120,
        alpha: 0.7,
        hue: hueFromNote(note),
        birth: Date.now() + i * 200,
        speed: 0.8 + Math.random() * 0.6,
      });

      time += Tone.Time(dur).toSeconds() * (0.8 + Math.random() * 0.4);
    });

    setRippleCount((c) => c + 1);
  }, []);

  // drone pad follows mouse
  const updatePad = useCallback(
    (x, y, width, height) => {
      if (!padRef.current || !started) return;
      const note = noteFromPosition(x, y, width, height);
      const freq = Tone.Frequency(note).toFrequency();
      padRef.current.frequency.rampTo(freq, 0.3);
    },
    [started]
  );

  // canvas animation loop
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    let w = (canvas.width = window.innerWidth);
    let h = (canvas.height = window.innerHeight);

    const handleResize = () => {
      w = canvas.width = window.innerWidth;
      h = canvas.height = window.innerHeight;
    };
    window.addEventListener("resize", handleResize);

    // ambient particles
    const dust = Array.from({ length: 80 }, () => ({
      x: Math.random() * w,
      y: Math.random() * h,
      vx: (Math.random() - 0.5) * 0.3,
      vy: (Math.random() - 0.5) * 0.3,
      size: Math.random() * 2 + 0.5,
      alpha: Math.random() * 0.15 + 0.03,
    }));

    const render = () => {
      ctx.fillStyle = "rgba(8, 8, 16, 0.15)";
      ctx.fillRect(0, 0, w, h);

      // dust
      dust.forEach((d) => {
        d.x += d.vx;
        d.y += d.vy;
        if (d.x < 0) d.x = w;
        if (d.x > w) d.x = 0;
        if (d.y < 0) d.y = h;
        if (d.y > h) d.y = 0;

        // react to mouse
        const mx = mouseRef.current.x;
        const my = mouseRef.current.y;
        if (mx > 0) {
          const dx = d.x - mx;
          const dy = d.y - my;
          const dist = Math.sqrt(dx * dx + dy * dy);
          if (dist < 150) {
            d.vx += (dx / dist) * 0.02;
            d.vy += (dy / dist) * 0.02;
          }
        }
        d.vx *= 0.99;
        d.vy *= 0.99;

        ctx.beginPath();
        ctx.arc(d.x, d.y, d.size, 0, Math.PI * 2);
        ctx.fillStyle = `rgba(140, 160, 220, ${d.alpha})`;
        ctx.fill();
      });

      // ripples
      const now = Date.now();
      ripplesRef.current = ripplesRef.current.filter((r) => r.alpha > 0.01);
      ripplesRef.current.forEach((r) => {
        if (now < r.birth) return;
        r.radius += r.speed;
        r.alpha *= 0.985;

        const gradient = ctx.createRadialGradient(
          r.x, r.y, r.radius * 0.3,
          r.x, r.y, r.radius
        );
        gradient.addColorStop(0, `hsla(${r.hue}, 60%, 60%, 0)`);
        gradient.addColorStop(0.6, `hsla(${r.hue}, 60%, 60%, ${r.alpha * 0.3})`);
        gradient.addColorStop(1, `hsla(${r.hue}, 60%, 60%, 0)`);

        ctx.beginPath();
        ctx.arc(r.x, r.y, r.radius, 0, Math.PI * 2);
        ctx.fillStyle = gradient;
        ctx.fill();

        // ring
        ctx.beginPath();
        ctx.arc(r.x, r.y, r.radius, 0, Math.PI * 2);
        ctx.strokeStyle = `hsla(${r.hue}, 50%, 70%, ${r.alpha * 0.5})`;
        ctx.lineWidth = 1;
        ctx.stroke();
      });

      // mouse glow
      if (mouseRef.current.x > 0 && started) {
        const mx = mouseRef.current.x;
        const my = mouseRef.current.y;
        const glow = ctx.createRadialGradient(mx, my, 0, mx, my, 80);
        glow.addColorStop(0, "rgba(97, 143, 236, 0.06)");
        glow.addColorStop(1, "rgba(97, 143, 236, 0)");
        ctx.fillStyle = glow;
        ctx.beginPath();
        ctx.arc(mx, my, 80, 0, Math.PI * 2);
        ctx.fill();
      }

      animRef.current = requestAnimationFrame(render);
    };

    render();

    return () => {
      cancelAnimationFrame(animRef.current);
      window.removeEventListener("resize", handleResize);
    };
  }, [started]);

  // mouse/touch handlers
  const handlePointerDown = useCallback(
    (e) => {
      const rect = canvasRef.current.getBoundingClientRect();
      const x = (e.clientX || e.touches?.[0]?.clientX) - rect.left;
      const y = (e.clientY || e.touches?.[0]?.clientY) - rect.top;

      if (!startedRef.current) {
        initAudio().then(() => {
          padRef.current?.triggerAttack("C3", Tone.now(), 0.15);
          spawnSequence(x, y, rect.width, rect.height);
        });
        return;
      }

      mouseRef.current.down = true;
      spawnSequence(x, y, rect.width, rect.height);
    },
    [initAudio, spawnSequence]
  );

  const handlePointerMove = useCallback(
    (e) => {
      const rect = canvasRef.current.getBoundingClientRect();
      const x = (e.clientX || e.touches?.[0]?.clientX) - rect.left;
      const y = (e.clientY || e.touches?.[0]?.clientY) - rect.top;
      mouseRef.current.x = x;
      mouseRef.current.y = y;
      updatePad(x, y, rect.width, rect.height);
    },
    [updatePad]
  );

  const handlePointerUp = useCallback(() => {
    mouseRef.current.down = false;
  }, []);

  const handlePointerLeave = useCallback(() => {
    mouseRef.current.x = -1;
    mouseRef.current.y = -1;
  }, []);

  return (
    <div
      style={{
        width: "100vw",
        height: "100vh",
        background: "#080810",
        position: "relative",
        overflow: "hidden",
        cursor: "crosshair",
        userSelect: "none",
        touchAction: "none",
      }}
      onMouseDown={handlePointerDown}
      onMouseMove={handlePointerMove}
      onMouseUp={handlePointerUp}
      onMouseLeave={handlePointerLeave}
      onTouchStart={handlePointerDown}
      onTouchMove={handlePointerMove}
      onTouchEnd={handlePointerUp}
    >
      <canvas
        ref={canvasRef}
        style={{
          position: "absolute",
          top: 0,
          left: 0,
          width: "100%",
          height: "100%",
        }}
      />

      {!started && (
        <div
          style={{
            position: "absolute",
            inset: 0,
            display: "flex",
            flexDirection: "column",
            alignItems: "center",
            justifyContent: "center",
            zIndex: 10,
            pointerEvents: "none",
          }}
        >
          <div
            style={{
              fontFamily: "'Georgia', serif",
              fontSize: 13,
              color: "rgba(255,255,255,0.3)",
              letterSpacing: "4px",
              textTransform: "uppercase",
              marginBottom: 8,
            }}
          >
            resonance
          </div>
          <div
            style={{
              fontFamily: "'Georgia', serif",
              fontSize: 11,
              color: "rgba(255,255,255,0.15)",
              letterSpacing: "1px",
            }}
          >
            click anywhere
          </div>
        </div>
      )}

      <div
        style={{
          position: "absolute",
          bottom: 16,
          right: 20,
          fontFamily: "'SF Mono', 'Consolas', monospace",
          fontSize: 10,
          color: "rgba(255,255,255,0.12)",
          letterSpacing: "0.5px",
        }}
      >
        {started ? `${ripplesRef.current.length} voices` : ""}
      </div>
    </div>
  );
}
