// =============================================================================
// MIXER-EXTRAS — pieces that live in the Mixer column
// -----------------------------------------------------------------------------
// Three small components, each addressing a piece of beat-matching UX feedback
// that pro DJ apps make obvious and we previously hid:
//
//   1. <BeatClock side bpm firstBeat trackId>
//      Four LEDs above each volume fader. The current beat (1/2/3/4) lights up
//      brightly with deck-color accent; the others are dim. When two decks are
//      synced the LEDs blink in lockstep — *that visual lock is what tells the
//      DJ they nailed it*. you.dj has this; rekordbox has this. We didn't.
//
//   2. <CenterBpm bpm matched syncOk pulse>
//      The central BPM number, large and centered above the faders. It pulses
//      on every downbeat of the master deck. Border turns green when both
//      decks share a tempo within ~0.5 BPM. This is the "I'm beat-matched"
//      glance.
//
//   3. <DualWaveform overviewA overviewB progressA progressB ...>
//      The classic two-deck stacked wave: A on top, B mirrored below. Scaled
//      to a shared timeline anchored at each deck's playhead, so when the
//      tracks are beat-locked you can SEE the kick blobs land on top of each
//      other. This is the bird's-eye-view DJ tool — adjusts with sync.
// =============================================================================

const { useEffect: useFx, useRef: useR, useMemo: useMM, useState: useS } = React;

// -----------------------------------------------------------------------------
// 4-LED BEAT CLOCK
// -----------------------------------------------------------------------------
// Driven by AudioEngine.getBarPhase() — sampled via RAF. We don't need to be
// frame-perfect: the LED is "on" while the bar phase is within ±0.15 beats of
// that LED's slot, with a soft falloff. This gives a visible "thump" on each
// beat without snapping rectangularly.
function BeatClock({ side, bpm, firstBeat, deckColor, label, isMaster, synced }) {
  const [bar, setBar] = useS(0); // 0..4
  const ref = useR(null);

  useFx(() => {
    if (!bpm) { setBar(0); return; }
    let raf = 0;
    let mounted = true;
    const tick = () => {
      if (!mounted) return;
      try {
        const b = window.AudioEngine?.getBarPhase?.(side, firstBeat || 0, bpm) || 0;
        setBar(b);
      } catch {}
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => { mounted = false; cancelAnimationFrame(raf); };
  }, [side, bpm, firstBeat]);

  // intensity for slot i (0..3)
  const intensity = (i) => {
    // Distance in beats — wraps around the bar cycle
    let d = Math.abs(bar - i);
    if (d > 2) d = 4 - d;
    // 0..1: brightest at d=0, falls off by d=0.5
    const v = Math.max(0, 1 - d / 0.5);
    return v;
  };

  return (
    <div className={`beat-clock ${synced ? 'synced' : ''} ${isMaster ? 'master' : ''}`} title={isMaster ? 'Beat clock — this deck is the master beat reference' : 'Beat clock — lights up on each beat (1, 2, 3, 4) of this deck'}>
      <div className="beat-clock-label" style={{ color: deckColor }}>{label}</div>
      <div className="beat-clock-leds" ref={ref}>
        {[0, 1, 2, 3].map((i) => {
          const v = bpm ? intensity(i) : 0;
          // Beat 1 is the "downbeat" — slightly larger + outline accent
          const isDownbeat = i === 0;
          return (
            <div
              key={i}
              className={`beat-clock-led ${isDownbeat ? 'down' : ''}`}
              style={{
                '--led-color': deckColor,
                '--led-on': v.toFixed(2),
              }}
              title={`Beat ${i + 1}${isDownbeat ? ' (downbeat)' : ''}`}
            >
              <span className="beat-clock-num">{i + 1}</span>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// -----------------------------------------------------------------------------
// CENTER BPM — large pulse-on-beat counter shared between decks
// -----------------------------------------------------------------------------
// Shows the master deck's current BPM (or "—" if no deck is loaded). When both
// decks are loaded and synced, the border glows green to confirm the lock.
// Pulses on each master downbeat — the user can glance at this one number to
// confirm the mix is alive.
function CenterBpm({ masterBpm, masterFirstBeat, masterSide, bothLoaded, bpmDiff, locked }) {
  const [pulse, setPulse] = useS(0);
  const lastBeatRef = useR(0);

  useFx(() => {
    if (!masterBpm || !masterSide) return;
    let raf = 0;
    let mounted = true;
    const tick = () => {
      if (!mounted) return;
      try {
        const bar = window.AudioEngine?.getBarPhase?.(masterSide, masterFirstBeat || 0, masterBpm) || 0;
        // Detect a fresh beat boundary (any of beats 1..4)
        const beatNow = Math.floor(bar);
        if (beatNow !== lastBeatRef.current) {
          lastBeatRef.current = beatNow;
          setPulse(p => p + 1);
        }
      } catch {}
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => { mounted = false; cancelAnimationFrame(raf); };
  }, [masterBpm, masterFirstBeat, masterSide]);

  const status = !bothLoaded
    ? 'idle'
    : (bpmDiff != null && bpmDiff < 0.5 ? (locked ? 'locked' : 'matched') : 'off');

  return (
    <div
      className={`center-bpm status-${status}`}
      title={
        !bothLoaded ? 'Load both decks to see the shared BPM and matching status' :
        status === 'locked' ? 'BPM matched and beats are locked together' :
        status === 'matched' ? 'BPMs are matched. Hit MATCH on either deck to lock the beats together.' :
        'Tempos differ. Hit MATCH on either deck to auto-sync them.'
      }
    >
      <div className={`center-bpm-num pulse-${pulse % 2}`}>
        {masterBpm ? masterBpm.toFixed(1) : '---'}
      </div>
      <div className="center-bpm-label">BPM</div>
      {bothLoaded && (
        <div className="center-bpm-status">
          {status === 'locked' ? '◆ LOCKED' : status === 'matched' ? '◇ MATCHED' : '✗ OFF'}
        </div>
      )}
    </div>
  );
}

// -----------------------------------------------------------------------------
// DUAL WAVEFORM — A above, B mirrored below, both scrolling around their playhead
// -----------------------------------------------------------------------------
// This is the most useful beat-matching aid in pro software. We render it
// canvas-style with a small, fast RAF loop so the bars move smoothly. When
// beat-matched, the kick blobs of A and B should visibly align vertically.
//
// We deliberately KEEP a shared time window centered at the current playhead.
// Window = 16 beats (4 bars) at the master tempo, so 4 bar-tick lines appear
// across the full width and the user can read the bar structure at a glance.
function DualWaveform({ deckA, deckB, masterBpm }) {
  const cvA = useR(null);
  const cvB = useR(null);
  const wrapRef = useR(null);

  // Generate band data deterministically per track (same recipe as <Waveform/>)
  const bandsA = useMM(() => deckA.track ? makeBands(deckA.track.seed || 1) : null, [deckA.track?.seed]);
  const bandsB = useMM(() => deckB.track ? makeBands(deckB.track.seed || 1) : null, [deckB.track?.seed]);

  // Render loop
  useFx(() => {
    let raf = 0;
    let mounted = true;
    const draw = () => {
      if (!mounted) return;
      const cA = cvA.current, cB = cvB.current, wrap = wrapRef.current;
      if (cA && cB && wrap) {
        const W = wrap.clientWidth;
        const H = 64;
        if (cA.width !== W * 2) { cA.width = W * 2; cA.height = H * 2; cA.style.width = W + 'px'; cA.style.height = H + 'px'; }
        if (cB.width !== W * 2) { cB.width = W * 2; cB.height = H * 2; cB.style.width = W + 'px'; cB.style.height = H + 'px'; }
        // We tie the time window to the master BPM so both decks share an
        // identical pixel-per-second mapping. At 128 BPM, 16 beats = 7.5s.
        const bpm = masterBpm || deckA.track?.bpm || deckB.track?.bpm || 120;
        const beatsVisible = 16; // 4 bars
        const windowSec = (60 / bpm) * beatsVisible;
        drawDeck(cA, deckA, bandsA, windowSec, W, H, false, 'cyan');
        drawDeck(cB, deckB, bandsB, windowSec, W, H, true, 'pink');
      }
      raf = requestAnimationFrame(draw);
    };
    raf = requestAnimationFrame(draw);
    return () => { mounted = false; cancelAnimationFrame(raf); };
  }, [deckA.track?.id, deckB.track?.id, masterBpm, bandsA, bandsB]);

  const empty = !deckA.track && !deckB.track;
  return (
    <div ref={wrapRef} className={`dual-wave ${empty ? 'empty' : ''}`} title="Beat-match overview — when the kick blobs of A (top) and B (bottom) land on top of each other, the beats are locked.">
      <canvas ref={cvA} className="dual-wave-cv top" />
      <div className="dual-wave-axis" />
      <canvas ref={cvB} className="dual-wave-cv bot" />
      {empty && <div className="dual-wave-hint">Load tracks on both decks to see beat-match overview</div>}
    </div>
  );
}

// -----------------------------------------------------------------------------
// SHARED HELPERS (band generator + canvas draw)
// -----------------------------------------------------------------------------
function makeBands(seed) {
  if (!seed) return null;
  const N = 600;
  const rngL = mulberry32_(seed * 9301);
  const rngM = mulberry32_(seed * 7919 + 13);
  const rngH = mulberry32_(seed * 2477 + 91);
  const low = [], mid = [], high = [];
  for (let i = 0; i < N; i++) {
    const t = i / N;
    const envMain = 0.35 + 0.65 * Math.sin(t * Math.PI) * Math.sin(t * Math.PI * 1.3);
    const drop = Math.max(0, 1 - Math.abs(t - 0.45) * 8) * 0.35;
    const drop2 = Math.max(0, 1 - Math.abs(t - 0.78) * 10) * 0.3;
    const env = Math.min(1, envMain + drop + drop2);
    low.push(Math.max(0.08, env * (0.4 + rngL() * 0.6) * (0.7 + drop + drop2)));
    mid.push(Math.max(0.06, env * (0.35 + rngM() * 0.55)));
    high.push(Math.max(0.04, env * (0.25 + rngH() * 0.6)));
  }
  return { low, mid, high };
}
function mulberry32_(seed) {
  let a = seed >>> 0;
  return function () {
    a = (a + 0x6D2B79F5) >>> 0;
    let t = a;
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

// Draws a deck's wave centered on its current playhead, time-scaled by
// `windowSec` (the shared timeline). `mirror=true` flips vertically (Deck B).
function drawDeck(cv, deck, bands, windowSec, W, H, mirror, accent) {
  const ctx = cv.getContext('2d');
  ctx.scale(2, 2);
  ctx.clearRect(0, 0, W, H);

  // Background
  ctx.fillStyle = mirror ? 'rgba(255,80,160,0.04)' : 'rgba(80,200,255,0.04)';
  ctx.fillRect(0, 0, W, H);

  // Centerline / playhead axis
  if (!deck.track || !bands) {
    // Empty pulse line
    ctx.strokeStyle = 'rgba(255,255,255,0.1)';
    ctx.beginPath();
    const y = mirror ? 0 : H - 1;
    ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke();
    ctx.scale(0.5, 0.5);
    return;
  }

  const dur = deck.track.duration || 180;
  const totalSamples = bands.low.length;
  const pxPerSec = W / windowSec;
  const playSec = (deck.progress || 0) * dur;
  const startSec = playSec - windowSec / 2;
  const samplesPerSec = totalSamples / dur;

  // Draw bar grid lines (downbeats of THIS deck — yellow tick on the time axis)
  if (deck.track.bpm) {
    const beatSec = 60 / deck.track.bpm;
    const barSec = 4 * beatSec;
    const firstBeat = deck.track.firstBeat || 0;
    // Find first bar boundary at or after startSec
    const offset = ((startSec - firstBeat) % barSec + barSec) % barSec;
    let bSec = startSec - offset + barSec; // first one inside window
    ctx.fillStyle = accent === 'cyan' ? 'rgba(120,220,255,0.14)' : 'rgba(255,120,200,0.14)';
    while (bSec < startSec + windowSec) {
      const x = (bSec - startSec) * pxPerSec;
      // Bar tick — full height
      ctx.fillRect(x, 0, 1, H);
      bSec += barSec;
    }
  }

  // Draw waveform sample by sample
  const baseY = mirror ? 0 : H;
  const dir = mirror ? 1 : -1;
  for (let x = 0; x < W; x++) {
    const tSec = startSec + x / pxPerSec;
    if (tSec < 0 || tSec >= dur) continue;
    const i = Math.floor(tSec * samplesPerSec);
    const lo = bands.low[i] || 0;
    const mi = bands.mid[i] || 0;
    const hi = bands.high[i] || 0;
    const total = Math.min(1, lo * 1.2 + mi * 0.8 + hi * 0.5);
    const h = total * (H - 4);
    // Body color: accent
    const grad = ctx.createLinearGradient(0, baseY, 0, baseY + dir * h);
    if (accent === 'cyan') {
      grad.addColorStop(0, 'rgba(120,230,255,0.95)');
      grad.addColorStop(1, 'rgba(80,160,255,0.4)');
    } else {
      grad.addColorStop(0, 'rgba(255,140,210,0.95)');
      grad.addColorStop(1, 'rgba(220,80,180,0.4)');
    }
    ctx.fillStyle = grad;
    ctx.fillRect(x, mirror ? 0 : baseY - h, 1, h);
  }

  // Playhead — bright vertical line
  ctx.fillStyle = mirror ? 'rgba(255,180,220,0.9)' : 'rgba(180,230,255,0.9)';
  ctx.fillRect(W / 2 - 0.5, 0, 1.5, H);

  ctx.scale(0.5, 0.5);
}

Object.assign(window, { BeatClock, CenterBpm, DualWaveform });
