import { ConflictBehavior, HotkeyCallback, HotkeyCallbackContextFor, HotkeyChord, HotkeyMeta, HotkeySequence, ParsedHotkeyChord, RegisterableHotkey, RegisterableHotkeyChord, RegisterableHotkeySequence } from "./hotkey.types.js";
import { AppHotkey, AppHotkeysStore } from "./app-hotkeys.js";
import { Store } from "@tanstack/store";
//#region src/hotkey-manager.d.ts
/** An element, document, or window that hotkeys can listen on. */
export type Target = HTMLElement | Document | Window;
/**
 * Options shared by chords and sequences.
 */
export interface HotkeyOptions {
  /**
   * Listen during capture, before descendant listeners. Defaults to `false` (bubble).
   * With `stopPropagation: true`, a match on an ancestor prevents the event from
   * reaching descendant widgets. Other listeners on the same target still run.
   * Applies to both `keydown` and `keyup`, for chords and sequences.
   * Changing this option preserves held-key reset state and sequence progress.
   */
  capture?: boolean;
  /** Behavior when this hotkey conflicts with an existing registration on the same target. Defaults to 'warn' */
  conflictBehavior?: ConflictBehavior;
  /**
   * Soft-disable: when `false`, the callback does not run but the registration
   * stays in `HotkeyManager` (and in devtools). Toggling this should update the
   * existing handle via `setOptions` rather than unregistering. Defaults to `true`.
   */
  enabled?: boolean;
  /** The event type to listen for. Defaults to 'keydown' */
  eventType?: 'keydown' | 'keyup';
  /**
   * Skip keys that a focused control inside the target uses itself: typing in
   * text inputs, textarea, select, and contenteditable; Enter/Space on buttons
   * and summary elements; Enter on links; and navigation keys in ARIA widgets
   * such as menus, listboxes, tabs, and grids. A hotkey targeted at the control
   * itself still receives them. Defaults to `'auto'`, which chooses from the
   * hotkey (the first step of a sequence): true for single keys and Shift/Alt
   * combos; false for Ctrl/Meta shortcuts and Escape. Set false to handle keys
   * even in these controls.
   */
  ignoreInputs?: boolean | 'auto';
  /** The target platform for resolving 'Mod' */
  platform?: 'mac' | 'windows' | 'linux';
  /**
   * Prevent the default browser action when the hotkey matches. For a
   * sequence, every step that advances it does. Defaults to true.
   *
   * `'always'` also prevents it while a chord can't fire because it is
   * disabled or its scope is inactive or suspended, unless another hotkey
   * handles the key. It never stops propagation for such a chord. Sequences
   * treat `'always'` as `true`.
   */
  preventDefault?: boolean | 'always';
  /**
   * Stop event propagation when the hotkey matches. For a sequence, only the
   * last step does, so earlier steps reach other listeners. Defaults to true
   */
  stopPropagation?: boolean;
  /** The DOM element to attach the event listener to. Defaults to document. */
  target?: Target | null;
  /** Optional metadata (name, description, custom fields via declaration merging) */
  meta?: HotkeyMeta;
  /**
   * Scope this hotkey belongs to. Defaults to `'global'`, which is always
   * active. Other scopes are active while activated with
   * `HotkeyManager.activateScope`, which framework providers do while
   * mounted. While an exclusive scope is active, only its hotkeys, those of
   * scopes nested in it, and those of scopes it allows fire.
   * When active scopes share a hotkey on the same target, the most recently
   * activated scope handles it.
   */
  scope?: string;
  /** Id of the app hotkey this registration implements. Adapters set this when registering an app hotkey from `defineAppHotkeys`. */
  appHotkeyId?: string;
}
/** Options for a single chord such as `'Mod+S'`. */
export interface HotkeyChordOptions extends HotkeyOptions {
  /** If true, only trigger once until all keys are released. Default: false */
  requireReset?: boolean;
}
/** Options for a sequence such as `['G', 'G']`. */
export interface HotkeySequenceOptions extends HotkeyOptions {
  /** Maximum time between steps in milliseconds. Default: 1000 */
  timeout?: number;
}
/**
 * The options type for a hotkey: chord options for chords (including one-step
 * tuples), sequence options for sequences.
 */
export type HotkeyOptionsFor<T extends RegisterableHotkey> = [T] extends [never] ? HotkeyChordOptions & HotkeySequenceOptions : T extends readonly [RegisterableHotkeyChord] ? HotkeyChordOptions : T extends RegisterableHotkeySequence ? HotkeySequenceOptions : HotkeyChordOptions;
interface RegistrationBase<TOptions extends HotkeyOptions> {
  /** Unique identifier for this registration */
  id: string;
  /** The callback to invoke */
  callback: HotkeyCallback;
  /** Resolved options for this registration */
  options: TOptions;
  /** The resolved target element for this registration */
  target: Target;
  /** How many times this registration's callback has been triggered */
  triggerCount: number;
  /** Whether this registration has fired (and, for requireReset chords, needs a reset) */
  hasFired: boolean;
}
/** A registered chord in the HotkeyManager. */
export interface HotkeyChordRegistration extends RegistrationBase<HotkeyChordOptions> {
  kind: 'chord';
  hotkey: HotkeyChord;
  parsedChord: ParsedHotkeyChord;
  /** The concrete key/code that activated a requireReset registration. */
  activeMatch?: {
    key: string;
    code: string;
  };
}
/** A registered sequence in the HotkeyManager. */
export interface HotkeySequenceRegistration extends RegistrationBase<HotkeySequenceOptions> {
  kind: 'sequence';
  hotkey: HotkeySequence;
  parsedSequence: Array<ParsedHotkeyChord>;
  /** Steps matched in the current attempt */
  currentIndex: number;
  /** `Date.now()` when the last step in the current attempt matched */
  lastKeyTime: number;
}
/** A registered hotkey in the HotkeyManager. */
export type HotkeyRegistration = HotkeyChordRegistration | HotkeySequenceRegistration;
/**
 * Whether a registration is on. Focus isn't checked: a registration on an
 * element also needs focus inside that element to fire.
 * - `'active'`: enabled, and its scope is active
 * - `'suspended'`: enabled, but its scope is inactive or suspended by an exclusive scope
 * - `'disabled'`: registered with `enabled: false`
 */
export type HotkeyRegistrationStatus = 'active' | 'suspended' | 'disabled';
interface RegistrationViewBase {
  id: string;
  /** Whether the registration is enabled and in an active scope. */
  status: HotkeyRegistrationStatus;
  target: Target;
  triggerCount: number;
  hasFired: boolean;
}
/** Public view of a chord registration, without its callback. */
export interface HotkeyChordRegistrationView extends RegistrationViewBase {
  kind: 'chord';
  hotkey: HotkeyChord;
  parsedChord: ParsedHotkeyChord;
  options: HotkeyChordOptions;
}
/** Public view of a sequence registration, without its callback. */
export interface HotkeySequenceRegistrationView extends RegistrationViewBase {
  kind: 'sequence';
  hotkey: HotkeySequence;
  parsedSequence: Array<ParsedHotkeyChord>;
  options: HotkeySequenceOptions;
  /** Steps matched in the current attempt (0 when idle or just completed). */
  matchedStepCount: number;
  /** `Date.now()` when the last step in the current attempt matched; 0 if none. */
  partialMatchLastKeyTime: number;
}
/**
 * Public view of a hotkey registration for display and introspection.
 * Narrow with `kind` to read chord- or sequence-specific fields.
 */
export type HotkeyRegistrationView = HotkeyChordRegistrationView | HotkeySequenceRegistrationView;
/** Creates a public view from an internal registration, stripping the callback. */
export declare function toHotkeyRegistrationView(reg: HotkeyRegistration): HotkeyRegistrationView;
/**
 * A handle returned from HotkeyManager.register() that allows updating
 * the callback and options without re-registering the hotkey.
 *
 * @example
 * ```ts
 * const handle = manager.register('Mod+S', callback, options)
 *
 * // Update callback without re-registering (avoids stale closures)
 * handle.callback = newCallback
 *
 * // Update options without re-registering
 * handle.setOptions({ enabled: false })
 *
 * // Unregister when done
 * handle.unregister()
 * ```
 */
export interface HotkeyRegistrationHandle<TOptions extends HotkeyOptions = HotkeyChordOptions | HotkeySequenceOptions> {
  /**
   * The callback function. Can be set directly to update without re-registering.
   * This avoids stale closures when the callback references React state.
   */
  callback: HotkeyCallback;
  /** Unique identifier for this registration */
  readonly id: string;
  /** Check if this registration is still active (not unregistered) */
  readonly isActive: boolean;
  /**
   * Update options (merged with existing options).
   * Useful for updating `enabled`, `preventDefault`, etc. without re-registering.
   * Changing `capture` moves the listener phase without clearing `requireReset`
   * state or sequence progress. A registration already processed for an event
   * cannot process it again in another phase.
   */
  setOptions: (options: Partial<TOptions>) => void;
  /** Unregister this hotkey */
  unregister: () => void;
}
/** Active hotkey scopes, published on `HotkeyManager.scopes`. */
export interface HotkeyScopesState {
  /**
   * Activated scopes, lowest precedence first. `'global'` is always active
   * and not listed.
   */
  active: ReadonlyArray<string>;
  /** The exclusive scope that owns the keyboard, or `null` when none is active. */
  exclusive: string | null;
  /**
   * Scopes the exclusive scope allows that are active, possibly including
   * `'global'`. Empty when no scope is exclusive.
   */
  allowed: ReadonlyArray<string>;
  /**
   * Scopes that are active but cannot fire because another scope is
   * exclusive, possibly including `'global'`.
   */
  suspended: ReadonlyArray<string>;
}
/** Options for `HotkeyManager.activateScope`. */
export interface ActivateHotkeyScopeOptions {
  /**
   * Suspend every other scope, including `'global'`, until this activation is
   * released. Scopes nested in this one stay active. Pass `{ allow }` to keep
   * other scopes active too, such as `{ allow: ['global'] }`. An allowed scope
   * still has to be activated, except `'global'`.
   */
  exclusive?: boolean | {
    allow: ReadonlyArray<string>;
  };
  /**
   * The scope this one is nested in. A nested scope takes precedence over its
   * ancestors even when it was activated first, as nested providers are when
   * their effects run child-first. Defaults to `'global'`.
   */
  parent?: string;
}
/**
 * Singleton manager for hotkey registrations, including sequences.
 *
 * This class provides a centralized way to register and manage keyboard
 * hotkeys. It shares one keydown and one keyup listener per target and
 * capture phase across registrations.
 *
 * Sequences (Vim-style hotkeys such as `['G', 'G']`) match when every step is
 * pressed within the timeout. Modifier-only events (Shift, Control, Alt, or
 * Meta alone) neither advance nor reset a sequence, so chained modifier chords
 * such as `['Shift+R', 'Shift+T']` work.
 *
 * @example
 * ```ts
 * const manager = HotkeyManager.getInstance()
 *
 * const save = manager.register('Mod+S', (event, context) => {
 *   console.log('Save triggered!')
 * })
 * const top = manager.register(['G', 'G'], () => scrollToTop(), { timeout: 500 })
 *
 * // Later, to unregister:
 * save.unregister()
 * top.unregister()
 * ```
 */
export declare class HotkeyManager {
  #private;
  /**
   * The TanStack Store containing all hotkey registrations, chords and sequences.
   * Use this to subscribe to registration changes or access current registrations.
   *
   * @example
   * ```ts
   * const manager = HotkeyManager.getInstance()
   *
   * // Subscribe to registration changes
   * const unsubscribe = manager.registrations.subscribe(() => {
   *   console.log('Registrations changed:', manager.registrations.state.size)
   * })
   *
   * // Access current registrations
   * for (const [id, reg] of manager.registrations.state) {
   *   console.log(reg.hotkey, reg.options.enabled)
   * }
   * ```
   */
  readonly registrations: Store<Map<string, HotkeyRegistration>>;
  /**
   * The app hotkeys store published by a framework provider, so tools such as
   * devtools can list declared hotkeys that are not registered.
   */
  readonly appHotkeys: Store<AppHotkeysStore | null>;
  /** Active scopes. Subscribe to show which hotkeys are suspended. */
  readonly scopes: Store<HotkeyScopesState>;
  private constructor();
  /**
   * Gets the singleton instance of HotkeyManager.
   */
  static getInstance(): HotkeyManager;
  /**
   * Resets the singleton instance. Useful for testing.
   */
  static resetInstance(): void;
  /**
   * Activates a scope until the returned function is called. Activations are
   * counted, so a scope stays active until every activation is released.
   * Framework providers call this while mounted.
   *
   * When active scopes share a hotkey, a nested scope takes precedence over
   * its ancestors, and otherwise the most recently activated scope wins. The
   * same order picks the exclusive scope when several are active, and only
   * that scope's `allow` list applies.
   *
   * @example
   * ```ts
   * const release = manager.activateScope('dialog', { exclusive: true })
   * // Only hotkeys with scope 'dialog', or scopes nested in it, fire now.
   * release()
   *
   * manager.activateScope('dialog', { exclusive: { allow: ['global'] } })
   * // Hotkeys with scope 'global' fire too.
   * ```
   */
  activateScope(scope: string, options?: ActivateHotkeyScopeOptions): () => void;
  /**
   * Whether hotkeys in a scope can fire: the scope is `'global'` or
   * activated, and either no scope is exclusive or this scope is the
   * exclusive one, nested in it, or allowed by it.
   */
  isScopeActive(scope?: string): boolean;
  /**
   * Registers a hotkey handler and returns a handle for updating the registration.
   *
   * Pass a chord (`'Mod+S'` or a RawHotkeyChord object) or a sequence (`['G', 'G']`).
   * A one-step sequence registers as its chord. Chords accept `requireReset`;
   * sequences accept `timeout`.
   *
   * The returned handle allows updating the callback and options without
   * re-registering, which is useful for avoiding stale closures in React.
   *
   * @param hotkey - A chord string, RawHotkeyChord object, or sequence
   * @param callback - The function to call when the hotkey is pressed
   * @param options - Options for the hotkey behavior
   * @returns A handle for managing the registration
   *
   * @example
   * ```ts
   * const handle = manager.register('Mod+S', callback)
   *
   * // Update callback without re-registering (avoids stale closures)
   * handle.callback = newCallback
   *
   * // Update options
   * handle.setOptions({ enabled: false })
   *
   * // Unregister when done
   * handle.unregister()
   * ```
   */
  register<const T extends RegisterableHotkey>(hotkey: T, callback: HotkeyCallback<HotkeyCallbackContextFor<T>>, options?: HotkeyOptionsFor<T>): HotkeyRegistrationHandle<HotkeyOptionsFor<T>>;
  /**
   * Resets the progress of every in-progress sequence.
   */
  resetSequenceProgress(): void;
  /**
   * Triggers a registration's callback programmatically from devtools.
   * Creates a synthetic KeyboardEvent from the chord (or a sequence's last step)
   * and invokes the callback.
   *
   * @param id - The registration ID to trigger
   * @returns True if the registration was found and triggered
   */
  triggerRegistration(id: string): boolean;
  /**
   * Gets the number of registered hotkeys, chords and sequences.
   */
  getRegistrationCount(): number;
  /**
   * Checks if a specific hotkey is registered.
   *
   * @param hotkey - The chord or sequence to check
   * @param target - Optional target element to match (if provided, both hotkey and target must match)
   * @returns True if a matching registration exists
   */
  isRegistered(hotkey: RegisterableHotkey, target?: Target): boolean;
  /**
   * Checks whether a hotkey can fire now: at least one registration is
   * enabled, in an active scope, and listening where key presses go. A
   * registration on `document` or `window` always is; one on an element is
   * only while focus is inside that element. An app hotkey matches
   * registrations by id, so custom hotkeys apply.
   *
   * @param hotkey - A chord, sequence, or app hotkey
   * @returns True if a registration that can fire exists
   */
  hasActiveRegistration(hotkey: RegisterableHotkey | AppHotkey): boolean;
  /**
   * Destroys the manager and removes all listeners.
   */
  destroy(): void;
}
/**
 * Gets the singleton HotkeyManager instance.
 * Convenience function for accessing the manager.
 */
export declare function getHotkeyManager(): HotkeyManager;
//#endregion
export type { ConflictBehavior };