// `ToastStore` is the single transient-notification primitive for the
// SvelteKit shell. Phase 24 ships it together with the push-event
// dispatch: the per-game layout shows one `Turn N is ready. View now.`
// toast on a verified `game.turn.ready` event. Later phases reuse the
// same store for mail / battle / lobby toasts (PLAN.md §"cross-cutting
// shell").
//
// The store keeps **one** active toast at a time: a fresh `show()`
// replaces the previous descriptor. This matches the UX intent of
// "one loud notification at a time" — the rare cases where several
// events arrive in quick succession are still observable, because
// each replacement re-arms the timer and the user sees every payload
// in turn.

import type { TranslationKey } from "./i18n/index.svelte";

/**
 * ToastDescriptor describes one toast in flight. `messageKey` and
 * `actionLabelKey` are typed against the i18n catalog so a missing
 * translation key fails at compile time. `durationMs === null` (or
 * `undefined`) makes the toast sticky — the user must dismiss it
 * through the action button or another `show()` call.
 */
export interface ToastDescriptor {
	id: string;
	messageKey: TranslationKey;
	messageParams?: Record<string, string>;
	actionLabelKey?: TranslationKey;
	onAction?: () => void;
	durationMs?: number | null;
}

class ToastStore {
	current: ToastDescriptor | null = $state(null);

	private timer: ReturnType<typeof setTimeout> | null = null;
	private counter = 0;

	/**
	 * show replaces the active toast with descriptor and returns its
	 * fresh id. Pass that id to `dismiss(id)` from a delayed callback
	 * to avoid dismissing a newer toast that already took its slot.
	 */
	show(descriptor: Omit<ToastDescriptor, "id">): string {
		this.clearTimer();
		this.counter += 1;
		const id = String(this.counter);
		const full: ToastDescriptor = { ...descriptor, id };
		this.current = full;
		if (
			full.durationMs !== null &&
			full.durationMs !== undefined &&
			full.durationMs > 0
		) {
			const duration = full.durationMs;
			this.timer = setTimeout(() => {
				this.dismiss(id);
			}, duration);
		}
		return id;
	}

	/**
	 * dismiss clears the active toast. With an id, the call is a
	 * no-op when the active toast has a different id — this guards
	 * the auto-dismiss timer from clobbering a newer descriptor.
	 */
	dismiss(id?: string): void {
		if (
			id !== undefined &&
			(this.current === null || this.current.id !== id)
		) {
			return;
		}
		this.clearTimer();
		this.current = null;
	}

	/**
	 * resetForTests forgets every in-flight descriptor and the id
	 * counter. Production code never calls this.
	 */
	resetForTests(): void {
		this.clearTimer();
		this.current = null;
		this.counter = 0;
	}

	private clearTimer(): void {
		if (this.timer !== null) {
			clearTimeout(this.timer);
			this.timer = null;
		}
	}
}

export const toast = new ToastStore();
