// DECKS — two-channel preview mixer for the storefront (Serato-internal-mode vibe).
// Pure Web Audio, no deps. Varispeed pitch (±8% like a turntable — pitch+tempo together,
// deliberately NO time-stretch/keylock: real decks don't). Anything loaded must be
// same-origin or CORS-clean; Apple 30s previews go through /shop/preview-proxy.
//
// API: DECKS.load('a'|'b', url, title) — everything else is the UI.

(function () {
  let ac, master;
  const deck = {};                       // a/b → state
  const XW = 0.5;                        // crossfader pos 0..1 (0=A, 1=B)
  let xf = 0.5;

  function ctx() {
    if (ac) return ac;
    ac = new (window.AudioContext || window.webkitAudioContext)();
    master = ac.createDynamicsCompressor();          // safety limiter — two slammed decks won't clip
    master.threshold.value = -6; master.ratio.value = 12;
    master.connect(ac.destination);
    ['a', 'b'].forEach(s => {
      const chan = ac.createGain(), cross = ac.createGain();
      chan.gain.value = 1; cross.gain.value = 0.707;
      chan.connect(cross); cross.connect(master);
      deck[s] = { chan, cross, buf: null, src: null, playing: false,
                  offset: 0, startedAt: 0, rate: 1, title: '', peaks: null };
    });
    setXf(xf);
    return ac;
  }

  // equal-power crossfade
  function setXf(v) {
    xf = Math.max(0, Math.min(1, v));
    if (!ac) return;
    deck.a.cross.gain.value = Math.cos(xf * Math.PI / 2);
    deck.b.cross.gain.value = Math.sin(xf * Math.PI / 2);
  }

  function pos(d) {                      // current playhead seconds
    if (!d.buf) return 0;
    const p = d.playing ? d.offset + (ac.currentTime - d.startedAt) * d.rate : d.offset;
    return Math.max(0, Math.min(d.buf.duration, p));
  }

  function stopSrc(d) {
    if (d.src) { d.src.onended = null; try { d.src.stop(); } catch (e) {} d.src = null; }
  }

  function play(s) {
    const d = deck[s]; if (!d.buf) return;
    if (d.playing) {                     // pause
      d.offset = pos(d); stopSrc(d); d.playing = false; ui(s); return;
    }
    if (d.offset >= d.buf.duration - 0.05) d.offset = 0;
    const src = ac.createBufferSource();
    src.buffer = d.buf; src.playbackRate.value = d.rate;
    src.connect(d.chan);
    src.onended = () => { if (d.src === src) { d.playing = false; d.offset = 0; d.src = null; ui(s); } };
    src.start(0, d.offset);
    d.src = src; d.startedAt = ac.currentTime; d.playing = true; ui(s);
  }

  function cue(s) {                      // back to the top, stopped
    const d = deck[s]; stopSrc(d); d.playing = false; d.offset = 0; ui(s);
  }

  function seek(s, t) {
    const d = deck[s]; if (!d.buf || !isFinite(t)) return;
    d.offset = Math.max(0, Math.min(d.buf.duration, t));
    if (d.playing) { stopSrc(d); d.playing = false; play(s); } else ui(s);
  }

  function setRate(s, pct) {             // pct ∈ [-8, +8] → varispeed
    const d = deck[s];
    if (d.playing) { d.offset = pos(d); d.startedAt = ac.currentTime; }   // re-anchor playhead math
    d.rate = 1 + pct / 100;
    if (d.src) d.src.playbackRate.value = d.rate;
    el(`#dk-${s} .dk-pct`).textContent = (pct > 0 ? '+' : '') + pct.toFixed(1) + '%';
  }

  async function load(s, url, title) {
    ctx(); panel().classList.add('dk-open');
    const d = deck[s];
    stopSrc(d); d.playing = false; d.offset = 0; d.buf = null; d.title = title || url.split('/').pop();
    el(`#dk-${s} .dk-title`).textContent = 'loading…';
    try {
      const raw = await fetch(url).then(r => { if (!r.ok) throw 0; return r.arrayBuffer(); });
      d.buf = await ctx().decodeAudioData(raw);
      d.peaks = makePeaks(d.buf, 300);
      el(`#dk-${s} .dk-title`).textContent = d.title;
    } catch (e) {
      el(`#dk-${s} .dk-title`).textContent = 'load failed';
    }
    ui(s);
  }

  function makePeaks(buf, n) {
    const ch = buf.getChannelData(0), step = Math.floor(ch.length / n), out = new Float32Array(n);
    for (let i = 0; i < n; i++) {
      let m = 0;
      for (let j = i * step, e = j + step; j < e; j += 16) { const v = Math.abs(ch[j]); if (v > m) m = v; }
      out[i] = m;
    }
    return out;
  }

  // --- UI ---
  const el = q => panel().querySelector(q) || document.querySelector(q);
  let _panel;
  function panel() {
    if (_panel) return _panel;
    const css = document.createElement('style');
    css.textContent = `
      #dk{position:fixed;left:0;right:0;bottom:0;z-index:60;background:#101014;border-top:1px solid #2a2a33;
          color:#eee;font:13px system-ui;transform:translateY(calc(100% - 30px));transition:transform .25s}
      #dk.dk-open{transform:none}
      #dk .dk-tab{position:absolute;top:0;left:50%;transform:translate(-50%,-100%);background:#101014;color:#ff5db1;
          border:1px solid #2a2a33;border-bottom:0;border-radius:8px 8px 0 0;padding:4px 18px;cursor:pointer;font-weight:600}
      #dk .dk-row{display:flex;gap:14px;align-items:stretch;padding:12px 14px;max-width:980px;margin:0 auto}
      #dk .dk-deck{flex:1;min-width:0}
      #dk .dk-title{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#ff5db1;font-weight:600;margin-bottom:4px}
      #dk canvas{width:100%;height:54px;display:block;background:#08080a;border-radius:6px;cursor:pointer}
      #dk .dk-ctl{display:flex;gap:8px;align-items:center;margin-top:6px}
      #dk button{background:#22222b;border:1px solid #3a3a44;color:#eee;border-radius:6px;padding:4px 12px;cursor:pointer}
      #dk button.dk-on{background:#ff5db1;color:#1a0a14;border-color:#ff5db1}
      #dk input[type=range]{accent-color:#ff5db1}
      #dk .dk-pitch{flex:1} #dk .dk-pct{width:48px;text-align:right;color:#aaa;font-variant-numeric:tabular-nums}
      #dk .dk-mix{display:flex;flex-direction:column;justify-content:space-between;align-items:center;width:150px;gap:6px}
      #dk .dk-gains{display:flex;gap:18px} #dk .dk-gains label{display:flex;flex-direction:column;align-items:center;gap:2px;color:#888}
      #dk .dk-gains input{writing-mode:vertical-lr;direction:rtl;height:64px;width:22px}
      #dk .dk-xf{width:100%}
      @media(max-width:720px){#dk .dk-row{flex-wrap:wrap}#dk .dk-mix{width:100%;flex-direction:row}#dk .dk-gains input{writing-mode:horizontal-tb;height:auto;width:64px}}`;
    document.head.appendChild(css);
    _panel = document.createElement('div');
    _panel.id = 'dk';
    const deckHtml = s => `
      <div class="dk-deck" id="dk-${s}">
        <div class="dk-title">DECK ${s.toUpperCase()} — load a track</div>
        <canvas width="300" height="54"></canvas>
        <div class="dk-ctl">
          <button class="dk-play">▶</button><button class="dk-cue">CUE</button>
          <input type="range" class="dk-pitch" min="-8" max="8" step="0.1" value="0" title="pitch ±8%">
          <span class="dk-pct">0.0%</span>
        </div>
      </div>`;
    _panel.innerHTML = `<div class="dk-tab">🎛 DECKS</div><div class="dk-row">
      ${deckHtml('a')}
      <div class="dk-mix">
        <div class="dk-gains">
          <label><input type="range" class="dk-ga" min="0" max="1.25" step="0.01" value="1">A</label>
          <label><input type="range" class="dk-gb" min="0" max="1.25" step="0.01" value="1">B</label>
        </div>
        <input type="range" class="dk-xf" min="0" max="1" step="0.01" value="0.5" title="crossfader">
      </div>
      ${deckHtml('b')}
    </div>`;
    document.body.appendChild(_panel);
    _panel.querySelector('.dk-tab').onclick = () => _panel.classList.toggle('dk-open');
    _panel.querySelector('.dk-xf').oninput = e => setXf(+e.target.value);
    _panel.querySelector('.dk-ga').oninput = e => { if (ac) deck.a.chan.gain.value = +e.target.value; };
    _panel.querySelector('.dk-gb').oninput = e => { if (ac) deck.b.chan.gain.value = +e.target.value; };
    ['a', 'b'].forEach(s => {
      const root = _panel.querySelector(`#dk-${s}`);
      root.querySelector('.dk-play').onclick = () => play(s);
      root.querySelector('.dk-cue').onclick = () => cue(s);
      root.querySelector('.dk-pitch').oninput = e => setRate(s, +e.target.value);
      root.querySelector('canvas').onclick = e => {
        const d = deck[s]; if (!d || !d.buf) return;
        const r = e.target.getBoundingClientRect();
        seek(s, (e.clientX - r.left) / r.width * d.buf.duration);
      };
    });
    requestAnimationFrame(drawLoop);
    return _panel;
  }

  function ui(s) {
    const d = deck[s], root = _panel && _panel.querySelector(`#dk-${s}`);
    if (!root) return;
    root.querySelector('.dk-play').classList.toggle('dk-on', d.playing);
    root.querySelector('.dk-play').textContent = d.playing ? '❚❚' : '▶';
    draw(s);
  }

  function draw(s) {
    const d = deck[s], cv = _panel.querySelector(`#dk-${s} canvas`), g = cv.getContext('2d');
    g.clearRect(0, 0, cv.width, cv.height);
    if (!d || !d.peaks) return;
    const mid = cv.height / 2, played = d.buf ? pos(d) / d.buf.duration * d.peaks.length : 0;
    for (let i = 0; i < d.peaks.length; i++) {
      const h = Math.max(1, d.peaks[i] * (cv.height - 6));
      g.fillStyle = i < played ? '#ff5db1' : '#4a4a58';
      g.fillRect(i, mid - h / 2, 1, h);
    }
    g.fillStyle = '#fff'; g.fillRect(Math.min(cv.width - 1, played), 0, 1, cv.height);
  }

  function drawLoop() {
    if (deck.a && deck.a.playing) draw('a');
    if (deck.b && deck.b.playing) draw('b');
    requestAnimationFrame(drawLoop);
  }

  window.DECKS = { load, _state: () => ({ xf, a: deck.a, b: deck.b, ac }) };  // _state = test hook
})();
