import { useEffect } from 'react';

import { type Keybind, matchKeybind, parseKeybind } from '@mary/keybinds';

import { useNonReactiveCallback } from '#/lib/hooks/use-non-reactive-callback';

/** callback run when a registered keybind matches. */
type KeybindHandler = (ev: KeyboardEvent) => void;

/** layer a keybind belongs to. only the highest-precedence active scope runs its keybinds. */
export type KeybindScope = 'app' | 'dialog' | 'drawer';

const SCOPE_PRECEDENCE: Record<KeybindScope, number> = {
	app: 0,
	drawer: 1,
	dialog: 2,
};

interface Registration {
	scope: KeybindScope;
	keybind: Keybind;
	handle: KeybindHandler;
}

const registrations = new Set<Registration>();
const activeScopes = new Set<KeybindScope>();

const getActiveScope = (): KeybindScope => {
	let active: KeybindScope = 'app';

	for (const scope of activeScopes) {
		if (SCOPE_PRECEDENCE[scope] > SCOPE_PRECEDENCE[active]) {
			active = scope;
		}
	}

	return active;
};

window.addEventListener('keydown', (ev) => {
	if (ev.defaultPrevented) {
		return;
	}

	const scope = getActiveScope();

	for (const registration of registrations) {
		if (registration.scope === scope && matchKeybind(ev, registration.keybind)) {
			ev.preventDefault();
			registration.handle(ev);
			break;
		}
	}
});

/**
 * marks a scope as claiming the keyboard.
 *
 * @param scope the scope to switch
 * @param active whether the surface owning that scope is currently showing
 */
export function setKeybindScopeActive(scope: KeybindScope, active: boolean): void {
	if (active) {
		activeScopes.add(scope);
	} else {
		activeScopes.delete(scope);
	}
}

interface UseKeybindOptions {
	scope: KeybindScope;
	/** whether the keybind is enabled; a disabled one is left unregistered, so the page keeps the key */
	enabled?: boolean;
	/** keybind string */
	keybind: string;
	/** keybind handler */
	handle: KeybindHandler;
}

/**
 * registers a keybind.
 *
 * @param options the keybind to register
 * @throws {TypeError} if `keybind` names an unknown modifier
 */
export function useKeybind({ scope, keybind, handle, enabled = true }: UseKeybindOptions): void {
	const stableHandle = useNonReactiveCallback(handle);

	useEffect(() => {
		if (!enabled) {
			return;
		}

		const registration: Registration = {
			scope,
			keybind: parseKeybind(keybind),
			handle: stableHandle,
		};

		registrations.add(registration);

		return () => {
			registrations.delete(registration);
		};
	}, [enabled, keybind, scope, stableHandle]);
}
