import { describe, it, expect } from 'vitest';
import { specificity } from '../specificity';
import type { GestureEvent, GesturePattern, InputPattern, KeyInputEvent } from '../types';
import { NO_HELD_KEYS, NO_MODIFIERS } from '../types';

function makeEvent(overrides: Partial<GestureEvent> = {}): GestureEvent {
	return {
		type: 'tap',
		phase: 'instant',
		subject: { kind: 'node', nodeId: 'n1' },
		source: 'mouse',
		button: 0,
		modifiers: NO_MODIFIERS,
		heldKeys: NO_HELD_KEYS,
		screenPosition: { x: 0, y: 0 },
		worldPosition: { x: 0, y: 0 },
		...overrides,
	};
}

function makeKeyEvent(overrides: Partial<KeyInputEvent> = {}): KeyInputEvent {
	return {
		kind: 'key',
		phase: 'down',
		key: 'a',
		code: 'KeyA',
		repeat: false,
		modifiers: NO_MODIFIERS,
		heldKeys: NO_HELD_KEYS,
		subject: { kind: 'background' },
		...overrides,
	};
}

describe('specificity', () => {
	it('empty pattern matches anything with score 0', () => {
		expect(specificity({}, makeEvent())).toBe(0);
	});

	it('type match adds 128', () => {
		expect(specificity({ type: 'tap' }, makeEvent())).toBe(128);
	});

	it('type mismatch returns -1', () => {
		expect(specificity({ type: 'drag' }, makeEvent())).toBe(-1);
	});

	it('subjectKind match adds 32', () => {
		expect(specificity({ subjectKind: 'node' }, makeEvent())).toBe(32);
	});

	it('subjectKind mismatch returns -1', () => {
		expect(specificity({ subjectKind: 'background' }, makeEvent())).toBe(-1);
	});

	it('source match adds 4', () => {
		expect(specificity({ source: 'mouse' }, makeEvent())).toBe(4);
	});

	it('source mismatch returns -1', () => {
		expect(specificity({ source: 'finger' }, makeEvent())).toBe(-1);
	});

	it('button match adds 2', () => {
		expect(specificity({ button: 0 }, makeEvent())).toBe(2);
	});

	it('button mismatch returns -1', () => {
		expect(specificity({ button: 2 }, makeEvent())).toBe(-1);
	});

	it('positive modifier adds 16', () => {
		const event = makeEvent({ modifiers: { ...NO_MODIFIERS, shift: true } });
		expect(specificity({ modifiers: { shift: true } }, event)).toBe(16);
	});

	it('negative modifier adds 8', () => {
		const event = makeEvent();
		expect(specificity({ modifiers: { shift: false } }, event)).toBe(8);
	});

	it('modifier mismatch returns -1', () => {
		const event = makeEvent();
		expect(specificity({ modifiers: { shift: true } }, event)).toBe(-1);
	});

	it('multiple positive modifiers stack', () => {
		const event = makeEvent({ modifiers: { shift: true, ctrl: true, alt: false, meta: false } });
		expect(specificity({ modifiers: { shift: true, ctrl: true } }, event)).toBe(32);
	});

	it('full pattern scores sum', () => {
		const event = makeEvent({
			type: 'drag',
			subject: { kind: 'background' },
			source: 'finger',
			button: 0,
			modifiers: { shift: true, ctrl: false, alt: false, meta: false },
		});
		const pattern: GesturePattern = {
			type: 'drag',       // 128
			subjectKind: 'background',   // 32
			source: 'finger',   // 4
			button: 0,          // 2
			modifiers: { shift: true },  // 16
		};
		expect(specificity(pattern, event)).toBe(128 + 32 + 4 + 2 + 16);
	});

	it('type alone (128) beats all modifiers + target + source + button (102)', () => {
		// Ensure type weight is sufficient
		expect(128).toBeGreaterThan(32 + 64 + 4 + 2); // 102
	});

	it('positive modifier (16) beats negative modifier (8)', () => {
		const eventShiftDown = makeEvent({ modifiers: { ...NO_MODIFIERS, shift: true } });
		const positive = specificity({ modifiers: { shift: true } }, eventShiftDown);
		const eventNoShift = makeEvent();
		const negative = specificity({ modifiers: { shift: false } }, eventNoShift);
		expect(positive).toBeGreaterThan(negative);
	});

	it('key binding scores 64', () => {
		expect(specificity({ kind: 'key', key: 'a' }, makeKeyEvent())).toBe(64);
	});

	it('code binding scores 64', () => {
		expect(specificity({ kind: 'key', code: 'KeyA' }, makeKeyEvent())).toBe(64);
	});

	it('key and code both must match', () => {
		const pattern: InputPattern = { kind: 'key', key: 'a', code: 'KeyA' };
		expect(specificity(pattern, makeKeyEvent())).toBe(128);
		expect(specificity(pattern, makeKeyEvent({ code: 'KeyB' }))).toBe(-1);
	});

	it('held-key positive scores 16', () => {
		const event = makeEvent({ heldKeys: { byKey: { ' ': true }, byCode: { Space: true } } });
		expect(specificity({ heldKeys: { byCode: { Space: true } } }, event)).toBe(16);
	});

	it('held-key negative scores 8', () => {
		expect(specificity({ heldKeys: { byCode: { Space: false } } }, makeEvent())).toBe(8);
	});

	it('held-key mismatch returns -1', () => {
		expect(
			specificity(
				{ heldKeys: { byCode: { Space: true } } },
				makeEvent({ heldKeys: { byCode: {}, byKey: {} } }),
			),
		).toBe(-1);
	});
});
