import type { VoidFunction } from '@robonen/stdlib';
import { noop } from '@robonen/stdlib';
import { useEventListener } from '@/composables/browser/useEventListener';
import { tryOnScopeDispose } from '@/composables/lifecycle/tryOnScopeDispose';
import { defaultWindow } from '@/types';

type EscapeListener = (event: KeyboardEvent) => void;

// Module-scoped stack: only the topmost non-paused layer handles Escape so that
// nested dismissables behave correctly (top-most dialog closes first).
const stack: EscapeListener[] = [];
let installed = false;
let cleanup: VoidFunction = noop;

function install() {
  if (installed || !defaultWindow) return;
  installed = true;

  cleanup = useEventListener(defaultWindow, 'keydown', (event: KeyboardEvent) => {
    if (event.key !== 'Escape') return;

    const top = stack.at(-1);
    top?.(event);
  }, { capture: true });
}

function uninstall() {
  if (!installed || stack.length > 0) return;
  installed = false;
  cleanup();
  cleanup = noop;
}

/**
 * @name useEscapeKey
 * @category Sensors
 * @description Register a callback for the topmost Escape keydown. Uses an internal
 * stack so that nested layers (e.g. nested Dialogs) dismiss in the correct order —
 * only the most recently-registered listener fires for a given keydown.
 *
 * @param {(event: KeyboardEvent) => void} handler Callback invoked on the topmost Escape
 * @returns {VoidFunction} Stop handle that removes the subscription
 *
 * @since 0.0.14
 */
export function useEscapeKey(handler: EscapeListener): VoidFunction {
  if (!defaultWindow) return noop;

  install();
  stack.push(handler);

  const stop = () => {
    const i = stack.lastIndexOf(handler);
    if (i !== -1) stack.splice(i, 1);
    uninstall();
  };

  tryOnScopeDispose(stop);
  return stop;
}
