/* global React, Icon, IconBtn, db */
const { useState, useEffect, useRef, useCallback, useMemo } = React;

// ============================================================
// Ambient sounds — local, generated.
// No external audio files: each channel is synthesized live via
// the Web Audio API. Mix layers to taste; set a timer to fade
// the whole thing out after N minutes.
// ============================================================

const AMBIENT_CHANNELS = [
  { id: 'rain',   label: 'Rain',         icon: 'water_drop',     color: 'hsl(212, 70%, 60%)', kind: 'rain' },
  { id: 'ocean',  label: 'Ocean',        icon: 'waves',          color: 'hsl(192, 75%, 50%)', kind: 'ocean' },
  { id: 'forest', label: 'Forest',       icon: 'park',           color: 'hsl(108, 50%, 45%)', kind: 'forest' },
  { id: 'fire',   label: 'Fireplace',    icon: 'local_fire_department', color: 'hsl(20, 80%, 55%)', kind: 'fire' },
  { id: 'cafe',   label: 'Café',         icon: 'local_cafe',     color: 'hsl(28, 60%, 50%)', kind: 'cafe' },
  { id: 'pink',   label: 'Pink noise',   icon: 'graphic_eq',     color: 'hsl(330, 60%, 60%)', kind: 'pink' },
  { id: 'brown',  label: 'Brown noise',  icon: 'volume_up',      color: 'hsl(28, 40%, 45%)', kind: 'brown' },
];

const TIMER_OPTIONS = [
  { mins: 0,  label: 'Off' },
  { mins: 15, label: '15m' },
  { mins: 30, label: '30m' },
  { mins: 45, label: '45m' },
  { mins: 60, label: '1h' },
  { mins: 90, label: '90m' },
];

// ============================================================
// AmbientEngine — encapsulates the AudioContext + channel graph.
// One instance per tool mount; we wire it via a ref.
// ============================================================
class AmbientEngine {
  constructor() {
    this.ctx = null;
    this.master = null;
    this.channels = new Map(); // id → { gain, nodes: [], chirpTimer? }
    this.noiseBuffer = null;
    this.brownNoiseBuffer = null;
    this.pinkNoiseBuffer = null;
  }
  ensureContext() {
    if (this.ctx) return;
    this.ctx = new (window.AudioContext || window.webkitAudioContext)();
    this.master = this.ctx.createGain();
    this.master.gain.value = 0.6;
    this.master.connect(this.ctx.destination);
    // Pre-generate noise buffers (white, brown, pink)
    const sampleRate = this.ctx.sampleRate;
    const len = sampleRate * 4; // 4 seconds, looped
    // White
    this.noiseBuffer = this.ctx.createBuffer(1, len, sampleRate);
    const wd = this.noiseBuffer.getChannelData(0);
    for (let i = 0; i < len; i++) wd[i] = Math.random() * 2 - 1;
    // Brown
    this.brownNoiseBuffer = this.ctx.createBuffer(1, len, sampleRate);
    const bd = this.brownNoiseBuffer.getChannelData(0);
    let lastOut = 0;
    for (let i = 0; i < len; i++) {
      const white = Math.random() * 2 - 1;
      lastOut = (lastOut + 0.02 * white) / 1.02;
      bd[i] = lastOut * 3.5;
    }
    // Pink — Voss-McCartney
    this.pinkNoiseBuffer = this.ctx.createBuffer(1, len, sampleRate);
    const pd = this.pinkNoiseBuffer.getChannelData(0);
    let b0 = 0, b1 = 0, b2 = 0, b3 = 0, b4 = 0, b5 = 0, b6 = 0;
    for (let i = 0; i < len; i++) {
      const white = Math.random() * 2 - 1;
      b0 = 0.99886 * b0 + white * 0.0555179;
      b1 = 0.99332 * b1 + white * 0.0750759;
      b2 = 0.96900 * b2 + white * 0.1538520;
      b3 = 0.86650 * b3 + white * 0.3104856;
      b4 = 0.55000 * b4 + white * 0.5329522;
      b5 = -0.7616 * b5 - white * 0.0168980;
      pd[i] = (b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362) * 0.11;
      b6 = white * 0.115926;
    }
  }
  setMaster(v) {
    if (this.master) this.master.gain.setTargetAtTime(v, this.ctx.currentTime, 0.05);
  }
  start(id, kind, volume) {
    this.ensureContext();
    if (this.channels.has(id)) return; // already on
    const ch = { nodes: [], chirpTimer: null };
    const ctx = this.ctx;
    const gain = ctx.createGain();
    gain.gain.value = volume;
    gain.connect(this.master);
    ch.gain = gain;

    if (kind === 'rain' || kind === 'pink') {
      const src = ctx.createBufferSource();
      src.buffer = this.pinkNoiseBuffer;
      src.loop = true;
      const hp = ctx.createBiquadFilter();
      hp.type = 'highpass';
      hp.frequency.value = kind === 'rain' ? 800 : 200;
      const lp = ctx.createBiquadFilter();
      lp.type = 'lowpass';
      lp.frequency.value = kind === 'rain' ? 8000 : 12000;
      src.connect(hp).connect(lp).connect(gain);
      src.start();
      ch.nodes.push(src, hp, lp);
    } else if (kind === 'brown') {
      const src = ctx.createBufferSource();
      src.buffer = this.brownNoiseBuffer;
      src.loop = true;
      src.connect(gain);
      src.start();
      ch.nodes.push(src);
    } else if (kind === 'ocean') {
      // Brown noise with a slow LFO on a low-pass filter cutoff to simulate waves
      const src = ctx.createBufferSource();
      src.buffer = this.brownNoiseBuffer;
      src.loop = true;
      const lp = ctx.createBiquadFilter();
      lp.type = 'lowpass';
      lp.frequency.value = 600;
      const lfo = ctx.createOscillator();
      lfo.frequency.value = 0.10; // ~10 sec wave
      const lfoGain = ctx.createGain();
      lfoGain.gain.value = 400;
      lfo.connect(lfoGain).connect(lp.frequency);
      src.connect(lp).connect(gain);
      src.start();
      lfo.start();
      ch.nodes.push(src, lp, lfo, lfoGain);
    } else if (kind === 'forest') {
      // Mid-range filtered pink noise + occasional bird chirps
      const src = ctx.createBufferSource();
      src.buffer = this.pinkNoiseBuffer;
      src.loop = true;
      const lp = ctx.createBiquadFilter();
      lp.type = 'lowpass';
      lp.frequency.value = 3000;
      src.connect(lp).connect(gain);
      src.start();
      ch.nodes.push(src, lp);
      // Schedule chirps at random intervals
      const chirp = () => {
        const now = ctx.currentTime;
        const baseFreq = 1800 + Math.random() * 1500;
        const osc = ctx.createOscillator();
        const og = ctx.createGain();
        osc.frequency.setValueAtTime(baseFreq, now);
        osc.frequency.exponentialRampToValueAtTime(baseFreq * (Math.random() > 0.5 ? 1.4 : 0.7), now + 0.12);
        og.gain.setValueAtTime(0.0001, now);
        og.gain.exponentialRampToValueAtTime(0.06, now + 0.02);
        og.gain.exponentialRampToValueAtTime(0.0001, now + 0.14);
        osc.connect(og).connect(gain);
        osc.start(now);
        osc.stop(now + 0.16);
        ch.chirpTimer = setTimeout(chirp, 2000 + Math.random() * 6000);
      };
      ch.chirpTimer = setTimeout(chirp, 1000 + Math.random() * 3000);
    } else if (kind === 'fire') {
      // Brown noise + occasional crackle bursts
      const src = ctx.createBufferSource();
      src.buffer = this.brownNoiseBuffer;
      src.loop = true;
      const lp = ctx.createBiquadFilter();
      lp.type = 'lowpass';
      lp.frequency.value = 1200;
      src.connect(lp).connect(gain);
      src.start();
      ch.nodes.push(src, lp);
      const crackle = () => {
        const now = ctx.currentTime;
        const burstSrc = ctx.createBufferSource();
        burstSrc.buffer = this.noiseBuffer;
        const bg = ctx.createGain();
        bg.gain.setValueAtTime(0, now);
        bg.gain.linearRampToValueAtTime(0.18, now + 0.005);
        bg.gain.exponentialRampToValueAtTime(0.001, now + 0.05 + Math.random() * 0.04);
        const hp = ctx.createBiquadFilter();
        hp.type = 'highpass';
        hp.frequency.value = 2000;
        burstSrc.connect(hp).connect(bg).connect(gain);
        burstSrc.start(now);
        burstSrc.stop(now + 0.12);
        ch.chirpTimer = setTimeout(crackle, 80 + Math.random() * 600);
      };
      ch.chirpTimer = setTimeout(crackle, 300);
    } else if (kind === 'cafe') {
      // Brown noise + occasional clicks (mugs / chairs / chatter mumble)
      const src = ctx.createBufferSource();
      src.buffer = this.brownNoiseBuffer;
      src.loop = true;
      const lp = ctx.createBiquadFilter();
      lp.type = 'lowpass';
      lp.frequency.value = 900;
      src.connect(lp).connect(gain);
      src.start();
      ch.nodes.push(src, lp);
      const click = () => {
        const now = ctx.currentTime;
        const pop = ctx.createBufferSource();
        pop.buffer = this.noiseBuffer;
        const cg = ctx.createGain();
        cg.gain.setValueAtTime(0.001, now);
        cg.gain.exponentialRampToValueAtTime(0.10, now + 0.01);
        cg.gain.exponentialRampToValueAtTime(0.001, now + 0.05);
        const bp = ctx.createBiquadFilter();
        bp.type = 'bandpass';
        bp.frequency.value = 800 + Math.random() * 1800;
        bp.Q.value = 6;
        pop.connect(bp).connect(cg).connect(gain);
        pop.start(now);
        pop.stop(now + 0.08);
        ch.chirpTimer = setTimeout(click, 600 + Math.random() * 2000);
      };
      ch.chirpTimer = setTimeout(click, 500);
    }

    this.channels.set(id, ch);
  }
  setChannelVolume(id, v) {
    const ch = this.channels.get(id);
    if (ch?.gain) ch.gain.gain.setTargetAtTime(v, this.ctx.currentTime, 0.05);
  }
  stop(id) {
    const ch = this.channels.get(id);
    if (!ch) return;
    if (ch.chirpTimer) clearTimeout(ch.chirpTimer);
    for (const n of ch.nodes) {
      try { n.stop?.(); } catch {}
      try { n.disconnect(); } catch {}
    }
    try { ch.gain.disconnect(); } catch {}
    this.channels.delete(id);
  }
  stopAll() {
    for (const id of Array.from(this.channels.keys())) this.stop(id);
  }
  fadeOutAndStopAll(seconds = 4) {
    if (!this.master) return this.stopAll();
    const now = this.ctx.currentTime;
    const startVal = this.master.gain.value;
    this.master.gain.cancelScheduledValues(now);
    this.master.gain.setValueAtTime(startVal, now);
    this.master.gain.exponentialRampToValueAtTime(0.001, now + seconds);
    setTimeout(() => {
      this.stopAll();
      this.master.gain.value = startVal;
    }, seconds * 1000 + 50);
  }
}

// ============================================================
// AmbientTool
// ============================================================
function AmbientTool() {
  const engineRef = useRef(null);
  const [active, setActive] = useState({}); // id → volume (0–1)
  const [master, setMaster] = useState(0.6);
  const [timerMins, setTimerMins] = useState(0);
  const [timerEndsAt, setTimerEndsAt] = useState(null);
  const [now, setNow] = useState(Date.now());

  useEffect(() => {
    engineRef.current = new AmbientEngine();
    return () => engineRef.current?.stopAll();
  }, []);

  // Load saved prefs
  useEffect(() => {
    db.kv.get('ambientPrefs').then(rec => {
      if (rec?.v) {
        if (typeof rec.v.master === 'number') setMaster(rec.v.master);
        // Don't auto-start sounds on mount — Web Audio requires gesture.
      }
    });
  }, []);
  const persist = useCallback(() => {
    db.kv.put({ k: 'ambientPrefs', v: { master } });
  }, [master]);
  useEffect(() => { persist(); }, [master]);

  // Master volume application
  useEffect(() => {
    engineRef.current?.setMaster(master);
  }, [master]);

  // Auto-shutoff timer
  useEffect(() => {
    if (!timerEndsAt) return;
    const id = setInterval(() => {
      const left = timerEndsAt - Date.now();
      setNow(Date.now());
      if (left <= 4000 && left > 0 && Object.keys(active).length > 0) {
        // Begin the fade slightly before zero
        engineRef.current?.fadeOutAndStopAll(left / 1000);
      }
      if (left <= 0) {
        setActive({});
        setTimerEndsAt(null);
      }
    }, 250);
    return () => clearInterval(id);
  }, [timerEndsAt, active]);

  const toggle = (ch) => {
    const engine = engineRef.current;
    if (!engine) return;
    if (active[ch.id]) {
      engine.stop(ch.id);
      const { [ch.id]: _, ...rest } = active;
      setActive(rest);
    } else {
      const initialVol = 0.6;
      engine.start(ch.id, ch.kind, initialVol);
      setActive({ ...active, [ch.id]: initialVol });
    }
  };

  const setChannelVol = (id, v) => {
    engineRef.current?.setChannelVolume(id, v);
    setActive(prev => ({ ...prev, [id]: v }));
  };

  const stopAll = () => {
    engineRef.current?.stopAll();
    setActive({});
    setTimerEndsAt(null);
  };

  const setTimer = (mins) => {
    setTimerMins(mins);
    if (mins === 0) setTimerEndsAt(null);
    else setTimerEndsAt(Date.now() + mins * 60 * 1000);
  };

  const timeLeft = timerEndsAt ? Math.max(0, timerEndsAt - now) : 0;

  return (
    <div className="amb-tool">
      <header className="amb-head">
        <div>
          <h1>Ambient</h1>
          <p>Layer sounds. No files downloaded — everything is generated in your browser.</p>
        </div>
        <div className="amb-master">
          <Icon name="volume_up" />
          <input
            type="range" min="0" max="1" step="0.01"
            value={master}
            onChange={(e) => setMaster(parseFloat(e.target.value))}
          />
          <span>{Math.round(master * 100)}%</span>
        </div>
      </header>

      <div className="amb-grid">
        {AMBIENT_CHANNELS.map(ch => (
          <AmbientCard
            key={ch.id}
            channel={ch}
            on={!!active[ch.id]}
            volume={active[ch.id] ?? 0.6}
            onToggle={() => toggle(ch)}
            onVolume={(v) => setChannelVol(ch.id, v)}
          />
        ))}
      </div>

      <footer className="amb-foot">
        <div className="amb-timer">
          <Icon name="timer" />
          <span>Sleep timer:</span>
          {TIMER_OPTIONS.map(o => (
            <button
              key={o.mins}
              className={`amb-timer-btn ${timerMins === o.mins ? 'is-active' : ''}`}
              onClick={() => setTimer(o.mins)}
            >{o.label}</button>
          ))}
          {timerEndsAt && (
            <span className="amb-timer-left">
              {formatAmbTime(timeLeft)} left
            </span>
          )}
        </div>
        <button className="lh-btn ghost" onClick={stopAll} disabled={Object.keys(active).length === 0}>
          <Icon name="stop_circle" />Stop all
        </button>
      </footer>
    </div>
  );
}

function AmbientCard({ channel, on, volume, onToggle, onVolume }) {
  return (
    <div
      className={`amb-card ${on ? 'is-on' : ''}`}
      style={{ '--amb-color': channel.color }}
    >
      <button className="amb-card-main" onClick={onToggle}>
        <div className="amb-card-icon">
          <Icon name={channel.icon} />
        </div>
        <div className="amb-card-meta">
          <div className="amb-card-label">{channel.label}</div>
          <div className="amb-card-state">{on ? 'Playing' : 'Tap to play'}</div>
        </div>
        <div className={`amb-card-led ${on ? 'is-on' : ''}`} />
      </button>
      {on && (
        <input
          type="range" min="0" max="1" step="0.01"
          className="amb-card-volume"
          value={volume}
          onChange={(e) => onVolume(parseFloat(e.target.value))}
          onClick={e => e.stopPropagation()}
        />
      )}
    </div>
  );
}

function formatAmbTime(ms) {
  const total = Math.floor(ms / 1000);
  const m = Math.floor(total / 60), s = total % 60;
  return `${m}:${String(s).padStart(2, '0')}`;
}

window.AmbientTool = AmbientTool;
