/*
 * Copyright (C) 2023-2024  Yomitan Authors
 * Copyright (C) 2020-2022  Yomichan Authors
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

const placeholder = '${title}';

/**
 * @template {import('test/translator').OptionsType} T
 * @param {T} type
 * @param {import('test/translator').OptionsPresetObject} optionsPresets
 * @param {import('test/translator').OptionsList} optionsArray
 * @returns {import('test/translator').OptionsPresetGeneric<T>}
 * @throws {Error}
 */
function getCompositePreset(type, optionsPresets, optionsArray) {
    const preset = /** @type {import('test/translator').OptionsPresetGeneric<T>} */ ({type});
    if (!Array.isArray(optionsArray)) { optionsArray = [optionsArray]; }
    for (const entry of optionsArray) {
        switch (typeof entry) {
            case 'string':
                {
                    if (!Object.prototype.hasOwnProperty.call(optionsPresets, entry)) {
                        throw new Error('Options preset not found');
                    }
                    const preset2 = optionsPresets[entry];
                    if (preset2.type !== type) {
                        throw new Error('Invalid options preset type');
                    }
                    Object.assign(preset, structuredClone(preset2));
                }
                break;
            case 'object':
                if (entry.type !== type) {
                    throw new Error('Invalid options preset type');
                }
                Object.assign(preset, structuredClone(entry));
                break;
            default:
                throw new Error('Invalid options type');
        }
    }
    return preset;
}


/**
 * @param {string} dictionaryName
 * @param {import('test/translator').OptionsPresetObject} optionsPresets
 * @param {import('test/translator').OptionsList} optionsArray
 * @returns {import('translation').FindKanjiOptions}
 */
export function createFindKanjiOptions(dictionaryName, optionsPresets, optionsArray) {
    const preset = getCompositePreset('kanji', optionsPresets, optionsArray);

    /** @type {import('translation').KanjiEnabledDictionaryMap} */
    const enabledDictionaryMap = new Map();
    const presetEnabledDictionaryMap = preset.enabledDictionaryMap;
    if (Array.isArray(presetEnabledDictionaryMap)) {
        for (const [key, value] of presetEnabledDictionaryMap) {
            enabledDictionaryMap.set(key === placeholder ? dictionaryName : key, value);
        }
    }

    return {
        enabledDictionaryMap,
        removeNonJapaneseCharacters: !!preset.removeNonJapaneseCharacters
    };
}

/**
 * @param {string} dictionaryName
 * @param {import('test/translator').OptionsPresetObject} optionsPresets
 * @param {import('test/translator').OptionsList} optionsArray
 * @returns {import('translation').FindTermsOptions}
 */
export function createFindTermsOptions(dictionaryName, optionsPresets, optionsArray) {
    const preset = getCompositePreset('terms', optionsPresets, optionsArray);

    /** @type {import('translation').TermEnabledDictionaryMap} */
    const enabledDictionaryMap = new Map();
    const presetEnabledDictionaryMap = preset.enabledDictionaryMap;
    if (Array.isArray(presetEnabledDictionaryMap)) {
        for (const [key, value] of presetEnabledDictionaryMap) {
            enabledDictionaryMap.set(key === placeholder ? dictionaryName : key, value);
        }
    }

    /** @type {import('translation').FindTermsTextReplacements} */
    const textReplacements = [];
    if (Array.isArray(preset.textReplacements)) {
        for (const value of preset.textReplacements) {
            if (Array.isArray(value)) {
                const array = [];
                for (const {pattern, flags, replacement} of value) {
                    array.push({pattern: new RegExp(pattern, flags), replacement});
                }
                textReplacements.push(array);
            } else {
                // Null
                textReplacements.push(value);
            }
        }
    }

    const {
        matchType,
        deinflect,
        mainDictionary,
        sortFrequencyDictionary,
        sortFrequencyDictionaryOrder,
        removeNonJapaneseCharacters,
        convertHalfWidthCharacters,
        convertNumericCharacters,
        convertAlphabeticCharacters,
        convertHiraganaToKatakana,
        convertKatakanaToHiragana,
        collapseEmphaticSequences,
        excludeDictionaryDefinitions,
        searchResolution
    } = preset;

    return {
        matchType: typeof matchType !== 'undefined' ? matchType : 'exact',
        deinflect: typeof deinflect !== 'undefined' ? deinflect : true,
        mainDictionary: typeof mainDictionary !== 'undefined' && mainDictionary !== placeholder ? mainDictionary : dictionaryName,
        sortFrequencyDictionary: typeof sortFrequencyDictionary !== 'undefined' ? sortFrequencyDictionary : null,
        sortFrequencyDictionaryOrder: typeof sortFrequencyDictionaryOrder !== 'undefined' ? sortFrequencyDictionaryOrder : 'ascending',
        removeNonJapaneseCharacters: typeof removeNonJapaneseCharacters !== 'undefined' ? removeNonJapaneseCharacters : false,
        convertHalfWidthCharacters: typeof convertHalfWidthCharacters !== 'undefined' ? convertHalfWidthCharacters : 'false',
        convertNumericCharacters: typeof convertNumericCharacters !== 'undefined' ? convertNumericCharacters : 'false',
        convertAlphabeticCharacters: typeof convertAlphabeticCharacters !== 'undefined' ? convertAlphabeticCharacters : 'false',
        convertHiraganaToKatakana: typeof convertHiraganaToKatakana !== 'undefined' ? convertHiraganaToKatakana : 'false',
        convertKatakanaToHiragana: typeof convertKatakanaToHiragana !== 'undefined' ? convertKatakanaToHiragana : 'false',
        collapseEmphaticSequences: typeof collapseEmphaticSequences !== 'undefined' ? collapseEmphaticSequences : 'false',
        textReplacements,
        enabledDictionaryMap,
        excludeDictionaryDefinitions: Array.isArray(excludeDictionaryDefinitions) ? new Set(excludeDictionaryDefinitions) : null,
        searchResolution: typeof searchResolution !== 'undefined' ? searchResolution : 'letter'
    };
}