// AmbientPlayer — Web Audio ambient synthesis inspired by each track's mood.
// NOT the real song — this is an original evocation generated live, labeled as "inspired by".

window.AmbientPlayer = (function() {
  function midiToFreq(m) { return 440 * Math.pow(2, (m - 69) / 12); }

  function createEngine() {
    const ctx = new (window.AudioContext || window.webkitAudioContext)();
    const master = ctx.createGain();
    master.gain.value = 0;
    const comp = ctx.createDynamicsCompressor();
    comp.threshold.value = -18; comp.ratio.value = 4; comp.attack.value = 0.01; comp.release.value = 0.25;
    master.connect(comp).connect(ctx.destination);

    // Reverb via convolver with synthesized impulse
    const conv = ctx.createConvolver();
    const irLen = 2.2 * ctx.sampleRate;
    const ir = ctx.createBuffer(2, irLen, ctx.sampleRate);
    for (let ch = 0; ch < 2; ch++) {
      const d = ir.getChannelData(ch);
      for (let i = 0; i < irLen; i++) {
        d[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / irLen, 3.2);
      }
    }
    conv.buffer = ir;
    const wet = ctx.createGain();
    wet.gain.value = 0.35;
    conv.connect(wet).connect(master);

    return { ctx, master, wet, conv };
  }

  // Style presets — voicing + rhythm + timbre choices.
  const STYLES = {
    dreamy: { osc: "triangle", padOsc: "sine", attack: 1.2, release: 2.5, vel: 0.35, noise: 0.04, filter: 1800 },
    jazzy:  { osc: "sine",     padOsc: "sine", attack: 0.3, release: 1.8, vel: 0.42, noise: 0.01, filter: 2400 },
    pulse:  { osc: "sawtooth", padOsc: "triangle", attack: 0.02, release: 0.4, vel: 0.25, noise: 0.02, filter: 1600 },
    warm:   { osc: "triangle", padOsc: "sine", attack: 0.6, release: 2.0, vel: 0.4, noise: 0.02, filter: 2000 },
    melancholic: { osc: "sine", padOsc: "triangle", attack: 1.0, release: 3.0, vel: 0.3, noise: 0.05, filter: 1400 },
  };

  function playNote(engine, midi, when, dur, style, gain = 1) {
    const { ctx, master, conv } = engine;
    const s = STYLES[style] || STYLES.warm;
    const freq = midiToFreq(midi);

    const osc = ctx.createOscillator();
    osc.type = s.osc;
    osc.frequency.value = freq;

    const osc2 = ctx.createOscillator();
    osc2.type = s.padOsc;
    osc2.frequency.value = freq * 1.005; // slight detune

    const filt = ctx.createBiquadFilter();
    filt.type = "lowpass";
    filt.frequency.value = s.filter;
    filt.Q.value = 0.7;

    const g = ctx.createGain();
    g.gain.setValueAtTime(0, when);
    g.gain.linearRampToValueAtTime(s.vel * gain, when + s.attack);
    g.gain.exponentialRampToValueAtTime(0.0001, when + dur + s.release);

    osc.connect(filt);
    osc2.connect(filt);
    filt.connect(g);
    g.connect(master);
    g.connect(conv); // also to reverb

    osc.start(when);
    osc2.start(when);
    osc.stop(when + dur + s.release + 0.1);
    osc2.stop(when + dur + s.release + 0.1);
  }

  function playBass(engine, midi, when, dur, style) {
    const { ctx, master } = engine;
    const osc = ctx.createOscillator();
    osc.type = "sine";
    osc.frequency.value = midiToFreq(midi - 12);
    const g = ctx.createGain();
    g.gain.setValueAtTime(0, when);
    g.gain.linearRampToValueAtTime(0.35, when + 0.04);
    g.gain.exponentialRampToValueAtTime(0.0001, when + dur);
    osc.connect(g).connect(master);
    osc.start(when);
    osc.stop(when + dur + 0.1);
  }

  function playHat(engine, when, style) {
    if (style !== "pulse" && style !== "warm") return;
    const { ctx, master } = engine;
    const buf = ctx.createBuffer(1, ctx.sampleRate * 0.08, ctx.sampleRate);
    const d = buf.getChannelData(0);
    for (let i = 0; i < d.length; i++) d[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / d.length, 4);
    const src = ctx.createBufferSource();
    src.buffer = buf;
    const hp = ctx.createBiquadFilter();
    hp.type = "highpass"; hp.frequency.value = 7000;
    const g = ctx.createGain();
    g.gain.value = 0.08;
    src.connect(hp).connect(g).connect(master);
    src.start(when);
  }

  function scheduleTrack(engine, mood, startTime, barCount = 8) {
    const { ctx } = engine;
    const beat = 60 / mood.bpm;
    const barLen = beat * 4;
    const chords = mood.chords;
    const style = mood.style;

    let t = startTime;
    for (let bar = 0; bar < barCount; bar++) {
      const chord = chords[bar % chords.length];
      // Pad: full chord held for the bar
      chord.forEach(iv => playNote(engine, mood.root + iv, t, barLen * 0.95, style, 0.7));
      // Bass: root on 1, fifth on 3
      playBass(engine, mood.root + chord[0], t, beat * 1.8, style);
      playBass(engine, mood.root + (chord[2] || chord[1] || 7), t + beat * 2, beat * 1.8, style);
      // Arp: sparse top notes
      if (style === "pulse") {
        for (let i = 0; i < 8; i++) {
          const n = chord[i % chord.length];
          playNote(engine, mood.root + n + 12, t + i * beat * 0.5, beat * 0.4, style, 0.4);
        }
      } else if (style === "dreamy" || style === "warm") {
        // lazy arp
        chord.forEach((n, i) => {
          playNote(engine, mood.root + n + 12, t + i * beat * 0.9, beat * 1.1, style, 0.45);
        });
      } else if (style === "jazzy") {
        // syncopated
        [0, 1.5, 2.25, 3].forEach((b, i) => {
          const n = chord[(i + 1) % chord.length];
          playNote(engine, mood.root + n + 12, t + b * beat, beat * 0.8, style, 0.4);
        });
      }
      // Hats
      if (style === "pulse") {
        for (let i = 0; i < 8; i++) playHat(engine, t + i * beat * 0.5, style);
      } else if (style === "warm") {
        for (let i = 0; i < 4; i++) playHat(engine, t + i * beat, style);
      }
      t += barLen;
    }
    return t; // next start time
  }

  const DEFAULT_MOOD = { root: 57, chords: [[0,4,7,11],[5,9,12,16],[7,11,14,17],[2,5,9,12]], bpm: 96, style: "warm" };

  function trackMode(track) {
    if (track && track.previewUrl) return "preview";
    if (track && track.mood) return "ambient";
    return "ambient-default";
  }

  function Player({ tracks, live }) {
    const [idx, setIdx] = React.useState(() => {
      if (live) return 0; // Always start at most-recent when Spotify is live
      const saved = parseInt(localStorage.getItem("jg_track") || "0", 10);
      return isNaN(saved) ? 0 : Math.max(0, Math.min(tracks.length - 1, saved));
    });
    const [playing, setPlaying] = React.useState(false);
    const [vol, setVol] = React.useState(() => {
      const v = parseFloat(localStorage.getItem("jg_vol"));
      return isNaN(v) ? 0.55 : v;
    });
    const [expanded, setExpanded] = React.useState(false);
    const engineRef = React.useRef(null);
    const nextTimeRef = React.useRef(0);
    const schedulerRef = React.useRef(null);
    const audioRef = React.useRef(null);
    const [pulse, setPulse] = React.useState(0);

    // Clamp idx if tracks change (Spotify list arrived after mount)
    React.useEffect(() => {
      if (idx >= tracks.length) setIdx(0);
    }, [tracks, idx]);

    const current = tracks[Math.min(idx, tracks.length - 1)] || tracks[0];
    const mode = trackMode(current);

    React.useEffect(() => { if (!live) localStorage.setItem("jg_track", String(idx)); }, [idx, live]);
    React.useEffect(() => { localStorage.setItem("jg_vol", String(vol)); }, [vol]);

    // Master volume (ambient engine)
    React.useEffect(() => {
      const e = engineRef.current;
      if (e) e.master.gain.linearRampToValueAtTime(playing ? vol : 0, e.ctx.currentTime + 0.3);
      const a = audioRef.current;
      if (a) a.volume = vol;
    }, [vol, playing]);

    // Pulse animation (beat indicator)
    React.useEffect(() => {
      if (!playing) return;
      const bpm = (current && current.mood && current.mood.bpm) || DEFAULT_MOOD.bpm;
      const beatMs = 60000 / bpm;
      const id = setInterval(() => setPulse(p => p + 1), beatMs);
      return () => clearInterval(id);
    }, [playing, current]);

    const stopAll = React.useCallback(() => {
      if (schedulerRef.current) { clearInterval(schedulerRef.current); schedulerRef.current = null; }
      const e = engineRef.current;
      if (e) {
        e.master.gain.cancelScheduledValues(e.ctx.currentTime);
        e.master.gain.linearRampToValueAtTime(0, e.ctx.currentTime + 0.3);
      }
      const a = audioRef.current;
      if (a) { a.pause(); a.src = ""; audioRef.current = null; }
    }, []);

    const startTrack = React.useCallback((track) => {
      // Spotify preview MP3 → play it directly
      if (track && track.previewUrl) {
        const a = new Audio(track.previewUrl);
        a.crossOrigin = "anonymous";
        a.volume = vol;
        a.play().catch(() => {});
        a.addEventListener("ended", () => {
          // Auto-advance if still playing
          window.dispatchEvent(new CustomEvent('jg-player-ended'));
        });
        audioRef.current = a;
        return;
      }
      // Otherwise synthesize ambient sketch
      if (!engineRef.current) engineRef.current = createEngine();
      const e = engineRef.current;
      if (e.ctx.state === "suspended") e.ctx.resume();
      e.master.gain.cancelScheduledValues(e.ctx.currentTime);
      e.master.gain.setValueAtTime(e.master.gain.value, e.ctx.currentTime);
      e.master.gain.linearRampToValueAtTime(vol, e.ctx.currentTime + 0.5);
      const mood = (track && track.mood) || DEFAULT_MOOD;
      nextTimeRef.current = e.ctx.currentTime + 0.1;
      nextTimeRef.current = scheduleTrack(e, mood, nextTimeRef.current, 4);
      if (schedulerRef.current) clearInterval(schedulerRef.current);
      schedulerRef.current = setInterval(() => {
        const e = engineRef.current;
        if (!e) return;
        const ahead = nextTimeRef.current - e.ctx.currentTime;
        if (ahead < 4) {
          nextTimeRef.current = scheduleTrack(e, mood, nextTimeRef.current, 2);
        }
      }, 1000);
    }, [vol]);

    const toggle = () => {
      if (playing) {
        stopAll();
        setPlaying(false);
      } else {
        startTrack(current);
        setPlaying(true);
      }
    };

    const switchTo = (newIdx) => {
      const wasPlaying = playing;
      stopAll();
      setIdx(newIdx);
      if (wasPlaying) {
        // small delay for fade
        setTimeout(() => startTrack(tracks[newIdx]), 320);
      }
    };

    const next = () => switchTo((idx + 1) % tracks.length);
    const prev = () => switchTo((idx - 1 + tracks.length) % tracks.length);

    React.useEffect(() => () => stopAll(), [stopAll]);

    // Listen for external play requests (from music cards)
    React.useEffect(() => {
      const handler = (e) => {
        const newIdx = e.detail.idx;
        if (newIdx === idx && playing) return;
        stopAll();
        setIdx(newIdx);
        setTimeout(() => { startTrack(tracks[newIdx]); setPlaying(true); }, 320);
      };
      window.addEventListener('jg-play-track', handler);
      return () => window.removeEventListener('jg-play-track', handler);
    }, [idx, playing, stopAll, startTrack, tracks]);

    // Auto-advance when a Spotify preview finishes
    React.useEffect(() => {
      const handler = () => {
        const n = (idx + 1) % tracks.length;
        stopAll();
        setIdx(n);
        setTimeout(() => { startTrack(tracks[n]); setPlaying(true); }, 250);
      };
      window.addEventListener('jg-player-ended', handler);
      return () => window.removeEventListener('jg-player-ended', handler);
    }, [idx, stopAll, startTrack, tracks]);

    return (
      <div style={{
        position: "fixed", right: 20, bottom: 20, zIndex: 2000,
        width: expanded ? 340 : 280,
        background: "var(--paper)", border: "1px solid var(--rule)",
        borderRadius: 12, boxShadow: "0 10px 30px rgba(0,0,0,0.12), 0 2px 8px rgba(0,0,0,0.06)",
        overflow: "hidden", transition: "width 0.25s ease",
        fontFamily: "inherit",
      }}>
        {/* Main row */}
        <div style={{ padding: "12px 14px", display: "flex", alignItems: "center", gap: 12 }}>
          <button onClick={toggle} aria-label={playing ? "Pause" : "Play"} style={{
            width: 38, height: 38, borderRadius: 99, border: 0, cursor: "pointer",
            background: playing ? "var(--accent)" : "var(--ink)", color: "#fff",
            display: "flex", alignItems: "center", justifyContent: "center",
            flexShrink: 0, transition: "transform 0.1s",
            transform: playing ? `scale(${1 + (pulse % 2) * 0.04})` : "scale(1)",
          }}>
            {playing ? (
              <svg width="12" height="12" viewBox="0 0 12 12"><rect x="2" y="1" width="3" height="10" fill="currentColor"/><rect x="7" y="1" width="3" height="10" fill="currentColor"/></svg>
            ) : (
              <svg width="12" height="12" viewBox="0 0 12 12"><path d="M2 1 L10 6 L2 11 Z" fill="currentColor"/></svg>
            )}
          </button>
          {live && current.image && (
            <img src={current.image} alt="" width={38} height={38}
              style={{ borderRadius: 4, flexShrink: 0, objectFit: "cover" }} />
          )}
          <div style={{ flex: 1, minWidth: 0 }}>
            <div className="serif" style={{ fontSize: 16, lineHeight: 1.15, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
              {current.title}
            </div>
            <div className="mono" style={{ fontSize: 10, color: "var(--mute)", letterSpacing: "0.06em", marginTop: 2, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
              {live ? current.artist : `inspired by · ${current.artist}`}
            </div>
          </div>
          <button onClick={() => setExpanded(x => !x)} aria-label="Toggle controls" style={{
            border: 0, background: "transparent", color: "var(--mute)", cursor: "pointer",
            padding: 4, fontSize: 10, letterSpacing: "0.1em",
          }}>
            {expanded ? "▾" : "▴"}
          </button>
        </div>

        {expanded && (
          <div style={{ padding: "0 14px 14px", borderTop: "1px solid var(--rule)", paddingTop: 12 }}>
            <div style={{ display: "flex", gap: 8, marginBottom: 10 }}>
              <button onClick={prev} style={btnStyle}>← prev</button>
              <button onClick={next} style={btnStyle}>next →</button>
            </div>
            <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 10 }}>
              <span className="mono" style={{ fontSize: 9, color: "var(--mute)", letterSpacing: "0.1em" }}>VOL</span>
              <input type="range" min="0" max="1" step="0.01" value={vol} onChange={e => setVol(parseFloat(e.target.value))}
                style={{ flex: 1, accentColor: "var(--accent)" }} />
            </div>
            <div className="mono" style={{ fontSize: 9.5, color: "var(--mute)", lineHeight: 1.5, letterSpacing: "0.03em" }}>
              {live && current.previewUrl
                ? `spotify preview · 30s · ${current.album || ""}`
                : live
                ? `no preview available · ambient sketch fallback`
                : `ambient sketch · original synthesis, not the record. ${(current.mood && current.mood.bpm) || 96} bpm · ${(current.mood && current.mood.style) || "warm"}.`}
            </div>
            {live && current.spotifyUrl && (
              <a href={current.spotifyUrl} target="_blank" rel="noopener"
                className="mono" style={{ display: "inline-block", marginTop: 8, fontSize: 9.5, color: "var(--accent)", textDecoration: "none", letterSpacing: "0.08em", textTransform: "uppercase", borderBottom: "1px solid var(--accent)" }}>
                ↗ open on spotify
              </a>
            )}
          </div>
        )}
      </div>
    );
  }

  const btnStyle = {
    flex: 1, padding: "6px 10px", fontSize: 11,
    border: "1px solid var(--rule)", borderRadius: 6,
    background: "var(--bg)", color: "var(--ink)", cursor: "pointer",
    fontFamily: "JetBrains Mono, ui-monospace, monospace",
    letterSpacing: "0.04em",
  };

  return { Player };
})();
