/**
 * Layer 3: Specificity Scoring
 *
 * Scores how well an InputPattern matches an InputEvent.
 * Higher score = more specific match. Returns -1 on mismatch.
 *
 * Score weights:
 *   type           128   (must outweigh all others combined)
 *   subjectKind     32
 *   modifier+       16   (per positive flag)
 *   modifier-        8   (per negative flag)
 *   source           4
 *   button           2
 */

import type {
	HeldKeysPattern,
	HeldKeysState,
	InputEvent,
	InputPattern,
	Modifiers,
} from './types';
import { isKeyInputEvent, isPointerGestureEvent } from './types';

const SCORE_TYPE = 128;
const SCORE_KEY = 64;
const SCORE_CODE = 64;
const SCORE_SUBJECT = 32;
const SCORE_MODIFIER_POSITIVE = 16;
const SCORE_MODIFIER_NEGATIVE = 8;
const SCORE_HELD_KEY_POSITIVE = 16;
const SCORE_HELD_KEY_NEGATIVE = 8;
const SCORE_SOURCE = 4;
const SCORE_BUTTON = 2;

/**
 * Compute the specificity score of a pattern against an event.
 * Returns -1 if the pattern does not match.
 */
export function specificity(pattern: InputPattern, event: InputEvent): number {
	let score = 0;

	// kind
	if (pattern.kind !== undefined) {
		const actualKind = isKeyInputEvent(event) ? 'key' : 'pointer';
		if (pattern.kind !== actualKind) return -1;
	}

	// type
	if (pattern.type !== undefined) {
		if (!isPointerGestureEvent(event) || pattern.type !== event.type) return -1;
		score += SCORE_TYPE;
	}

	// subjectKind
	if (pattern.subjectKind !== undefined) {
		if (event.subject === undefined || pattern.subjectKind !== event.subject.kind) return -1;
		score += SCORE_SUBJECT;
	}

	// phase
	if (pattern.phase !== undefined && pattern.phase !== event.phase) {
		return -1;
	}

	// source
	if (pattern.source !== undefined) {
		if (!isPointerGestureEvent(event)) return -1;
		if (pattern.source !== event.source) return -1;
		score += SCORE_SOURCE;
	}

	// button
	if (pattern.button !== undefined) {
		if (!isPointerGestureEvent(event)) return -1;
		if (pattern.button !== event.button) return -1;
		score += SCORE_BUTTON;
	}

	// key / code
	if (pattern.key !== undefined) {
		if (!isKeyInputEvent(event) || pattern.key !== event.key) return -1;
		score += SCORE_KEY;
	}

	if (pattern.code !== undefined) {
		if (!isKeyInputEvent(event) || pattern.code !== event.code) return -1;
		score += SCORE_CODE;
	}

	// modifiers
	if (pattern.modifiers !== undefined) {
		const ms = modifierScore(pattern.modifiers, event.modifiers);
		if (ms === -1) return -1;
		score += ms;
	}

	// held keys
	if (pattern.heldKeys !== undefined) {
		const hs = heldKeyScore(pattern.heldKeys, event.heldKeys);
		if (hs === -1) return -1;
		score += hs;
	}

	return score;
}

/**
 * Score modifier pattern against actual modifier state.
 * Each explicit true flag adds SCORE_MODIFIER_POSITIVE.
 * Each explicit false flag adds SCORE_MODIFIER_NEGATIVE.
 * Returns -1 if any constraint is violated.
 */
function modifierScore(
	pattern: Partial<Modifiers>,
	actual: Modifiers,
): number {
	let score = 0;
	const keys = ['shift', 'ctrl', 'alt', 'meta'] as const;

	for (const key of keys) {
		const required = pattern[key];
		if (required === undefined) continue;

		if (required !== actual[key]) return -1;

		score += required ? SCORE_MODIFIER_POSITIVE : SCORE_MODIFIER_NEGATIVE;
	}

	if (pattern.custom) {
		const actualCustom = actual.custom ?? {};
		for (const [key, required] of Object.entries(pattern.custom)) {
			const actualVal = actualCustom[key] ?? false;
			if (required !== actualVal) return -1;
			score += required ? SCORE_MODIFIER_POSITIVE : SCORE_MODIFIER_NEGATIVE;
		}
	}

	return score;
}

function heldKeyScore(pattern: HeldKeysPattern, actual: HeldKeysState): number {
	let score = 0;

	for (const [key, required] of Object.entries(pattern.byKey ?? {})) {
		const actualVal = actual.byKey[key] ?? false;
		if (required !== actualVal) return -1;
		score += required ? SCORE_HELD_KEY_POSITIVE : SCORE_HELD_KEY_NEGATIVE;
	}

	for (const [code, required] of Object.entries(pattern.byCode ?? {})) {
		const actualVal = actual.byCode[code] ?? false;
		if (required !== actualVal) return -1;
		score += required ? SCORE_HELD_KEY_POSITIVE : SCORE_HELD_KEY_NEGATIVE;
	}

	return score;
}
