'use strict';
// ── KAOS front-end controller ──────────────────────────────────────────────
// Connects to /events (SSE) at ~20 Hz and mirrors server state into the DOM.
// All control actions POST to the Flask API; the next SSE tick confirms them.

// ── SSE connection ──────────────────────────────────────────────────────────

const evtSource = new EventSource('/events');
let   lastState = {};   // mirror of most recent server payload

evtSource.onmessage = function (e) {
  try {
    lastState = JSON.parse(e.data);
    renderState(lastState);
  } catch (_) {}
};

evtSource.onerror = function () {
  setStatusPill('idle', '⚡ Disconnected');
};

// ── DOM rendering ────────────────────────────────────────────────────────────

function renderState(d) {
  // Status pill
  if (d.mute) {
    setStatusPill('muted', '○ MIDI Muted');
  } else if (d.hand_detected) {
    setStatusPill('hand', '✦ Hand Detected');
  } else {
    setStatusPill('running', '● Running — No Hand');
  }

  // Stats
  setText('s-cap-fps', d.capture_fps.toFixed(0));
  setText('s-vis-fps', d.process_fps.toFixed(0));
  setText('s-lat',     d.process_ms.toFixed(1));
  setText('s-cc',      d.cc_value);
  setText('s-cc-label', `CC ${d.cc_number}`);

  // CC bar
  const pct = ((d.cc_value / 127) * 100).toFixed(1);
  document.getElementById('cc-bar').style.width = pct + '%';
  setText('cc-raw-label', d.cc_value);

  // Mute button
  const btnMute = document.getElementById('btn-mute');
  if (d.mute) {
    btnMute.textContent = '○ MIDI Muted';
    btnMute.className   = 'btn btn-muted-state wide';
  } else {
    btnMute.textContent = '● MIDI Active';
    btnMute.className   = 'btn btn-active wide';
  }

  // Toggle buttons
  applyToggle('btn-invert', d.invert,  'Invert');
  applyToggle('btn-curve',  d.curve,   'Curve');
  applyToggle('btn-preview', d.preview_enabled, 'Preview');

  // Calibration indicator
  renderCalIndicator(d.cal_min, d.cal_max);

  // Sync sliders (skip if user is dragging)
  syncSlider('min_cutoff',     d.min_cutoff,     1);
  syncSlider('beta',           d.beta,           2);
  syncSlider('deadzone',       d.deadzone,       0);
  syncSlider('pitch_range',    d.pitch_range,    2);
  syncSlider('pitch_deadzone', d.pitch_deadzone, 2);

  // Hand lock selector
  document.querySelectorAll('#hand-seg button').forEach(btn => {
    const val = btn.textContent === 'Any' ? 'any' : btn.textContent;
    btn.classList.toggle('seg-active', val === d.hand_lock);
  });
  const badge = document.getElementById('hand-badge');
  if (badge) {
    badge.textContent = d.hand_detected ? (d.active_hand || '✓') : '';
  }

  // Pitch bend
  applyToggle('btn-pitch', d.pitch_enabled, 'Pitch Bend');
  document.getElementById('pitch-controls').style.display =
    d.pitch_enabled ? 'block' : 'none';
  applyToggle('btn-pitch-inv', d.pitch_invert, 'Invert');
  renderPitchMeter(d.pitch_value);

  // Sync CC select
  syncCCSelect(d.cc_number);

  // Preview visibility
  const wrap = document.getElementById('preview-wrap');
  const img  = document.getElementById('preview-img');
  if (d.preview_enabled) {
    wrap.style.display = 'block';
    if (!img.src.endsWith('/video_feed')) img.src = '/video_feed';
  } else {
    wrap.style.display = 'none';
    img.src = '';
  }
}

function setStatusPill(type, text) {
  const pill = document.getElementById('status-pill');
  const span = document.getElementById('status-text');
  pill.className = `pill pill-${type}`;
  span.textContent = text;
}

function setText(id, value) {
  const el = document.getElementById(id);
  if (el) el.textContent = value;
}

function applyToggle(id, active, label) {
  const btn = document.getElementById(id);
  if (!btn) return;
  if (active) {
    btn.className   = 'btn btn-toggle btn-toggle-on';
    btn.textContent = `${label}: On`;
  } else {
    btn.className   = 'btn btn-toggle';
    btn.textContent = `${label}: Off`;
  }
  if (id === 'btn-preview') btn.classList.add('wide');
}

function renderCalIndicator(min, max) {
  const el = document.getElementById('cal-indicator');
  if (!el) return;
  if (min !== null && max !== null) {
    el.className   = 'cal-ready';
    el.textContent = `✓ Calibrated   MIN ${min.toFixed(4)}   MAX ${max.toFixed(4)}`;
  } else if (min !== null) {
    el.className   = 'cal-partial';
    el.textContent = 'MIN set — now bring hand close and click MAX';
  } else if (max !== null) {
    el.className   = 'cal-partial';
    el.textContent = 'MAX set — now hold hand far and click MIN';
  } else {
    el.className   = 'cal-uncal';
    el.textContent = 'Not calibrated — hold hand far, click MIN; bring it near, click MAX';
  }
}

// ── Slider sync ──────────────────────────────────────────────────────────────
// Track whether a slider is being actively dragged so we don't stomp it.

const _sliderBusy  = {};   // paramName → true while user is dragging
const _sliderTimer = {};   // paramName → setTimeout handle

function syncSlider(param, value, decimals) {
  if (_sliderBusy[param]) return;
  const slider  = document.getElementById(`sl-${param}`);
  const display = document.getElementById(`v-${param}`);
  if (slider)  slider.value    = value;
  if (display) display.textContent = parseFloat(value).toFixed(decimals);
}

function syncCCSelect(ccNum) {
  const sel = document.getElementById('cc-select');
  if (!sel) return;
  const known = ['11', '1', '74'];
  if (known.includes(String(ccNum))) {
    sel.value = String(ccNum);
    document.getElementById('cc-custom-row').style.display = 'none';
  } else {
    sel.value = 'custom';
    document.getElementById('cc-custom-row').style.display = 'flex';
    document.getElementById('cc-custom').value = ccNum;
  }
}

// ── Slider input handler ─────────────────────────────────────────────────────

function onSlider(param, value, displayId, decimals) {
  // Mark as being dragged
  _sliderBusy[param] = true;
  clearTimeout(_sliderTimer[param]);
  _sliderTimer[param] = setTimeout(() => {
    _sliderBusy[param] = false;
  }, 1200);

  // Update label immediately
  const display = document.getElementById(displayId);
  if (display) display.textContent = value.toFixed(decimals);

  // Debounced POST
  scheduleParam(param, value);
}

// ── Debounced param send ─────────────────────────────────────────────────────

let _paramTimer   = null;
let _pendingParam = {};

function scheduleParam(key, value) {
  _pendingParam[key] = value;
  clearTimeout(_paramTimer);
  _paramTimer = setTimeout(flushParams, 80);
}

function flushParams() {
  if (!Object.keys(_pendingParam).length) return;
  postJSON('/set_params', _pendingParam);
  _pendingParam = {};
}

function sendParam(key, value) {
  postJSON('/set_params', { [key]: value });
}

function postJSON(url, body) {
  fetch(url, {
    method:  'POST',
    headers: { 'Content-Type': 'application/json' },
    body:    JSON.stringify(body),
  }).catch(() => {});
}

function postAction(url) {
  fetch(url, { method: 'POST' }).catch(() => {});
}

// ── Button handlers ──────────────────────────────────────────────────────────

function toggleMute()        { sendParam('mute',          !lastState.mute);          }
function toggleInvert()      { postAction('/toggle_invert'); }
function toggleCurve()       { postAction('/toggle_curve');  }
function togglePitch()       { sendParam('pitch_enabled',  !lastState.pitch_enabled); }

function setHandLock(hand) {
  sendParam('hand_lock', hand);
}
function togglePitchInvert() { sendParam('pitch_invert',   !lastState.pitch_invert);  }

function setPitchCenter() {
  fetch('/set_pitch_center', { method: 'POST' }).catch(() => {});
}

function renderPitchMeter(pb) {
  // pb: -8192 … 0 … 8191
  const bar   = document.getElementById('pitch-bar');
  const label = document.getElementById('pitch-val-label');
  if (!bar) return;
  const rel = pb / 8191;          // -1 … 0 … 1
  if (rel >= 0) {
    bar.style.left  = '50%';
    bar.style.width = (rel * 50).toFixed(1) + '%';
  } else {
    bar.style.width = (Math.abs(rel) * 50).toFixed(1) + '%';
    bar.style.left  = (50 + rel * 50).toFixed(1) + '%';
  }
  if (label) label.textContent = pb;
}

function togglePreview() {
  sendParam('preview_enabled', !lastState.preview_enabled);
}

function setMin() {
  fetch('/set_min', { method: 'POST' })
    .then(r => r.json())
    .then(d => renderCalIndicator(d.min_proxy, d.max_proxy))
    .catch(() => {});
}

function setMax() {
  fetch('/set_max', { method: 'POST' })
    .then(r => r.json())
    .then(d => renderCalIndicator(d.min_proxy, d.max_proxy))
    .catch(() => {});
}

function resetCal() {
  postAction('/reset_calibration');
}

// ── CC number selector ───────────────────────────────────────────────────────

function onCCSelect() {
  const sel       = document.getElementById('cc-select');
  const customRow = document.getElementById('cc-custom-row');
  if (sel.value === 'custom') {
    customRow.style.display = 'flex';
  } else {
    customRow.style.display = 'none';
    sendParam('cc_number', +sel.value);
  }
}

// ── Rate limit segmented control ─────────────────────────────────────────────

function setRateLimit(hz) {
  sendParam('rate_limit', hz);
  document.querySelectorAll('#rate-seg button').forEach(btn => {
    btn.classList.toggle('seg-active', +btn.textContent === hz);
  });
}
