// *****************************************************************************
// Copyright (C) 2021 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { FormatType, Localization } from './i18n/localization';

export namespace nls {

    export let localization: Localization | undefined;

    export const defaultLocale = 'en';

    export const localeId = 'localeId';

    export const locale = typeof window === 'object' && window && window.localStorage.getItem(localeId) || undefined;

    let keyProvider: LocalizationKeyProvider | undefined;

    /**
     * Automatically localizes a text if that text also exists in the vscode repository.
     */
    export function localizeByDefault(defaultValue: string, ...args: FormatType[]): string {
        if (localization) {
            const key = getDefaultKey(defaultValue);
            if (key) {
                return localize(key, defaultValue, ...args);
            } else {
                console.warn(`Could not find translation key for default value: "${defaultValue}"`);
            }
        }
        return Localization.format(defaultValue, args);
    }

    export function getDefaultKey(defaultValue: string): string {
        if (!keyProvider) {
            keyProvider = new LocalizationKeyProvider();
        }
        const key = keyProvider.get(defaultValue);
        if (key) {
            return key;
        }
        return '';
    }

    export function localize(key: string, defaultValue: string, ...args: FormatType[]): string {
        return Localization.localize(localization, key, defaultValue, ...args);
    }

    export function isSelectedLocale(id: string): boolean {
        if (locale === undefined && id === defaultLocale) {
            return true;
        }
        return locale === id;
    }

    export function setLocale(id: string): void {
        window.localStorage.setItem(localeId, id);
    }

    /**
     * Sets the 'lang' attribute on the given HTML element based on the current locale.
     * If no locale is set, defaults to 'en' (English).
     * Typically used with document.documentElement (the <html> tag) to set the page language.
     *
     * @param element The HTML element to set the language attribute on
     */
    export function setHtmlLang(element: HTMLElement): void {
        const lang = locale?.split('_').at(0) || 'en';
        element.setAttribute('lang', lang);
    }

    /**
     * Sets the 'translate' attribute to 'no' and adds the 'notranslate' class
     * to the given HTML element. This prevents translation tools from translating
     * the content of the element.
     * Typically used with document.documentElement (the <html> tag) to disable page translation.
     *
     * @param element The HTML element to set translation attributes on
     */
    export function setHtmlNoTranslate(element: HTMLElement): void {
        element.setAttribute('translate', 'no');
        element.classList.add('notranslate');
    }
}

interface NlsKeys {
    [key: string]: (string | NlsInfo)[]
}

interface NlsInfo {
    key: string
    comment: string[]
}

class LocalizationKeyProvider {

    private preferredKeys = new Set([
        // We only want the `File` translation used in the menu
        'vscode/fileActions.contribution/filesCategory',
        // Needed for `Close Editor` translation
        'vscode/editor.contribution/closeEditor'
    ]);
    private data = this.buildData();

    get(defaultValue: string): string | undefined {
        const normalized = Localization.normalize(defaultValue);
        return this.data.get(normalized) || this.data.get(normalized.toUpperCase());
    }

    /**
     * Transforms the data coming from the `nls.metadata.json` file into a map.
     * The original data contains arrays of keys and messages.
     * The result is a map that matches each message to the key that belongs to it.
     *
     * This allows us to skip the key in the localization process and map the original english default values to their translations in different languages.
     */
    private buildData(): Map<string, string> {
        const bundles = require('../../src/common/i18n/nls.metadata.json');
        const keys: NlsKeys = bundles.keys;
        const messages: Record<string, string[]> = bundles.messages;
        const data = new Map<string, string>();
        const foundPreferredKeys = new Set<string>();
        const keysAndMessages = this.buildKeyMessageTuples(keys, messages);
        for (const { key, message } of keysAndMessages) {
            if (!foundPreferredKeys.has(message)) {
                data.set(message, key);
                if (this.preferredKeys.has(key)) {
                    // Prevent messages with preferred keys to be overridden
                    foundPreferredKeys.add(message);
                }
            }
        }
        // Second pass adds each message again in upper case, if the message doesn't already exist in upper case
        // The second pass is needed to not accidentally override any translations which actually use the upper case message
        for (const { key, message } of keysAndMessages) {
            const upperMessage = message.toUpperCase();
            if (!data.has(upperMessage)) {
                data.set(upperMessage, key);
            }
        }
        return data;
    }

    private buildKeyMessageTuples(keys: NlsKeys, messages: Record<string, string[]>): { key: string, message: string }[] {
        const list: { key: string, message: string }[] = [];
        for (const [fileKey, messageBundle] of Object.entries(messages)) {
            const keyBundle = keys[fileKey];
            for (let i = 0; i < messageBundle.length; i++) {
                const message = Localization.normalize(messageBundle[i]);
                const key = keyBundle[i];
                const localizationKey = this.buildKey(typeof key === 'string' ? key : key.key, fileKey);
                list.push({
                    key: localizationKey,
                    message
                });
            }
        }
        return list;
    }

    private buildKey(key: string, filepath: string): string {
        return `vscode/${Localization.transformKey(filepath)}/${key}`;
    }
}
