/**
 * @file React hook for registering one or more keyboard shortcuts.
 *
 * Wraps the framework-agnostic ShortcutManager with React lifecycle management.
 * Supports two call signatures:
 *
 *   Single shortcut (original):
 *     useShortcut('mod+k', handler, options?)
 *
 *   Array of shortcuts (new):
 *     useShortcut([
 *       { keys: 'mod+k', handler: handleSave, id: 'save', description: 'Save' },
 *       { keys: 'mod+s', handler: handleExport, description: 'Export' },
 *     ], sharedOptions?)
 *
 * --- Namespace gating ---
 * Impact UI library components pass a `namespace` string on their bindings
 * (e.g. 'sidebar', 'filterPanel'). The ShortcutsProvider at the app root
 * controls which namespaces are active. Bindings WITHOUT a namespace are
 * always registered regardless of provider config — this protects consumer
 * code that calls useShortcut directly.
 *
 * In array mode `sharedOptions` supplies default values for every item.
 * Per-item fields always win over shared defaults.
 *
 * Handlers are kept fresh via refs so the effect never needs to re-run just
 * because the handler function reference changed.
 */

import { useContext, useEffect, useRef } from 'react';
import { ShortcutManager } from '../../utils/shortcuts';
import { ShortcutScopeContext } from './ShortcutContext';
import { ShortcutsConfigContext } from './ShortcutsConfigContext';

/** @typedef {import('../../utils/shortcuts/types').ShortcutBinding} ShortcutBinding */

/**
 * Decide whether a given namespace is allowed by the current provider config.
 *
 * Rules:
 *  - No namespace → always allowed (consumer code, unaffected by provider)
 *  - Namespace set + provider disabled → skip
 *  - Namespace set + provider enabled, no namespace filter → allow all
 *  - Namespace set + provider enabled, filter list present → allow if in list
 *
 * @param {string | undefined} ns
 * @param {{ enabled: boolean, namespaces: string[] | null }} config
 * @returns {boolean}
 */
function isNamespaceAllowed(ns, config) {
  if (!ns) return true;
  if (!config.enabled) return false;
  if (!config.namespaces) return true;
  return config.namespaces.includes(ns);
}

/**
 * Register one or more keyboard shortcuts, cleaned up automatically on unmount.
 *
 * --- Single mode ---
 * @param {string} keysOrBindings
 * @param {(event: KeyboardEvent) => void} handlerOrShared
 * @param {Object} [singleOptions]
 * @param {string}  [singleOptions.id]
 * @param {string}  [singleOptions.scope]
 * @param {string}  [singleOptions.namespace]
 * @param {boolean} [singleOptions.enabled=true]
 * @param {boolean} [singleOptions.allowInInput=false]
 * @param {number}  [singleOptions.priority=0]
 * @param {string}  [singleOptions.description='']
 * @param {boolean} [singleOptions.preventDefault=true]
 * @param {boolean} [singleOptions.propagate=false]
 *
 * --- Array mode ---
 * @param {ShortcutBinding[]} keysOrBindings
 * @param {Omit<ShortcutBinding, 'keys'|'handler'>} [handlerOrShared] shared defaults
 */
export function useShortcut(keysOrBindings, handlerOrShared = {}, singleOptions = {}) {
  const contextScope = useContext(ShortcutScopeContext);
  const config = useContext(ShortcutsConfigContext);

  const isArrayMode = Array.isArray(keysOrBindings);

  const bindings = isArrayMode
    ? keysOrBindings
    : [{ keys: keysOrBindings, handler: handlerOrShared, ...singleOptions }];

  const shared = isArrayMode ? handlerOrShared : {};

  // Stable IDs — computed once on first render.
  const stableIdsRef = useRef(null);
  if (stableIdsRef.current === null) {
    stableIdsRef.current = bindings.map((b) => {
      const scope = b.scope ?? shared.scope ?? contextScope;
      return b.id ?? `${scope}::${b.keys}`;
    });
  }

  // Keep every handler fresh without triggering re-registration.
  const handlersRef = useRef([]);
  bindings.forEach((b, i) => {
    handlersRef.current[i] = b.handler;
  });

  // Serialize all structural (non-function) fields including namespace and
  // config state so the effect re-runs when the provider config changes.
  const configKey = config.enabled
    ? config.namespaces
      ? config.namespaces.join(',')
      : 'all'
    : 'off';

  const depsKey = bindings
    .map((b) => {
      const merged = { ...shared, ...b };
      return [
        b.keys,
        merged.scope ?? contextScope,
        merged.namespace ?? '',
        merged.enabled ?? true,
        merged.allowInInput ?? false,
        merged.priority ?? 0,
        merged.description ?? '',
        merged.preventDefault ?? true,
        merged.propagate ?? false,
      ].join('|');
    })
    .join('||') + '||config:' + configKey;

  useEffect(() => {
    const manager = ShortcutManager.getInstance();
    const ids = stableIdsRef.current;
    const registered = [];

    bindings.forEach((b, i) => {
      const merged = { ...shared, ...b };
      const {
        keys,
        namespace,
        scope = contextScope,
        enabled = true,
        allowInInput = false,
        priority = 0,
        description = '',
        preventDefault = true,
        propagate = false,
      } = merged;

      if (!enabled) return;
      if (!isNamespaceAllowed(namespace, config)) return;

      manager.register({
        id: ids[i],
        keys,
        handler: (e) => handlersRef.current[i](e),
        scope,
        priority,
        allowInInput,
        description,
        preventDefault,
        propagate,
      });

      registered.push(ids[i]);
    });

    return () => {
      registered.forEach((id) => manager.unregister(id));
    };
  }, [depsKey, contextScope]);
}
