/**
 * Gesture Rules — Composable Event System
 *
 * A rule-based gesture resolution engine where ANY combination of
 * (button x modifier x source x gesture x target) can be mapped to any action.
 *
 * Resolution works by scoring each rule's pattern against the current gesture
 * descriptor. The most specific match wins, with automatic fallback to
 * less-specific rules.
 *
 * Types are in ./gesture-rules-types.ts
 * Defaults + labels + merge are in ./gesture-rules-defaults.ts
 */

// Re-export all types for backward compat
export type {
	GestureButton,
	ModifierFlags,
	GesturePhase,
	GestureDescriptor,
	GesturePattern,
	GestureRule,
	ResolvedGesture,
	GestureActionEvent,
	ResolveGestureOptions,
} from './gesture-rules-types';

import type {
	ModifierFlags,
	GestureDescriptor,
	GesturePattern,
	GestureRule,
	GestureRuleIndex,
	ResolvedGesture,
	ResolveGestureOptions,
} from './gesture-rules-types';

import type { GestureType } from './gesture-resolver';

// Re-export defaults, labels, merge for backward compat
export {
	DEFAULT_GESTURE_RULES,
	mergeRules,
	formatRuleLabel,
} from './gesture-rules-defaults';

// Re-export the index type
export type { GestureRuleIndex } from './gesture-rules-types';

// =============================================================================
// Specificity Scoring
// =============================================================================

const MODIFIER_KEYS: (keyof ModifierFlags)[] = ['shift', 'ctrl', 'alt', 'meta'];

/**
 * Calculate how specifically a pattern matches a descriptor.
 *
 * Returns -1 if the pattern does NOT match.
 * Returns 0+ for a match, where higher = more specific.
 *
 * Scoring:
 *   gesture:   +32  (most important dimension)
 *   target:    +16
 *   modifiers: +8 per matching modifier flag
 *   source:    +4
 *   button:    +2
 */
export function matchSpecificity(pattern: GesturePattern, desc: GestureDescriptor): number {
	let score = 0;

	if (pattern.gesture !== undefined) {
		if (pattern.gesture !== desc.gesture) return -1;
		score += 32;
	}
	if (pattern.target !== undefined) {
		if (pattern.target !== desc.target) return -1;
		score += 16;
	}
	if (pattern.source !== undefined) {
		if (pattern.source !== desc.source) return -1;
		score += 4;
	}
	if (pattern.button !== undefined) {
		if (pattern.button !== (desc.button ?? 0)) return -1;
		score += 2;
	}
	if (pattern.modifiers !== undefined) {
		const dm = desc.modifiers ?? {};
		for (const key of MODIFIER_KEYS) {
			const required = pattern.modifiers[key];
			if (required === undefined) continue;
			const actual = dm[key] ?? false;
			if (required !== actual) return -1;
			score += 8;
		}
	}

	return score;
}

// =============================================================================
// Resolver
// =============================================================================

/** Sentinel rule for palm-rejected gestures */
const PALM_REJECTION_RULE: GestureRule = {
	id: '__palm-rejection__',
	pattern: {},
	actionId: 'none',
	label: 'Palm rejection',
};

/**
 * Resolve a gesture descriptor against a rule set.
 *
 * 1. Apply palm rejection (finger + stylus active → remap)
 * 2. Score every rule against the (possibly transformed) descriptor
 * 3. Pick highest specificity; break ties with rule.priority
 * 4. Return resolved action or null if no match
 */
export function resolveGesture(
	desc: GestureDescriptor,
	rules: readonly GestureRule[],
	options?: ResolveGestureOptions,
): ResolvedGesture | null {
	const palmRejection = options?.palmRejection !== false;

	// Palm rejection: when stylus is active, finger touches become
	// navigation-only (taps → none, drags on nodes → pan)
	if (palmRejection && desc.isStylusActive && desc.source === 'finger') {
		if (desc.gesture === 'tap' || desc.gesture === 'long-press' || desc.gesture === 'double-tap' || desc.gesture === 'triple-tap') {
			return { actionId: 'none', rule: PALM_REJECTION_RULE, score: Infinity };
		}
		if (desc.gesture === 'drag' && desc.target !== 'background') {
			// Re-resolve as background pan
			return resolveGesture(
				{ ...desc, target: 'background', isStylusActive: false },
				rules,
				{ palmRejection: false },
			);
		}
	}

	let best: ResolvedGesture | null = null;

	for (const rule of rules) {
		const specificity = matchSpecificity(rule.pattern, desc);
		if (specificity < 0) continue;

		// Combine specificity with priority for final score
		const effectiveScore = specificity * 1000 + (rule.priority ?? 0);
		if (!best || effectiveScore > best.score) {
			best = { actionId: rule.actionId, rule, score: effectiveScore };
		}
	}

	return best;
}

// =============================================================================
// Rule Index (for fast lookup)
// =============================================================================

export function buildRuleIndex(rules: readonly GestureRule[]): GestureRuleIndex {
	const buckets = new Map<string, GestureRule[]>();
	const wildcardRules: GestureRule[] = [];

	for (const rule of rules) {
		const key = rule.pattern.gesture;
		if (key === undefined) {
			wildcardRules.push(rule);
		} else {
			const bucket = buckets.get(key);
			if (bucket) {
				bucket.push(rule);
			} else {
				buckets.set(key, [rule]);
			}
		}
	}

	// Pre-concatenate wildcard rules into each gesture bucket
	const index: GestureRuleIndex = new Map();
	if (wildcardRules.length > 0) {
		for (const [key, bucket] of buckets) {
			index.set(key as GestureType, bucket.concat(wildcardRules));
		}
		index.set('__wildcard__', wildcardRules);
	} else {
		for (const [key, bucket] of buckets) {
			index.set(key as GestureType, bucket);
		}
	}

	return index;
}

/**
 * Resolve using an indexed rule set (faster for large rule sets).
 */
export function resolveGestureIndexed(
	desc: GestureDescriptor,
	index: GestureRuleIndex,
	options?: ResolveGestureOptions,
): ResolvedGesture | null {
	const rules = index.get(desc.gesture) ?? index.get('__wildcard__') ?? [];
	return resolveGesture(desc, rules, options);
}
