// Lightweight i18n primitive used by the login form, the root
// layout, and the lobby placeholder. The translation table is a
// per-locale flat dictionary keyed by dotted strings; lookup falls
// back to the default (English) locale when a key is missing.
//
// Adding a new language is a two-step change inside this folder:
//   1. drop a `locales/<bcp47>.ts` file mirroring the shape of
//      `locales/en.ts` (TypeScript enforces matching keys via the
//      `Record<keyof typeof en, string>` annotation in `ru.ts`);
//   2. register the file in `SUPPORTED_LOCALES` below — that single
//      list drives the language picker UI and the runtime lookup
//      table at the same time.
//
// The locale state is exposed through a Svelte 5 runes singleton
// (`i18n`) so components stay reactive without ceremony:
// `<p>{i18n.t('login.title')}</p>` re-renders whenever
// `i18n.locale` changes. Phase 35 will swap this primitive for a
// fuller solution once message-format pluralisation and lazy
// loading become necessary.

import enTranslations from "./locales/en";
import ruTranslations from "./locales/ru";

export type Locale = "en" | "ru";
export type TranslationKey = keyof typeof enTranslations;

export interface LocaleEntry {
	readonly code: Locale;
	readonly nativeName: string;
	readonly translations: Readonly<Record<TranslationKey, string>>;
}

export const SUPPORTED_LOCALES: readonly LocaleEntry[] = [
	{
		code: "en",
		nativeName: "English",
		translations: enTranslations,
	},
	{
		code: "ru",
		nativeName: "Русский",
		translations: ruTranslations,
	},
];

export const DEFAULT_LOCALE: Locale = "en";

const TRANSLATIONS_BY_LOCALE: Record<
	Locale,
	Readonly<Record<TranslationKey, string>>
> = SUPPORTED_LOCALES.reduce(
	(acc, entry) => {
		acc[entry.code] = entry.translations;
		return acc;
	},
	{} as Record<Locale, Readonly<Record<TranslationKey, string>>>,
);

/**
 * detectInitialLocale returns the best supported locale match for
 * the supplied BCP 47 preference list. The web target passes
 * `navigator.languages`; native wrappers pass the system locale
 * (one entry). The first preference whose primary subtag matches
 * a `SUPPORTED_LOCALES` entry wins; otherwise [DEFAULT_LOCALE].
 */
export function detectInitialLocale(
	preferences?: readonly string[],
): Locale {
	const prefs = preferences ?? readBrowserPreferences();
	for (const tag of prefs) {
		const primary = primarySubtag(tag);
		if (primary === null) {
			continue;
		}
		const found = SUPPORTED_LOCALES.find((entry) => entry.code === primary);
		if (found !== undefined) {
			return found.code;
		}
	}
	return DEFAULT_LOCALE;
}

function readBrowserPreferences(): readonly string[] {
	if (typeof navigator === "undefined") {
		return [];
	}
	if (Array.isArray(navigator.languages) && navigator.languages.length > 0) {
		return navigator.languages;
	}
	if (typeof navigator.language === "string" && navigator.language !== "") {
		return [navigator.language];
	}
	return [];
}

function primarySubtag(tag: string): Locale | null {
	const trimmed = tag.trim().toLowerCase();
	if (trimmed.length === 0) {
		return null;
	}
	const code = trimmed.split(/[-_]/)[0] ?? "";
	return isLocale(code) ? code : null;
}

function isLocale(value: string): value is Locale {
	return SUPPORTED_LOCALES.some((entry) => entry.code === value);
}

class I18nStore {
	locale: Locale = $state(detectInitialLocale());

	/**
	 * setLocale changes the active locale. Components reading
	 * `i18n.t(...)` re-render automatically through the rune.
	 */
	setLocale(next: Locale): void {
		this.locale = next;
	}

	/**
	 * t looks up `key` in the active locale, falling back to the
	 * default locale when the key is missing. `params` is an optional
	 * `{name -> value}` map; placeholders in the template (`{name}`)
	 * are replaced literally with no escaping — callers are expected
	 * to feed user-safe values.
	 */
	t(key: TranslationKey, params?: Record<string, string>): string {
		const active = TRANSLATIONS_BY_LOCALE[this.locale];
		const fallback = TRANSLATIONS_BY_LOCALE[DEFAULT_LOCALE];
		const template = active[key] ?? fallback[key] ?? key;
		if (params === undefined) {
			return template;
		}
		return template.replace(/\{(\w+)\}/g, (match, name: string) => {
			const value = params[name];
			return value === undefined ? match : value;
		});
	}

	/**
	 * resetForTests forces the singleton back to its module-load
	 * state. Production code never calls this; the Vitest harness
	 * uses it to keep cases independent.
	 */
	resetForTests(initial: Locale = detectInitialLocale()): void {
		this.locale = initial;
	}
}

export const i18n = new I18nStore();
