import { describe, it, expect, beforeEach, vi } from 'vitest';
import { registerAction, unregisterAction, getHandler, clearHandlers, dispatch } from '../gestures/dispatcher';
import type { InputEvent, ResolvedAction } from '../gestures/types';
import { NO_MODIFIERS } from '../gestures/types';

const EMPTY_HELD: any = { byKey: {}, byCode: {} };

function makePointerEvent(phase: string): InputEvent {
	return {
		kind: 'pointer',
		type: 'tap',
		phase,
		source: 'mouse',
		button: 0,
		modifiers: NO_MODIFIERS,
		heldKeys: EMPTY_HELD,
		screenX: 0, screenY: 0, worldX: 0, worldY: 0,
		subject: { kind: 'background' },
	} as any;
}

function makeKeyEvent(phase: string): InputEvent {
	return {
		kind: 'key',
		phase,
		key: 'a',
		code: 'KeyA',
		modifiers: NO_MODIFIERS,
		heldKeys: EMPTY_HELD,
	} as any;
}

function makeResolution(actionId: string): ResolvedAction {
	return {
		actionId,
		binding: { id: 'test', pattern: {}, actionId } as any,
		contextId: 'test',
		score: 128,
		consumed: false,
	};
}

describe('gesture dispatcher', () => {
	beforeEach(() => {
		clearHandlers();
	});

	describe('registry', () => {
		it('registers and retrieves a handler', () => {
			const handler = vi.fn();
			registerAction('test-action', handler);
			expect(getHandler('test-action')).toBe(handler);
		});

		it('unregisters a handler', () => {
			registerAction('test-action', vi.fn());
			unregisterAction('test-action');
			expect(getHandler('test-action')).toBeUndefined();
		});

		it('clearHandlers removes all', () => {
			registerAction('a1', vi.fn());
			registerAction('a2', vi.fn());
			clearHandlers();
			expect(getHandler('a1')).toBeUndefined();
			expect(getHandler('a2')).toBeUndefined();
		});
	});

	describe('dispatch', () => {
		it('none action returns true without invoking', () => {
			const result = dispatch(makePointerEvent('instant'), makeResolution('none'));
			expect(result).toBe(true);
		});

		it('unknown action returns false', () => {
			const result = dispatch(makePointerEvent('instant'), makeResolution('unknown'));
			expect(result).toBe(false);
		});

		it('simple handler fires on instant', () => {
			const handler = vi.fn();
			registerAction('fire', handler);
			dispatch(makePointerEvent('instant'), makeResolution('fire'));
			expect(handler).toHaveBeenCalledTimes(1);
		});

		it('simple handler fires on start', () => {
			const handler = vi.fn();
			registerAction('fire', handler);
			dispatch(makePointerEvent('start'), makeResolution('fire'));
			expect(handler).toHaveBeenCalledTimes(1);
		});

		it('simple handler does NOT fire on move', () => {
			const handler = vi.fn();
			registerAction('fire', handler);
			dispatch(makePointerEvent('move'), makeResolution('fire'));
			expect(handler).not.toHaveBeenCalled();
		});

		it('simple handler fires on key down', () => {
			const handler = vi.fn();
			registerAction('fire', handler);
			dispatch(makeKeyEvent('down'), makeResolution('fire'));
			expect(handler).toHaveBeenCalledTimes(1);
		});

		it('phase-aware handler routes to onStart', () => {
			const onStart = vi.fn();
			registerAction('phased', { onStart });
			dispatch(makePointerEvent('start'), makeResolution('phased'));
			expect(onStart).toHaveBeenCalledTimes(1);
		});

		it('phase-aware handler routes to onEnd', () => {
			const onEnd = vi.fn();
			registerAction('phased', { onEnd });
			dispatch(makePointerEvent('end'), makeResolution('phased'));
			expect(onEnd).toHaveBeenCalledTimes(1);
		});
	});
});
