/**
 * Timed State Runner
 *
 * Timer bridge that wraps the pure TimedState transitions with actual
 * setTimeout/clearTimeout. Each pointer gets its own runner.
 *
 * The runner is imperative on purpose — it's managed by refs in hooks,
 * not by React state. This avoids re-renders on timer state changes.
 */

import { transition, IDLE, type TimedEvent, type TimedConfig, DEFAULT_LONG_PRESS_MS, DEFAULT_MULTI_TAP_WINDOW_MS } from './timed-state';
import type { GestureType, TimedState } from './types';

export class TimedStateRunner {
	private state: TimedState = IDLE;
	private timers = new Map<string, ReturnType<typeof setTimeout>>();
	private config: TimedConfig;

	/** Called when the state machine emits a gesture (tap, double-tap, long-press, etc.) */
	onEmit: ((gestureType: GestureType) => void) | null = null;

	constructor(config?: Partial<TimedConfig>) {
		this.config = {
			longPressMs: config?.longPressMs ?? DEFAULT_LONG_PRESS_MS,
			multiTapWindowMs: config?.multiTapWindowMs ?? DEFAULT_MULTI_TAP_WINDOW_MS,
		};
	}

	/**
	 * Feed a lifecycle event into the state machine.
	 * Returns the emitted gesture type if any (synchronous emit only).
	 * Timer-based emits are delivered asynchronously via `onEmit`.
	 */
	feed(event: 'down' | 'up' | 'move-beyond-threshold' | 'cancel'): GestureType | undefined {
		return this.apply(event);
	}

	/** Current state tag (for debugging/testing). */
	get tag(): TimedState['tag'] {
		return this.state.tag;
	}

	/** Destroy: cancel all pending timers. */
	destroy(): void {
		for (const timer of this.timers.values()) {
			clearTimeout(timer);
		}
		this.timers.clear();
		this.state = IDLE;
		this.onEmit = null;
	}

	private apply(event: TimedEvent): GestureType | undefined {
		const result = transition(this.state, event, this.config);
		this.state = result.state;

		if (result.cancelTimer) {
			const timer = this.timers.get(result.cancelTimer);
			if (timer !== undefined) {
				clearTimeout(timer);
				this.timers.delete(result.cancelTimer);
			}
		}

		if (result.scheduleTimer) {
			const { id, delayMs } = result.scheduleTimer;
			// Clear any existing timer with this id
			const existing = this.timers.get(id);
			if (existing !== undefined) {
				clearTimeout(existing);
			}
			this.timers.set(
				id,
				setTimeout(() => {
					this.timers.delete(id);
					const timerEvent = `timer:${id}` as TimedEvent;
					const timerResult = transition(this.state, timerEvent, this.config);
					this.state = timerResult.state;
					if (timerResult.emit) {
						this.onEmit?.(timerResult.emit);
					}
				}, delayMs),
			);
		}

		return result.emit;
	}
}
