import { describe, it, expect } from 'vitest';
import { indexContext, buildMappingIndex, resolve } from '../gestures/mapper';
import type { MappingContext, Binding, InputEvent, GuardContext } from '../gestures/types';
import { NO_MODIFIERS } from '../gestures/types';

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

function makeContext(id: string, priority: number, bindings: Binding[]): MappingContext {
	return { id, priority, enabled: true, bindings };
}

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

const GUARD: GuardContext = {
	isStylusActive: false,
	isDragging: false,
	isSplitting: false,
	commandLineVisible: false,
	isSearchActive: false,
	keyboardInteractionMode: 'none',
	custom: {},
} as any;

describe('gesture mapper', () => {
	describe('indexContext', () => {
		it('buckets bindings by type', () => {
			const ctx = makeContext('test', 100, [
				{ id: 'b1', pattern: { type: 'tap' }, actionId: 'a1' },
				{ id: 'b2', pattern: { type: 'drag' }, actionId: 'a2' },
			]);
			const idx = indexContext(ctx);
			expect(idx.buckets.has('pointer:tap')).toBe(true);
			expect(idx.buckets.has('pointer:drag')).toBe(true);
		});

		it('preserves context metadata', () => {
			const ctx = makeContext('my-ctx', 42, []);
			const idx = indexContext(ctx);
			expect(idx.contextId).toBe('my-ctx');
			expect(idx.priority).toBe(42);
		});
	});

	describe('buildMappingIndex', () => {
		it('sorts by priority ascending', () => {
			const c1 = makeContext('low', 100, []);
			const c2 = makeContext('high', 10, []);
			const index = buildMappingIndex([c1, c2]);
			expect(index[0].contextId).toBe('high');
			expect(index[1].contextId).toBe('low');
		});
	});

	describe('resolve', () => {
		it('matches a simple binding', () => {
			const ctx = makeContext('test', 100, [
				{ id: 'b1', pattern: { type: 'tap', subjectKind: 'node' }, actionId: 'select-node' },
			]);
			const index = buildMappingIndex([ctx]);
			const event = makeTapEvent('node');
			const result = resolve(event, index, GUARD);
			expect(result).not.toBeNull();
			expect(result!.actionId).toBe('select-node');
		});

		it('returns null on no match', () => {
			const ctx = makeContext('test', 100, [
				{ id: 'b1', pattern: { type: 'drag' }, actionId: 'move' },
			]);
			const index = buildMappingIndex([ctx]);
			const event = makeTapEvent('background');
			const result = resolve(event, index, GUARD);
			expect(result).toBeNull();
		});

		it('higher-priority context wins', () => {
			const high = makeContext('high', 10, [
				{ id: 'h1', pattern: { type: 'tap', subjectKind: 'node' }, actionId: 'high-action' },
			]);
			const low = makeContext('low', 100, [
				{ id: 'l1', pattern: { type: 'tap', subjectKind: 'node' }, actionId: 'low-action' },
			]);
			const index = buildMappingIndex([low, high]);
			const event = makeTapEvent('node');
			const result = resolve(event, index, GUARD);
			expect(result!.actionId).toBe('high-action');
		});

		it('consumeInput returns immediately', () => {
			const c1 = makeContext('consumer', 10, [
				{ id: 'c1', pattern: { type: 'tap', subjectKind: 'node' }, actionId: 'consumed', consumeInput: true },
			]);
			const c2 = makeContext('fallback', 100, [
				{ id: 'f1', pattern: { type: 'tap', subjectKind: 'node' }, actionId: 'fallback' },
			]);
			const index = buildMappingIndex([c1, c2]);
			const result = resolve(makeTapEvent('node'), index, GUARD);
			expect(result!.consumed).toBe(true);
			expect(result!.actionId).toBe('consumed');
		});

		it('respects when guard', () => {
			const ctx = makeContext('guarded', 100, [
				{ id: 'g1', pattern: { type: 'tap' }, actionId: 'guarded-action', when: () => false },
			]);
			const index = buildMappingIndex([ctx]);
			const result = resolve(makeTapEvent('background'), index, GUARD);
			expect(result).toBeNull();
		});
	});
});
