// app/static/js/services/bridgeAudioNode.js
// Audio sink: consumes 'bridge-audio' PCM frames (native bridge) and plays them
// through a ScriptProcessorNode into the WebAudio graph (track FX / master bus).
(function () {
  // Bridge renders one 256-sample block per ~5.8ms; ScriptProcessor fires once
  // per 4096 samples (~16 blocks @44.1k). The ring MUST cover one full callback
  // period or we underrun (silence gaps) — 24 blocks ~= 139ms headroom.
  var RING_DEPTH = 24;
  var SP_BUFFER = 4096;
  var _chunks = [];       // [{l: Float32Array(256), r: Float32Array(256)}]
  var _spn = null;
  var _gainNode = null;
  var _ctx = null;
  var _initialized = false;
  var _dest = null; // destination gain node da noi (chong connect trung)

  function _getCtx() {
    if (_ctx) return _ctx;
    if (typeof getAudioContext === 'function') _ctx = getAudioContext();
    else if (window.__sharedAudioCtx) _ctx = window.__sharedAudioCtx;
    else _ctx = new (window.AudioContext || window.webkitAudioContext)();
    return _ctx;
  }

  function init(audioCtx) {
    if (_initialized) return;
    _ctx = audioCtx || _getCtx();
    _gainNode = _ctx.createGain();
    _gainNode.gain.value = 1.0;
    _spn = _ctx.createScriptProcessor(SP_BUFFER, 0, 2);
    _spn.onaudioprocess = function (e) {
      var L = e.outputBuffer.getChannelData(0);
      var R = e.outputBuffer.getChannelData(1);
      var outLen = L.length;
      L.fill(0); R.fill(0);
      if (!_chunks.length) return;
      // Drain as many queued blocks as fit in this callback (16 max) — filling
      // only one block per callback previously played 1/16 of the audio.
      var written = 0;
      while (written < outLen && _chunks.length) {
        var blk = _chunks[0];
        var n = Math.min(blk.l.length, outLen - written);
        L.set(blk.l.subarray(0, n), written);
        R.set(blk.r.subarray(0, n), written);
        written += n;
        if (n >= blk.l.length) {
          _chunks.shift();
        } else {
          // Partial block (only possible if block size != 256): keep remainder.
          blk.l = blk.l.subarray(n);
          blk.r = blk.r.subarray(n);
        }
      }
    };
    _spn.connect(_gainNode);
    _initialized = true;
    console.log('[BridgeAudioNode] initialized (ring depth ' + RING_DEPTH + ')');
  }

  function onAudio(l, r) {
    if (!_initialized) init();
    _chunks.push({ l: new Float32Array(l), r: new Float32Array(r) });
    // Drop oldest on overrun — keeps latency bounded (~139ms max).
    while (_chunks.length > RING_DEPTH) _chunks.shift();
  }

  function flush() { _chunks = []; }

  function getOutputNode() { return _gainNode; }

  function disconnect() {
    flush();
    if (_spn && _gainNode) {
      try { _spn.disconnect(); } catch (e) {}
      try { _gainNode.disconnect(); } catch (e) {}
    }
    // Reset state so a later init() can rebuild (AudioRoutingEngine re-connect).
    _initialized = false;
    _spn = null;
    _gainNode = null;
    _ctx = null;
    _dest = null;
  }

  window.BridgeAudioNode = {
    init: init,
    onAudio: onAudio,
    flush: flush,
    getOutputNode: getOutputNode,
    connect: function (dest) {
      if (!_gainNode) return;
      if (_dest === dest) return;              // da noi — khong noi trung (double-sum)
      if (_dest) { try { _gainNode.disconnect(_dest); } catch (e) {} }
      _gainNode.connect(dest);
      _dest = dest;
    },
    disconnect: disconnect,
    isReady: function () { return _initialized; }
  };
})();
