/* MIDI OUT — send the groove as MIDI to external gear (a drum module / e-kit), in sync with
   playback, plus an optional 24-PPQN MIDI clock + Start/Stop so the gear's tempo locks to the editor.
   Shared by editor.html + pm_e-2.html (one copy → no drift).

   Relies on page globals: $, audioCtx, state, _midiOutputs(), _ensureMidi(), _isDevicePort().
   The page wires three transport hooks: midiOutStart(t0) in start(), midiOutStop() in stop(),
   midiOutClock(aheadTime) at the end of scheduler(); engine.js calls onMeterHit() per scheduled hit. */
const SOUND_GM = {
  kick: 36, kick808: 36, kick909: 36, snare: 38, snare808: 38, snare909: 38,
  clap: 39, clap808: 39, clap909: 39, rim: 37,
  hatClosed: 42, hat808: 42, hat909: 42, hatOpen: 46, openHat808: 46,
  ride: 51, ride909: 51, crash: 49, crash909: 49,
  tomLow: 41, tom808: 45, tomMid: 45, tomHigh: 48, tambourine: 54,
  cowbell: 56, cowbell808: 56, woodblock: 76, jamblock: 76, claves: 75, beep: 37,
};
let _midiOutOn = false, _midiClkNext = 0;

function _midiOutTarget() {
  const sel = $("midiOutSel"); if (!sel) return null;
  return _midiOutputs().find((o) => o.id === sel.value) || null;
}
function populateMidiOutPorts() {
  const sel = $("midiOutSel"); if (!sel) return;
  const outs = _midiOutputs(), prev = sel.value;
  sel.innerHTML = outs.length
    ? outs.map((o) => `<option value="${o.id}">${(o.name || "output").slice(0, 22)}</option>`).join("")
    : `<option value="">(no MIDI outputs)</option>`;
  if (outs.some((o) => o.id === prev)) sel.value = prev;                              // keep prior choice
  else { const ext = outs.find((o) => !_isDevicePort(o)); sel.value = ((ext || outs[0] || {}).id) || ""; }  // prefer external gear
}
// schedule `bytes` at audio-context time `audioTime`, converted to the Web-MIDI (performance.now) clock
function _midiOutSendAt(bytes, audioTime) {
  const out = _midiOutTarget(); if (!out) return;
  const ts = (typeof audioCtx !== "undefined" && audioCtx)
    ? performance.now() + (audioTime - audioCtx.currentTime) * 1000 : performance.now();
  try { out.send(bytes, ts); } catch (_) {}
}
// engine.js per-hit hook: GM drum Note-On (ch10) at the hit's audio time, then a Note-Off 60 ms later
function onMeterHit(sound, time, lvl) {
  if (!_midiOutOn) return;
  const note = SOUND_GM[sound]; if (note == null) return;
  const vel = lvl === 2 ? 112 : lvl === 3 ? 45 : 90;                                  // accent / ghost / normal
  _midiOutSendAt([0x99, note, vel], time);
  _midiOutSendAt([0x89, note, 0], time + 0.06);
}
function _clockOn() { const c = $("midiClkChk"); return _midiOutOn && !!c && c.checked; }
function midiOutStart(t0) { if (_clockOn()) { _midiOutSendAt([0xFA], t0); _midiClkNext = t0; } }   // MIDI Start
function midiOutStop() {
  if (_clockOn() && typeof audioCtx !== "undefined" && audioCtx) _midiOutSendAt([0xFC], audioCtx.currentTime);  // MIDI Stop
}
// schedule 24-PPQN clock ticks up to `aheadTime` (called from the page's look-ahead scheduler)
function midiOutClock(aheadTime) {
  if (!_clockOn() || !state.running || typeof audioCtx === "undefined" || !audioCtx) return;
  const tickDur = (60 / state.bpm) / 24;
  let guard = 0;
  while (_midiClkNext < aheadTime && guard++ < 512) { _midiOutSendAt([0xF8], _midiClkNext); _midiClkNext += tickDur; }
  if (_midiClkNext < aheadTime) _midiClkNext = aheadTime;             // never starve-loop if tickDur is tiny
}
function updateMidiOutBtn() {
  const b = $("midiOutBtn"), sel = $("midiOutSel"), clk = $("midiClkWrap"); if (!b) return;
  b.style.color = _midiOutOn ? "#7ab8ff" : "var(--muted)";
  b.style.borderColor = _midiOutOn ? "#7ab8ff" : "var(--edge)";
  if (sel) sel.hidden = !_midiOutOn;
  if (clk) clk.hidden = !_midiOutOn;
}
async function toggleMidiOut() {
  if (_midiOutOn) { midiOutStop(); _midiOutOn = false; updateMidiOutBtn(); return; }
  if (!(await _ensureMidi())) return alert("Driving external MIDI gear needs the Web MIDI API — use Chrome, Edge, or Firefox.");
  populateMidiOutPorts();
  if (!_midiOutputs().length) return alert("No MIDI output ports found. Connect your drum module / e-kit (USB-MIDI) and try again.");
  _midiOutOn = true; updateMidiOutBtn();
  if (state.running && typeof audioCtx !== "undefined" && audioCtx) midiOutStart(audioCtx.currentTime + 0.05);  // armed mid-play → Start now
}
