/**
 * External VU Store
 *
 * Manages VU meter data outside React state to avoid expensive reconciliation cycles.
 * Uses requestAnimationFrame batching to limit updates to 60fps.
 *
 * The native C++ client sends VU data at 50-70 updates/second. This store buffers
 * those updates and only emits changes on animation frames, preventing React from
 * re-rendering more than 60 times per second.
 */

// Core state - stored outside React
let vuLevels = {}; // { mixerId: { level: 0.0-1.0, clipping: boolean, timestamp: ms } }
let listeners = new Set(); // Subscriber callbacks
let pendingUpdates = {}; // Buffer for incoming updates before RAF flush
let rafId = null; // Animation frame ID for cleanup

/**
 * Notify all subscribers that vuLevels has changed
 */
function emitChange() {
  for (const listener of listeners) {
    listener();
  }
}

/**
 * Flush pending updates into vuLevels and notify subscribers
 * Runs on requestAnimationFrame loop (60fps max)
 */
function flushUpdates() {
  // Only create new vuLevels object if there are pending updates
  if (Object.keys(pendingUpdates).length > 0) {
    // Merge pending updates into vuLevels
    vuLevels = { ...vuLevels, ...pendingUpdates };
    pendingUpdates = {};
    emitChange();
  }

  // Request next frame
  rafId = requestAnimationFrame(flushUpdates);
}

/**
 * External VU store API
 */
export const vuStore = {
  /**
   * Update VU level for a mixer
   * Called by native bridge at 50-70 updates/sec
   * Buffers update without immediate emission (RAF loop handles it)
   *
   * @param {string} mixerId - Qualified mixer ID (e.g., "M123" or "P456")
   * @param {number} level - Normalized level 0.0-1.0
   * @param {boolean} clipping - Whether audio is clipping
   */
  updateLevel(mixerId, level, clipping) {
    pendingUpdates[mixerId] = {
      level,
      clipping,
      timestamp: performance.now()
    };
    // Don't emit immediately - RAF loop will flush on next frame
  },

  /**
   * Remove VU level for a mixer
   * Called when mixer is removed from session
   * Emits change immediately since this is infrequent
   *
   * @param {string} mixerId - Qualified mixer ID
   */
  removeLevel(mixerId) {
    // Remove from pending updates if exists
    delete pendingUpdates[mixerId];

    // Remove from vuLevels if exists
    if (vuLevels[mixerId]) {
      const newLevels = { ...vuLevels };
      delete newLevels[mixerId];
      vuLevels = newLevels;
      emitChange();
    }
  },

  /**
   * Get snapshot of all VU levels
   * CRITICAL: Must return same reference if data hasn't changed
   * This is required for useSyncExternalStore's Object.is() comparison
   *
   * @returns {Object} - Map of mixerId to { level, clipping, timestamp }
   */
  getSnapshot() {
    return vuLevels;
  },

  /**
   * Get snapshot of single mixer's VU level
   * More efficient for components that only need one mixer's data
   * Checks both pending updates and flushed levels for immediate access
   *
   * @param {string} mixerId - Qualified mixer ID
   * @returns {Object|null} - { level, clipping, timestamp } or null
   */
  getLevelSnapshot(mixerId) {
    // Check pending updates first (most recent), then flushed levels
    return pendingUpdates[mixerId] || vuLevels[mixerId] || null;
  },

  /**
   * Subscribe to VU level changes
   * Starts RAF loop when first subscriber is added
   * Returns unsubscribe function
   *
   * @param {Function} listener - Callback invoked on each change
   * @returns {Function} - Unsubscribe function
   */
  subscribe(listener) {
    listeners.add(listener);

    // Start RAF loop when first subscriber
    if (listeners.size === 1 && !rafId) {
      rafId = requestAnimationFrame(flushUpdates);
    }

    // Return unsubscribe function
    return () => {
      listeners.delete(listener);

      // Stop RAF loop when no subscribers
      if (listeners.size === 0 && rafId) {
        cancelAnimationFrame(rafId);
        rafId = null;
      }
    };
  },

  /**
   * Reset all VU state
   * Called on session end for cleanup
   */
  reset() {
    vuLevels = {};
    pendingUpdates = {};
    // Note: Don't clear listeners or cancel RAF - subscribers will clean themselves up
  }
};
