const presetDetails = {
    standard: {
        title: 'Standard',
        summary: 'Balanced compatibility for everyday browsing.',
    },
    strong: {
        title: 'Strong',
        summary: 'Recommended protection against ads, trackers, annoyances, and cookie banners.',
    },
    ultra: {
        title: 'Ultra',
        summary: 'Maximum protection. Some websites may need adjustments.',
    },
};

const presetOrder = [ 'standard', 'strong', 'ultra' ];

// Curated stock-list selection per preset. Each tier is additive on top of
// the previous one. Core defaults (off === undefined) and the browser-language
// regional list are always preserved through the isDefault flag, so they do
// not need to be listed here. The goal is stronger and more precise blocking,
// not enabling every available list.
const presetStockKeys = {
    standard: [
        'ublock-filters',
        'ublock-badware',
        'ublock-privacy',
        'ublock-unbreak',
        'ublock-quick-fixes',
        'easylist',
        'easyprivacy',
        'urlhaus-1',
        'plowe-0',
        'adguard-spyware-url',
    ],
    strong: [
        'ublock-cookies-easylist',
        'ublock-annoyances',
        'easylist-annoyances',
        'adguard-popup-overlays',
        'curben-phishing',
    ],
    ultra: [
        'fanboy-cookiemonster',
        'adguard-other-annoyances',
        'easylist-notifications',
        'easylist-newsletters',
        'adguard-widgets',
        'adguard-social',
        'fanboy-social',
        'dpollock-0',
        'block-lan',
    ],
};

// Map DeepBlocker UI language -> ordered candidate stock regional list keys.
// Only keys that actually exist in the assets system are ever used.
const regionListMap = {
    ar: [ 'ara-0' ],
    'zh-CN': [ 'CHN-0' ],
    nl: [ 'NLD-0' ],
    fr: [ 'FRA-0' ],
    de: [ 'DEU-0' ],
    el: [ 'GRC-0' ],
    hi: [ 'IND-0' ],
    id: [ 'IDN-0' ],
    it: [ 'ITA-0' ],
    ja: [ 'JPN-1' ],
    ko: [ 'KOR-1' ],
    pl: [ 'POL-0' ],
    pt: [ 'spa-1' ],
    ru: [ 'RUS-0' ],
    es: [ 'spa-0', 'spa-1' ],
    sv: [ 'SWE-1' ],
    tr: [ 'TUR-0' ],
    uk: [ 'UKR-0' ],
    vi: [ 'VIE-1' ],
};

const cumulativeStockKeysForPreset = preset => {
    const idx = presetOrder.indexOf(preset);
    const upto = idx === -1 ? presetOrder.indexOf('strong') : idx;
    const keys = new Set();
    for ( let i = 0; i <= upto; i += 1 ) {
        for ( const key of presetStockKeys[presetOrder[i]] ) {
            keys.add(key);
        }
    }
    return keys;
};

const isUserOrCustomList = (key, details = {}) => {
    const group = details.group2 || details.group || '';
    return (
        details.external === true ||
        group === 'custom' ||
        group === 'user' ||
        key === 'user-filters'
    );
};

const isStockFilterList = (key, details = {}) =>
    details.content === 'filters' && isUserOrCustomList(key, details) === false;

// Regional lists are region/language specific and should not, by themselves,
// determine whether the active protection is Standard / Strong / Ultra.
const isRegionalList = (key, details = {}) => {
    const group = details.group2 || details.group || '';
    return group === 'regions';
};

const isComparableStockList = (key, details = {}) =>
    isStockFilterList(key, details) && isRegionalList(key, details) === false;

// Return the best matching stock regional list key for a DeepBlocker UI
// language, or '' when none exists in the current assets system.
const regionKeyForLanguage = (available = {}, language = '') => {
    if ( typeof language !== 'string' || language === '' ) { return ''; }
    const candidates =
        regionListMap[language] ||
        regionListMap[language.split('-')[0]] ||
        [];
    for ( const key of candidates ) {
        const details = available[key];
        if ( details instanceof Object && details.content === 'filters' ) {
            return key;
        }
    }
    return '';
};

const getDeepBlockerLanguage = async ( ) => {
    const result = await storageGet('deepBlockerUiLanguage');
    return typeof result.deepBlockerUiLanguage === 'string'
        ? result.deepBlockerUiLanguage
        : '';
};

const storageGet = keys => new Promise((resolve, reject) => {
    try {
        browser.storage.local.get(keys, result => {
            const err = browser.runtime.lastError;
            if ( err ) {
                reject(new Error(err.message));
            } else {
                resolve(result || {});
            }
        });
    } catch (ex) {
        reject(ex);
    }
});

const storageSet = details => new Promise((resolve, reject) => {
    try {
        browser.storage.local.set(details, ( ) => {
            const err = browser.runtime.lastError;
            if ( err ) {
                reject(new Error(err.message));
            } else {
                resolve();
            }
        });
    } catch (ex) {
        reject(ex);
    }
});

const selectedKeysFromListDetails = listsetDetails => {
    const selected = [];
    const available = listsetDetails && listsetDetails.available || {};
    for ( const [ key, details ] of Object.entries(available) ) {
        if ( details instanceof Object === false ) { continue; }
        if ( details.off === true ) { continue; }
        selected.push(key);
    }
    return selected;
};

const defaultKeysFromListDetails = listsetDetails => {
    const keys = [];
    const available = listsetDetails && listsetDetails.available || {};
    for ( const [ key, details ] of Object.entries(available) ) {
        if ( details instanceof Object === false ) { continue; }
        if ( details.content !== 'filters' ) { continue; }
        if (
            details.isDefault === true ||
            details.isImportant === true ||
            details.preferred === true
        ) {
            keys.push(key);
        } else if ( isUserOrCustomList(key, details) && details.off !== true ) {
            keys.push(key);
        }
    }
    return keys;
};

const keysForPreset = (listsetDetails, preset, regionKey = '') => {
    const available = listsetDetails && listsetDetails.available || {};
    const wanted = cumulativeStockKeysForPreset(preset);
    const keys = new Set();
    for ( const [ key, details ] of Object.entries(available) ) {
        if ( details instanceof Object === false ) { continue; }
        if ( details.content !== 'filters' ) { continue; }
        // Preserve user/custom filter lists exactly as the user left them.
        if ( isUserOrCustomList(key, details) ) {
            if ( details.off !== true ) {
                keys.add(key);
            }
            continue;
        }
        // Always keep core defaults and the browser-language regional list
        // (both surface through isDefault), plus any engine-important list.
        if ( details.isDefault === true || details.isImportant === true ) {
            keys.add(key);
            continue;
        }
        // Otherwise include only the curated lists for this tier.
        if ( wanted.has(key) ) {
            keys.add(key);
        }
    }
    // Strong and Ultra also enable the regional list matching the selected
    // DeepBlocker UI language, when such a stock list exists.
    if (
        regionKey !== '' &&
        preset !== 'standard' &&
        available[regionKey] instanceof Object
    ) {
        keys.add(regionKey);
    }
    return Array.from(keys);
};

const savePresetChoice = async preset => {
    await storageSet({
        deepBlockerOnboardingDone: true,
        deepBlockerProtectionPreset: preset,
        deepBlockerProtectionPresetModified: false,
    });
};

const getSavedPresetChoice = async ( ) => {
    const result = await storageGet('deepBlockerProtectionPreset');
    return result.deepBlockerProtectionPreset || '';
};

const markPresetSelectionModified = async ( ) => {
    await storageSet({
        deepBlockerProtectionPresetModified: true,
    });
};

const setsAreEqual = (a, b) => {
    if ( a.size !== b.size ) { return false; }
    for ( const item of a ) {
        if ( b.has(item) === false ) { return false; }
    }
    return true;
};

const stockSelectionForPreset = (listsetDetails, preset) => {
    const available = listsetDetails && listsetDetails.available || {};
    // Regional lists are excluded from preset identity so enabling or disabling
    // a regional list never flips the active preset to "Modified".
    return new Set(keysForPreset(listsetDetails, preset).filter(key =>
        isComparableStockList(key, available[key] || {})
    ));
};

const currentStockSelection = listsetDetails => {
    const available = listsetDetails && listsetDetails.available || {};
    const selected = new Set();
    for ( const [ key, details ] of Object.entries(available) ) {
        if ( details instanceof Object === false ) { continue; }
        if ( details.off === true ) { continue; }
        if ( isComparableStockList(key, details) === false ) { continue; }
        selected.add(key);
    }
    return selected;
};

const getPresetState = async listsetDetails => {
    const selected = currentStockSelection(listsetDetails);
    for ( const preset of presetOrder ) {
        if ( setsAreEqual(selected, stockSelectionForPreset(listsetDetails, preset)) ) {
            return { preset, modified: false, label: presetDetails[preset].title };
        }
    }
    const result = await storageGet([
        'deepBlockerProtectionPreset',
        'deepBlockerProtectionPresetModified',
    ]);
    const saved = result.deepBlockerProtectionPreset || '';
    if ( presetDetails[saved] !== undefined ) {
        return {
            preset: saved,
            modified: true,
            label: `Modified from ${presetDetails[saved].title}`,
        };
    }
    return { preset: 'custom', modified: true, label: 'Custom' };
};

const applyProtectionPreset = async (preset, options = {}) => {
    const listsetDetails = await vAPI.messaging.send('dashboard', {
        what: 'getLists',
    });
    if (
        listsetDetails instanceof Object === false ||
        listsetDetails.available instanceof Object === false
    ) {
        throw new Error('Filter list data is unavailable');
    }
    await storageSet({
        deepBlockerPresetPreviousLists: selectedKeysFromListDetails(listsetDetails),
    });
    const language = await getDeepBlockerLanguage();
    const regionKey = regionKeyForLanguage(listsetDetails.available, language);
    const toSelect = keysForPreset(listsetDetails, preset, regionKey);
    await vAPI.messaging.send('dashboard', {
        what: 'applyFilterListSelection',
        toSelect,
        preserveCache: true,
    });
    await savePresetChoice(preset);
    if ( preset === 'strong' || preset === 'ultra' ) {
        await vAPI.messaging.send('dashboard', {
            what: 'userSettings',
            name: 'parseAllABPHideFilters',
            value: true,
        });
    }
    if ( options.reload === true ) {
        await vAPI.messaging.send('dashboard', { what: 'reloadAllFilters' });
    }
    if ( options.update === true ) {
        await vAPI.messaging.send('dashboard', { what: 'updateNow' });
    }
    return { listsetDetails, toSelect };
};

const resetToDeepBlockerDefaults = async (options = {}) => {
    const listsetDetails = await vAPI.messaging.send('dashboard', {
        what: 'getLists',
    });
    if (
        listsetDetails instanceof Object === false ||
        listsetDetails.available instanceof Object === false
    ) {
        throw new Error('Filter list data is unavailable');
    }
    // "Reset DeepBlocker defaults" restores the recommended Strong tier.
    const language = await getDeepBlockerLanguage();
    const regionKey = regionKeyForLanguage(listsetDetails.available, language);
    const toSelect = keysForPreset(listsetDetails, 'strong', regionKey);
    await vAPI.messaging.send('dashboard', {
        what: 'applyFilterListSelection',
        toSelect,
        preserveCache: true,
    });
    await storageSet({
        deepBlockerProtectionPreset: 'strong',
        deepBlockerProtectionPresetModified: false,
    });
    if ( options.reload === true ) {
        await vAPI.messaging.send('dashboard', { what: 'reloadAllFilters' });
    }
    return { listsetDetails, toSelect };
};

export {
    applyProtectionPreset,
    defaultKeysFromListDetails,
    getDeepBlockerLanguage,
    getSavedPresetChoice,
    getPresetState,
    isRegionalList,
    keysForPreset,
    markPresetSelectionModified,
    presetDetails,
    presetOrder,
    presetStockKeys,
    regionKeyForLanguage,
    resetToDeepBlockerDefaults,
    savePresetChoice,
    selectedKeysFromListDetails,
};
