/**
 * Canvas Interaction Store — runtime gates for canvas scroll + zoom behavior.
 *
 * Two toggles that go *beyond* presentation (which is purely visual):
 *
 *   - `scrollAxis` — restricts the native scroll axes of the canvas
 *     scroll container. Useful when a canvas is rendered as a "webpage"
 *     (e.g. a landing page) and should scroll only vertically.
 *     Valid values: `"both"` (default), `"vertical"`, `"horizontal"`, `"none"`.
 *
 *   - `zoomGestures` — when false, disables the cmd+wheel zoom, the
 *     trackpad pinch zoom, and the iframe-forwarded wheel-zoom message
 *     handler. The +/- toolbar buttons and keyboard shortcuts still work —
 *     only the gesture inputs are gated. Useful for landing pages where
 *     you want users to scroll the canvas like a normal webpage instead
 *     of accidentally zooming it.
 *
 * Defaults are seeded from `canvas.scroll.axis` and `canvas.zoom.gestures`
 * in `storyboard.config.json`. Runtime overrides can be applied at any
 * time via `setCanvasInteraction(partial)`.
 *
 * This is an **advanced, undocumented** extension surface — see
 * `core/stores/presentationStore.js` for the sibling system.
 *
 * Framework-agnostic (zero npm dependencies).
 */

const VALID_AXES = new Set(['both', 'vertical', 'horizontal', 'none'])

const _defaults = {
  scrollAxis: 'both',
  zoomGestures: true,
}

let _state = { ..._defaults }

/** @type {Set<Function>} */
const _listeners = new Set()

let _snapshotVersion = 0

/**
 * Seed the store from a partial config (typically from
 * `storyboard.config.json` → `canvas.scroll.axis` + `canvas.zoom.gestures`).
 * Replaces any previous overrides. Called once at app startup.
 *
 * @param {{ scrollAxis?: string, zoomGestures?: boolean }} [config]
 */
export function initCanvasInteraction(config) {
  _state = { ..._defaults }
  if (config && typeof config === 'object') {
    if (typeof config.scrollAxis === 'string' && VALID_AXES.has(config.scrollAxis)) {
      _state.scrollAxis = config.scrollAxis
    }
    if (typeof config.zoomGestures === 'boolean') {
      _state.zoomGestures = config.zoomGestures
    }
  }
  _notify()
}

/**
 * Apply a partial runtime override. Unspecified keys keep their current value.
 * @param {{ scrollAxis?: string, zoomGestures?: boolean }} partial
 */
export function setCanvasInteraction(partial) {
  if (!partial || typeof partial !== 'object') return
  let changed = false
  if (typeof partial.scrollAxis === 'string' && VALID_AXES.has(partial.scrollAxis) && partial.scrollAxis !== _state.scrollAxis) {
    _state.scrollAxis = partial.scrollAxis
    changed = true
  }
  if (typeof partial.zoomGestures === 'boolean' && partial.zoomGestures !== _state.zoomGestures) {
    _state.zoomGestures = partial.zoomGestures
    changed = true
  }
  if (changed) _notify()
}

/**
 * @returns {{ scrollAxis: 'both'|'vertical'|'horizontal'|'none', zoomGestures: boolean }}
 */
export function getCanvasInteraction() {
  return _state
}

/**
 * Subscribe to interaction changes. Returns an unsubscribe fn.
 * Compatible with `useSyncExternalStore`.
 * @param {Function} callback
 * @returns {Function}
 */
export function subscribeToCanvasInteraction(callback) {
  _listeners.add(callback)
  return () => _listeners.delete(callback)
}

/**
 * Snapshot version for change detection. Compatible with `useSyncExternalStore`.
 * @returns {string}
 */
export function getCanvasInteractionSnapshot() {
  return String(_snapshotVersion)
}

function _notify() {
  _snapshotVersion++
  for (const cb of _listeners) {
    try { cb() } catch (err) {
      console.error('[storyboard] Error in canvas-interaction subscriber:', err)
    }
  }
}

/** Reset all state. Only for tests. */
export function _resetCanvasInteraction() {
  _state = { ..._defaults }
  _listeners.clear()
  _snapshotVersion = 0
}
