/**
 * Internationalization (i18n) Service
 * Handles translations and locale switching
 * @module i18n
 */

/**
 * @typedef {'de' | 'en' | 'fr' | 'it' | 'es' | 'pt' | 'ru'} Locale
 */

/**
 * Mapping from short locale codes (used in frontend) to
 * Directus long locale codes (used in categories_translations etc.)
 * @type {Object<Locale, string>}
 */
const LOCALE_TO_DIRECTUS = {
    de: 'de-DE',
    en: 'en-US',
    fr: 'fr-FR',
    it: 'it-IT',
    es: 'es-ES',
    pt: 'pt-BR',
    ru: 'ru-RU'
}

const DIRECTUS_TO_LOCALE = Object.fromEntries(
    Object.entries(LOCALE_TO_DIRECTUS).map(([k, v]) => [v, k])
)

/**
 * I18n Service class
 * @class
 */
class I18n {
    constructor() {
        /** @type {Object<Locale, Object>} */
        this.translations = {}
        /** @type {Locale} */
        this.currentLocale = 'de'
        /** @type {Locale} */
        this.fallbackLocale = 'de'
        /** @type {Locale[]} */
        this.supportedLocales = ['de', 'en', 'fr', 'it', 'es', 'pt', 'ru']
        /** @type {Set<Function>} */
        this.subscribers = new Set()
        /** @type {boolean} */
        this.loaded = false
    }

    /**
     * Initialize i18n - load translations for detected locale
     * @returns {Promise<void>}
     */
    async init() {
        const savedLocale = localStorage.getItem('locale')
        const browserLocale = navigator.language.split('-')[0]
        
        this.currentLocale = savedLocale 
            || (this.supportedLocales.includes(browserLocale) ? browserLocale : this.fallbackLocale)
        
        await this.loadTranslations(this.currentLocale)
        this.loaded = true
        this.updateDOM()
    }

    /**
     * Load translations for a locale
     * @param {Locale} locale - Locale to load
     * @returns {Promise<void>}
     */
    async loadTranslations(locale) {
        if (this.translations[locale]) return
        
        try {
            const response = await fetch(`/locales/${locale}.json`)
            if (!response.ok) throw new Error(`Failed to load ${locale}`)
            this.translations[locale] = await response.json()
        } catch (error) {
            console.error(`Failed to load translations for ${locale}:`, error)
            if (locale !== this.fallbackLocale) {
                await this.loadTranslations(this.fallbackLocale)
            }
        }
    }

    /**
     * Change current locale
     * @param {Locale} locale - New locale
     * @returns {Promise<void>}
     */
    async setLocale(locale) {
        if (!this.supportedLocales.includes(locale)) {
            console.warn(`Locale ${locale} is not supported`)
            return
        }
        
        await this.loadTranslations(locale)
        this.currentLocale = locale
        localStorage.setItem('locale', locale)
        document.documentElement.lang = locale
        
        this.updateDOM()
        this.notifySubscribers()
    }

    /**
     * Get current locale
     * @returns {Locale}
     */
    getLocale() {
        return this.currentLocale
    }

    /**
     * Translate a key with optional parameters
     * @param {string} key - Translation key (e.g., 'home.title')
     * @param {Object<string, string|number>} [params={}] - Interpolation params
     * @returns {string} Translated text or key if not found
     * @example
     * t('listing.expiresInDays', { days: 5 }) // "Noch 5 Tage"
     */
    t(key, params = {}) {
        const translations = this.translations[this.currentLocale] 
            || this.translations[this.fallbackLocale] 
            || {}
        
        let text = this.getNestedValue(translations, key)
        
        if (text === undefined) {
            console.warn(`Missing translation: ${key}`)
            return key
        }
        
        Object.entries(params).forEach(([param, value]) => {
            text = text.replace(new RegExp(`{{${param}}}`, 'g'), value)
        })
        
        return text
    }

    /**
     * Get nested value from object by dot-notation path
     * @private
     * @param {Object} obj - Object to search
     * @param {string} path - Dot-notation path (e.g., 'home.title')
     * @returns {*} Value or undefined
     */
    getNestedValue(obj, path) {
        return path.split('.').reduce((current, key) => {
            return current && current[key] !== undefined ? current[key] : undefined
        }, obj)
    }

    /**
     * Update all DOM elements with data-i18n attributes
     * Called automatically on locale change
     */
    updateDOM() {
        document.querySelectorAll('[data-i18n]').forEach((el) => {
            const key = el.getAttribute('data-i18n')
            let params = {}
            if (el.dataset.i18nParams) {
                try {
                    params = JSON.parse(el.dataset.i18nParams)
                } catch (e) {
                    console.warn(`Invalid i18n params for key "${key}":`, e)
                }
            }
            el.textContent = this.t(key, params)
        })
        
        document.querySelectorAll('[data-i18n-placeholder]').forEach((el) => {
            const key = el.getAttribute('data-i18n-placeholder')
            el.placeholder = this.t(key)
        })
        
        document.querySelectorAll('[data-i18n-title]').forEach((el) => {
            const key = el.getAttribute('data-i18n-title')
            el.title = this.t(key)
        })
        
        document.querySelectorAll('[data-i18n-aria]').forEach((el) => {
            const key = el.getAttribute('data-i18n-aria')
            el.setAttribute('aria-label', this.t(key))
        })
    }

    /**
     * Subscribe to locale changes
     * @param {Function} callback - Called with new locale on change
     * @returns {Function} Unsubscribe function
     */
    subscribe(callback) {
        this.subscribers.add(callback)
        return () => this.subscribers.delete(callback)
    }

    /** @private */
    notifySubscribers() {
        this.subscribers.forEach(callback => callback(this.currentLocale))
    }

    /**
     * Get list of supported locales
     * @returns {Locale[]}
     */
    getSupportedLocales() {
        return this.supportedLocales
    }

    /**
     * Get human-readable display name for a locale
     * @param {Locale} locale
     * @returns {string}
     */
    getLocaleDisplayName(locale) {
        const names = {
            de: 'Deutsch',
            en: 'English',
            fr: 'Français',
            it: 'Italiano',
            es: 'Español',
            pt: 'Português',
            ru: 'Русский'
        }
        return names[locale] || locale
    }

    /**
     * Get the Directus language code for a frontend locale
     * @param {Locale} [locale] - Frontend locale (defaults to current)
     * @returns {string} Directus language code (e.g. 'de-DE')
     */
    getDirectusLocale(locale) {
        return LOCALE_TO_DIRECTUS[locale || this.currentLocale] || LOCALE_TO_DIRECTUS[this.fallbackLocale]
    }

    /**
     * Get the frontend locale for a Directus language code
     * @param {string} directusCode - Directus code (e.g. 'it-IT')
     * @returns {Locale}
     */
    fromDirectusLocale(directusCode) {
        return DIRECTUS_TO_LOCALE[directusCode] || directusCode?.split('-')[0] || this.fallbackLocale
    }
}

export const i18n = new I18n()
export const t = (key, params) => i18n.t(key, params)
export const getCurrentLanguage = () => i18n.getLocale()
export { LOCALE_TO_DIRECTUS, DIRECTUS_TO_LOCALE }
