// i18n/index.ts — Internationalization system for Planu
// Loads message bundles from JSON files at runtime and provides
// a t() translation function with dot-notation key lookup.
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
// ---------------------------------------------------------------------------
// Internal state
// ---------------------------------------------------------------------------
const SUPPORTED_LOCALES = ['en', 'es', 'pt'];
let currentLocale = 'en';
/** Locale -> flattened key->value map */
const messageCache = new Map();
// ---------------------------------------------------------------------------
// Message loading
// ---------------------------------------------------------------------------
/**
 * Resolve the absolute path to the messages directory.
 * Works regardless of whether we are running from `src/` (ts-node / dev)
 * or from `dist/` (compiled).  The JSON files always live under
 * `src/i18n/messages/` and are NOT copied by tsc — so we resolve
 * relative to the *source* location via import.meta.url.
 */
function messagesDir() {
    const thisFile = fileURLToPath(import.meta.url);
    const thisDir = dirname(thisFile);
    // When compiled, thisDir = <root>/dist/i18n — messages live in src/i18n/messages.
    // When running from source, thisDir = <root>/src/i18n — messages live in ./messages.
    // We try both locations.
    const srcPath = join(thisDir, 'messages');
    const distFallback = join(thisDir, '..', '..', 'src', 'i18n', 'messages');
    try {
        // Quick existence check
        readFileSync(join(srcPath, 'en.json'), 'utf-8');
        return srcPath;
    }
    catch {
        return distFallback;
    }
}
/**
 * Flatten a nested object into dot-notation keys.
 * { a: { b: "x" } } => { "a.b": "x" }
 */
function flatten(obj, prefix = '') {
    const result = {};
    for (const [key, value] of Object.entries(obj)) {
        const fullKey = prefix ? `${prefix}.${key}` : key;
        if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
            Object.assign(result, flatten(value, fullKey));
        }
        else {
            result[fullKey] = String(value);
        }
    }
    return result;
}
/**
 * Load and cache messages for a locale. Returns the flattened map.
 */
function loadMessages(locale) {
    const cached = messageCache.get(locale);
    if (cached) {
        return cached;
    }
    const dir = messagesDir();
    try {
        const raw = readFileSync(join(dir, `${locale}.json`), 'utf-8');
        const parsed = JSON.parse(raw);
        const flattened = flatten(parsed);
        messageCache.set(locale, flattened);
        return flattened;
    }
    catch {
        // If the locale file is missing, return an empty map.
        // The t() function will fall back to "en".
        const empty = {};
        messageCache.set(locale, empty);
        return empty;
    }
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Get the currently active locale.
 */
export function getLocale() {
    return currentLocale;
}
/**
 * Set the active locale. If the locale is not supported, it stays unchanged.
 */
export function setLocale(locale) {
    if (SUPPORTED_LOCALES.includes(locale)) {
        currentLocale = locale;
    }
}
/**
 * Check whether a locale string is a valid SupportedLocale.
 */
export function isSupportedLocale(value) {
    return SUPPORTED_LOCALES.includes(value);
}
/**
 * Return the list of supported locale codes.
 */
export function getSupportedLocales() {
    return [...SUPPORTED_LOCALES];
}
/**
 * Translate a dot-notation key to a localized string.
 *
 * @param key    Dot-notation key, e.g. "tools.create_spec.success"
 * @param locale Optional locale override (defaults to the current global locale)
 * @returns      The translated string, or the key itself if not found in any locale.
 *
 * Interpolation: use `{placeholder}` in message strings. Pass values via
 * the `t()` return and then call `.replace()` — or use the `ti()` helper.
 */
export function t(key, locale) {
    const targetLocale = locale ?? currentLocale;
    const messages = loadMessages(targetLocale);
    if (messages[key] !== undefined) {
        return messages[key];
    }
    // Fallback to English
    if (targetLocale !== 'en') {
        const enMessages = loadMessages('en');
        if (enMessages[key] !== undefined) {
            return enMessages[key];
        }
    }
    // Key not found — return the key itself as a last resort
    return key;
}
// ---------------------------------------------------------------------------
// Language detection
// ---------------------------------------------------------------------------
/** High-frequency function words per language for heuristic detection. */
const LANG_SIGNATURES = {
    es: [
        'el',
        'la',
        'los',
        'las',
        'de',
        'del',
        'que',
        'en',
        'con',
        'por',
        'una',
        'más',
        'pero',
        'como',
        'crear',
        'nueva',
        'muestra',
        'lista',
        'actualiza',
    ],
    en: [
        'the',
        'a',
        'an',
        'of',
        'in',
        'to',
        'is',
        'are',
        'for',
        'that',
        'this',
        'with',
        'create',
        'make',
        'show',
        'list',
        'update',
        'run',
        'get',
    ],
    pt: [
        'o',
        'os',
        'do',
        'da',
        'das',
        'dos',
        'para',
        'com',
        'no',
        'na',
        'uma',
        'mais',
        'não',
        'mas',
        'criar',
        'mostrar',
        'listar',
    ],
    de: [
        'der',
        'die',
        'das',
        'ein',
        'eine',
        'und',
        'von',
        'zu',
        'ist',
        'für',
        'mit',
        'auf',
        'nicht',
        'erstelle',
        'zeige',
    ],
    fr: [
        'le',
        'la',
        'les',
        'de',
        'un',
        'une',
        'est',
        'pas',
        'pour',
        'dans',
        'qui',
        'des',
        'du',
        'créer',
        'afficher',
    ],
};
/**
 * Detect the language of a text string using heuristic word-frequency scoring.
 * Returns a BCP-47-style language code ('es', 'en', 'pt', 'de', 'fr', ...) or 'en' as default.
 * This is intentionally conservative: when confidence is low, falls back to 'en'.
 */
export function detectLanguage(input) {
    const tokens = input
        .toLowerCase()
        .split(/\s+/)
        .filter((tok) => tok.length > 1);
    if (tokens.length === 0) {
        return 'en';
    }
    const scores = {};
    for (const [lang, keywords] of Object.entries(LANG_SIGNATURES)) {
        const keywordSet = new Set(keywords);
        const hits = tokens.filter((tok) => keywordSet.has(tok)).length;
        scores[lang] = hits / tokens.length;
    }
    // Find best-scoring language
    let bestLang = 'en';
    let bestScore = 0;
    for (const [lang, score] of Object.entries(scores)) {
        if (score > bestScore) {
            bestScore = score;
            bestLang = lang;
        }
    }
    // Minimum confidence threshold to avoid spurious detection
    return bestScore >= 0.05 ? bestLang : 'en';
}
/**
 * Translate with interpolation.
 *
 * @param key    Dot-notation key
 * @param params Key-value pairs to replace `{placeholder}` tokens
 * @param locale Optional locale override
 */
export function ti(key, params, locale) {
    let message = t(key, locale);
    for (const [param, value] of Object.entries(params)) {
        message = message.replaceAll(`{${param}}`, String(value));
    }
    return message;
}
//# sourceMappingURL=index.js.map