import {__, sprintf} from "@wordpress/i18n";
import {handleError} from "./notifications";
import {areDeepEqual, escapeRegex, isEmptyObject} from "../../helpers";

export const ALL_SETTINGS_DATA_KEY = 'lbaic.settings.dataToUpdate';

export const ALL_SETTINGS_ERRORS_DATA_KEY = 'lbaic.settings.errors';

export const CHATBOT_CPT_ERRORS_DATA_KEY = 'lbaic.posts.chatbot.{id}.errors';

/**
 * Get routes root path
 *
 * @return {string}
 */
export const getRoutesRootPath = () => {
    return LimbChatbot.Hooks.applyFilters('lbaic.admin.page.settings.routes.rootPath', window.location.pathname);
}

/**
 * Navigation menu item link main classes
 *
 * @param {object} params Menu item params
 * @param {object} state React routing NavLink classes state
 * @return {string[]}
 */
export const navLinkMainClasses = (params, state) => {
    const {isActive, isPending, isTransitioning} = state;
    const searchParams = new URLSearchParams(window.location.search);

    const classes = [
        'lbaic-settings-button-reset',
    ];
    // Pending
    if (isPending) {
        classes.push('pending');
    }
    // Active
    if (isActive && searchParams.get('menu') === params['menu']) {
        classes.push('active');
    }
    // Transitioning
    if (isTransitioning) {
        classes.push('transitioning');
    }

    return LimbChatbot.Hooks.applyFilters('lbaic.admin.page.settings.menuLink.classes', classes, params, state);
}

/**
 * Navigation menu parent items' link classes
 *
 * @param {object} params Menu item params
 * @param {object} state React routing NavLink classes state
 * @return {string[]}
 */
export const navLinkClasses = (params, state) => {
    const classes = [
        'lbaic-settings-nav-item-in',
        ...navLinkMainClasses(params, state),
    ];

    return LimbChatbot.Hooks.applyFilters('lbaic.admin.page.settings.navLink.classes', classes, params, state).join(' ');
}

/**
 * Navigation menu dropdown items' link classes
 *
 * @param {object} item Menu item
 * @param {object} params Menu item params
 * @param {object} state React routing NavLink classes state
 * @return {string[]}
 */
export const dropdownNavLinkClasses = (item, params, state) => {
    const classes = [
        'lbaic-settings-nav-dropdown-item-in',
        ...navLinkMainClasses(params, state),
    ];

    return LimbChatbot.Hooks.applyFilters('lbaic.admin.page.settings.dropdownNavLink.classes', classes, item, params, state).join(' ');
}

/**
 * Page tabs' main classes
 *
 * @param {object} params Tab params
 * @param {object} state React routing NavLink classes state
 * @return {string[]}
 */
export const tabLinkMainClasses = (params, state) => {
    const {isActive, isPending, isTransitioning} = state;
    const searchParams = new URLSearchParams(window.location.search);

    let active = true;
    if (isActive) {
        // Custom check of query params to detect active state
        for (const key in params) {
            if (searchParams.get(key) !== params[key]) {
                active = false;
                break;
            }
        }
    }

    const classes = [
        'lbaic-settings-tab',
    ];
    // Pending
    if (isPending) {
        classes.push('pending');
    }
    // Active
    if (isActive && active) {
        classes.push('active');
    }
    // Transitioning
    if (isTransitioning) {
        classes.push('transitioning');
    }

    return LimbChatbot.Hooks.applyFilters('lbaic.admin.page.settings.tabLink.mainClasses', classes, params, state);
}

/**
 * Page tabs' classes
 *
 * @param {object} item Tab
 * @param {object} params Tab params
 * @param {object} state React routing NavLink classes state
 * @return {string[]}
 */
export const tabLinkClasses = (item, params, state) => {
    const classes = [
        ...tabLinkMainClasses(params, state)
    ];

    return LimbChatbot.Hooks.applyFilters('lbaic.admin.page.settings.tabLink.classes', classes, item, params, state).join(' ');
}

/**
 * Get page root path
 *
 * @return {string}
 */
export const getPageRootPath = () => {
    if (LimbChatbot.Storage?.navLinkRootPath) {
        return LimbChatbot.Storage?.navLinkRootPath;
    }

    const path = window.location.pathname;
    const searchParams = new URLSearchParams(window.location.search);
    const page = searchParams.get('page');

    const rootPath = `${path}?page=${page}`;

    // Keep it in global to optimize this method
    LimbChatbot.Storage = {
        ...(LimbChatbot.Storage || {}),
        navLinkRootPath: LimbChatbot.Hooks.applyFilters('lbaic.admin.page.settings.navLink.rootPath', rootPath),
    }

    return LimbChatbot.Storage.navLinkRootPath;
}

/**
 * Get navigation menu item link params
 *
 * @param {string} menu Menu
 * @param {string} tab Tab
 * @param {boolean} asString Return params or convert to string
 * @return {string|{menu:string,tab?:string}}
 */
export const getNavLinkToParams = (menu, tab = '', asString = false) => {
    // Menu
    let params = {menu};
    // Tab
    if (tab) {
        params.tab = tab;
    }

    params = LimbChatbot.Hooks.applyFilters('lbaic.admin.page.settings.navLink.to.params', params, menu, tab, asString);

    return asString ? new URLSearchParams(params).toString() : params;
}

/**
 * Get utility link params
 *
 * @param {string} menu Menu
 * @param {string} tab Tab
 * @param {string} utility Utility
 * @param {string} action Action
 * @param {boolean} asString Return params or convert to string
 * @return {string|{menu:string,tab:string,utility:string}}
 */
export const getUtilityLinkToParams = ({menu, tab, utility, action}, asString = false) => {
    // Menu
    let params = {menu};
    // Tab
    if (tab) {
        params.tab = tab;
    }
    // Utility
    if (utility) {
        params.utility = utility;
    }
    // Action
    if (action) {
        params.action = action;
    }

    params = LimbChatbot.Hooks.applyFilters('lbaic.admin.page.settings.utilityLink.to.params', params, menu, tab, utility, action, asString);

    return asString ? new URLSearchParams(params).toString() : params;
}

/**
 * Get navigation link
 *
 * @param {string} menu Menu
 * @param {string} tab Tab
 * @param {object} params More params
 * @return {string}
 */
export const getNavLinkTo = (menu, tab = '', params = {}) => {
    const coreParams = getNavLinkToParams(menu, tab, true);
    let to = getPageRootPath() + '&' + coreParams;
    // More params
    if (params && Object.keys(params).length) {
        to += '&' + new URLSearchParams(params).toString();
    }

    return LimbChatbot.Hooks.applyFilters('lbaic.admin.page.settings.navLink.to', to, menu, tab);
}

/**
 * Get utility link
 *
 * @param {string} menu Menu
 * @param {string} tab Tab
 * @param {string} utility Utility
 * @param {string} action Action
 * @return {string}
 */
export const getUtilityLinkTo = (menu, tab, utility, action) => {
    const params = getUtilityLinkToParams({menu, tab, utility, action}, true);
    const to = getPageRootPath() + '&' + params;

    return LimbChatbot.Hooks.applyFilters('lbaic.admin.page.settings.utilityLink.to', to, menu, tab, utility, action);
}

/**
 * Get page utility link
 *
 * @param {string} page Page
 * @param {string} menu Menu
 * @param {string} tab Tab
 * @param {string} utility Utility
 * @param {string} action Action
 * @return {string}
 */
export const getPageUtilityLinkTo = (page, menu, tab, utility, action) => {
    const params = getUtilityLinkToParams({menu, tab, utility, action}, true);
    const to = `${window.location.origin}/wp-admin/admin.php?page=${page}&${params}`;

    return LimbChatbot.Hooks.applyFilters('lbaic.admin.page.settings.pageUtilityLink.to', to, page, menu, tab, utility, action);
}

/**
 * Get default page layout
 *
 * @param {object[]} navItems Navigation menu items
 * @param {string} menu Menu
 * @return {object|null}
 */
export const getDefaultPageLayoutData = (navItems, menu) => {
    if (!navItems?.length) {
        return null;
    }
    if (menu) {
        // Find a page data of the menu
        for (const navItem of navItems) {
            if (navItem.slug === menu) {
                return navItem;
            } else if (navItem.children?.length) {
                const data = getDefaultPageLayoutData(navItem.children, menu);
                if (data) {
                    return data;
                }
            }
        }
    }
    // Get the first page data of the menu
    for (const navItem of navItems) {
        if ('children' in navItem) {
            if (navItem.children.length) {
                const data = getDefaultPageLayoutData(navItem.children, menu);
                if (data) {
                    return data;
                }
            }
        } else {
            return navItem;
        }
    }
    return null;
}

/**
 * Get page data from navigation menu items
 *
 * @param {object[]} navItems Navigation menu items
 * @param {string} menu Menu
 * @param {string} tab Tab
 * @return {object|null}
 */
export const getPageLayoutData = (navItems, menu, tab = '') => {
    for (const navItem of navItems) {
        if (navItem.slug === menu) {
            return navItem;
        } else if (navItem.children?.length) {
            const data = getPageLayoutData(navItem.children, menu);
            if (data) {
                return data;
            }
        }
    }
    return null;
}

/**
 * Get initial data based on provided data
 *
 * @param {object} data Data
 * @return {object}
 */
export const getInitialData = (data) => {
    return Object.keys(data).reduce((obj, key) => {
        if (typeof data[key][0] === 'object') {
            obj[key] = JSON.parse(JSON.stringify(data[key][0]));
        } else {
            obj[key] = data[key][0];
        }
        return obj;
    }, {})
}

/**
 * Get data which has been changed to update
 *
 * @param {object} data Data
 * @param {object} initial Initial data
 * @param {string} prefix Data keys prefix
 * @param {string} suffix Data keys suffix
 * @return {{key: string, value: any}[]}
 */
export const getSettingsDataToUpdate = (data, initial, prefix = '', suffix = '') => {
    return Object.keys(data).reduce((arr, key) => {
        if (!areDeepEqual(data[key][0], initial[key])) {
            arr.push({
                key: prefix + key + suffix,
                value: data[key][0],
            });
        }
        return arr;
    }, []);
}

/**
 * Get all data to update
 *
 * @return {{key: string, value: any}[]|null}
 */
export const getAllSettingsChanges = () => {
    try {
        return JSON.parse(sessionStorage.getItem(ALL_SETTINGS_DATA_KEY) || '[]') || [];
    } catch (e) {
        console.error(e);
        return null;
    }
}

/**
 * Get all settings errors
 *
 * @return {object|null}
 */
export const getAllSettingsErrors = () => {
    try {
        return JSON.parse(sessionStorage.getItem(ALL_SETTINGS_ERRORS_DATA_KEY) || '{}') || {};
    } catch (e) {
        console.error(e);
        return null;
    }
}

/**
 * Get all settings data to update
 *
 * @param {object} data Data
 * @param {object} initial Initial data
 * @param {string} prefix Data keys prefix
 * @param {string} suffix Data keys suffix
 * @return {{key: *, value: *}[]|null}
 */
export const getAllSettingsDataToUpdate = (data, initial, prefix = '', suffix = '') => {
    try {
        // All settings keys
        const allKeys = Object.keys(data).map(item => prefix + item + suffix);
        // Changed data
        const changedData = getSettingsDataToUpdate(data, initial, prefix, suffix);
        // All data to update
        const dataToUpdate = getAllSettingsChanges();
        // New data to update
        return getMergedSettings(dataToUpdate, changedData, allKeys);
    } catch (e) {
        console.error(e);
        return null;
    }
}

/**
 * Set all settings data to update
 *
 * @param {object} data Data
 * @param {object} initial Initial data
 * @param {string} prefix Data keys prefix
 * @param {string} suffix Data keys suffix
 * @return {{key: *, value: *}[]|null}
 */
export const setSettingsDataToUpdate = (data, initial, prefix = '', suffix = '') => {
    try {
        const dataToUpdate = getAllSettingsDataToUpdate(data, initial, prefix, suffix);
        // Update the session storage
        sessionStorage.setItem(ALL_SETTINGS_DATA_KEY, JSON.stringify(dataToUpdate || []));

        return dataToUpdate;
    } catch (e) {
        console.error(e);
        return null;
    }
}

/**
 * Set all settings data to update based on multiple data
 *
 * @param {object[]} data Data
 * @param {object[]} initial Initial data
 * @param {string[]} prefixes Data keys prefixes
 * @param {string[]} suffixes Data keys suffixes
 * @return {{key: *, value: *}[]|null}
 */
export const setSettingsDataToUpdateFromMultiple = (data, initial, prefixes = [], suffixes = []) => {
    try {
        const dataToUpdate = getAllSettingsDataToUpdateFromMultiple(data, initial, prefixes, suffixes);
        // Update the session storage
        sessionStorage.setItem(ALL_SETTINGS_DATA_KEY, JSON.stringify(dataToUpdate || []));

        return dataToUpdate;
    } catch (e) {
        console.error(e);
        return null;
    }
}

/**
 * Get all settings data to update based on multiple data
 *
 * @param {object[]} data Data
 * @param {object[]} initial Initial data
 * @param {string[]} prefixes Data keys prefixes
 * @param {string[]} suffixes Data keys suffixes
 * @return {{key: *, value: *}[]|null}
 */
export const getAllSettingsDataToUpdateFromMultiple = (data, initial, prefixes = [], suffixes = []) => {
    try {
        const allKeys = [], changedData = [];
        for (let i = 0; i < data.length; i++) {
            // All settings keys
            const prefix = i < prefixes?.length ? prefixes[i] : '';
            const suffix = i < suffixes?.length ? suffixes[i] : '';
            allKeys.push(...Object.keys(data[i]).map(item => prefix + item + suffix));
            // Changed data
            changedData.push(...getSettingsDataToUpdate(data[i], initial[i], prefix, suffix));
        }
        // All data to update
        const dataToUpdate = getAllSettingsChanges();
        // New data to update
        return getMergedSettings(dataToUpdate, changedData, allKeys);
    } catch (e) {
        console.error(e);
        return null;
    }
}

/**
 * Merge new settings changes with current one
 *
 * @param {{key: string, value: any}[]} allSettingsToUpdate All settings to update
 * @param {{key: string, value: any}[]} localChangedSettings Local changed settings
 * @param {string[]} localSettingsKeys Local settings keys
 * @return {{key: *, value: *}[]}
 */
export const getMergedSettings = (allSettingsToUpdate, localChangedSettings, localSettingsKeys) => {
    const mergedMap = new Map();
    // Set all settings into a map
    allSettingsToUpdate.forEach(item => mergedMap.set(item.key, item.value));
    // Overwrite with the local settings array
    localChangedSettings.forEach(item => mergedMap.set(item.key, item.value));
    // Convert back to an array of objects
    const merged = Array.from(mergedMap, ([key, value]) => ({key, value}));
    // Keys to remove
    const keysToRemove = localSettingsKeys.filter(key => !localChangedSettings.find(item => item.key === key));
    if (keysToRemove.length) {
        // Remove settings that have no changes
        return merged.filter(({key, value}) => keysToRemove.indexOf(key) === -1);
    }
    return merged;
}

/**
 * Get data which has been changed to update
 *
 * @param {object} data Data
 * @param {object} initial Initial data
 * @param {string} prefix Data keys prefix
 * @param {string} suffix Data keys suffix
 * @return {{meta_key: string, meta_value: any}[]}
 */
export const getMetasDataToUpdate = (data, initial, prefix = '', suffix = '') => {
    return Object.keys(data).reduce((arr, key) => {
        if (!areDeepEqual(data[key][0], initial[key])) {
            arr.push({
                meta_key: prefix + key + suffix,
                meta_value: data[key][0],
            });
        }
        return arr;
    }, []);
}

/**
 * Get setting formated value
 *
 * @param {*} newValue New value
 * @param {*} initialValue Initial value
 * @param {string} key Key
 * @return {*|*[]|boolean}
 */
export const getSettingFormatedValue = (newValue, initialValue, key) => {
    let formattedValue;
    if (typeof initialValue === 'boolean') {
        formattedValue = newValue === 'true' || !!(+newValue);
    } else if (Array.isArray(initialValue)) {
        formattedValue = Array.isArray(newValue) ? newValue : [];
    } else if (typeof initialValue === 'string') {
        formattedValue = newValue ? newValue : '';
    } else if (typeof initialValue === 'object') {
        formattedValue = newValue ? newValue : JSON.parse(JSON.stringify(initialValue));
    }
    formattedValue = LimbChatbot.Hooks.applyFilters('lbaic.admin.settings.formatValue', formattedValue, key);

    return formattedValue !== undefined ? formattedValue : newValue;
}

/**
 * Get setting formated value for form
 *
 * @param {any} value Value
 * @return {number|string|*}
 */
export const getSettingFormatedValueForForm = (value) => {
    if (typeof value === 'boolean') {
        return +value || '';
    }

    return value;
}

/**
 * Helper to setup setting
 *
 * @param {string} keyPrefix Settings keys prefix
 * @param {object} setting Fetched setting
 * @param {object} settings Settings state
 * @param {object} initial Settings initial state ref
 * @return {0|1|-1}
 */
export const setupSetting = (keyPrefix, setting, settings, initial) => {
    try {
        const key = getSettingsLocalKey(setting.key, keyPrefix);
        if (key in settings) {
            // Format value
            const value = getSettingFormatedValue(setting.value, settings[key][0], key);
            // Update settings
            settings[key][1](value);
            // Update the initial state
            if (initial?.current) {
                initial.current[key] = value;
            }
            return 1;
        }
        // Not found
        return -1;
    } catch (e) {
        handleError(e);
        return 0;
    }
}

/**
 * Setup settings
 *
 * @param {object} settings Settings state
 * @param {string} storageKey Storage key
 * @param {string} keysPrefix Settings keys prefix
 * @param {object} dbSettings Settings DB data
 * @param {object|false} initialData Settings initial state
 * @param {string[]} skipKeys Skip keys getting from storage
 */
export const setupSettings = (settings, storageKey, keysPrefix, dbSettings, initialData = false, skipKeys = []) => {
    const settingsKeys = Object.keys(settings);
    // Session storage
    const storageData = JSON.parse((storageKey ? sessionStorage.getItem(storageKey) : false) || '{}');
    for (const key of settingsKeys) {
        // Loop on settings
        let value = typeof settings[key][0] === 'object' ? JSON.parse(JSON.stringify(settings[key][0])) : settings[key][0];
        // Check data in DB
        if (dbSettings?.length) {
            const dbKey = keysPrefix + key;
            const setting = dbSettings.find(item => item.key === dbKey);
            if (setting) {
                value = getSettingFormatedValue(setting.value, settings[key][0], key);
            }
        }
        // Update the initial state
        if (initialData) {
            initialData.current[key] = value;
        }
        // Check data in session storage
        if (skipKeys.indexOf(key) === -1 && key in storageData) {
            value = storageData[key];
        }
        settings[key][1](value);
    }
}

/**
 * Get data from react state
 *
 * @param {object} data Data
 * @return {{key: string, value: any}}
 */
export const getDataFromState = (data) => {
    return Object.keys(data).reduce((obj, key) => {
        obj[key] = data[key][0];
        return obj;
    }, {});
}

/**
 * Discard changes
 *
 * @param {object} data Data
 * @param {object} initial Initial data
 */
export const discardChanges = (data, initial) => {
    Object.keys(data).forEach(key => {
        data[key][1](initial[key]);
    });
    // Update the session storage
    sessionStorage.removeItem(ALL_SETTINGS_DATA_KEY);
    sessionStorage.removeItem(ALL_SETTINGS_ERRORS_DATA_KEY);
}

/**
 * Validate cost/token limits
 * Removes unrestricted units
 *
 * @param {object} limits Limits by units
 * @return {object}
 */
export const validateCostTokenLimits = (limits) => {
    try {
        const validatedLimits = {};
        for (const utility of Object.keys(limits)) {
            const utilityLimits = limits[utility].filter(item => item.value > 0);
            if (utilityLimits?.length) {
                validatedLimits[utility] = utilityLimits;
            }
        }
        return validatedLimits;
    } catch (e) {
        console.error(e);
    }
    return limits;
}

/**
 * Get formatted limits data
 * Removes unrestricted units
 *
 * @param {array} data Data
 * @param {number} index Limits settings index
 * @return {array}
 */
export const getValidatedCostsLimits = (data, index) => {
    try {
        if (data[index]?.value) {
            const newData = JSON.parse(JSON.stringify(data));
            newData[index].value = validateCostTokenLimits(newData[index].value);

            return newData;
        }
    } catch (e) {
        console.error(e);
    }
    return data;
}

/**
 * Get settings local key
 * Removes prefix and suffix
 *
 * @param {string} key Setting key
 * @param {string} prefix Setting key prefix
 * @return {string}
 */
export const getSettingsLocalKey = (key, prefix) => {
    try {
        return key.replace(new RegExp('^' + escapeRegex(prefix + '.')), '').replace(new RegExp(escapeRegex('.preview') + '$'), '');
    } catch (e) {
        console.error(e);
    }
    return key;
}

/**
 * Convert chatbot limits data -> strings to numbers
 *
 * @param {object} value Limits data
 * @return {object}
 */
export const convertChatbotLimits = (value) => {
    try {
        const result = {};
        for (const key in value) {
            result[key] = value[key].map(entry => {
                const converted = {};
                for (const k in entry) {
                    converted[k] = Number(entry[k]);
                }
                return converted;
            });
        }
        return result;
    } catch (e) {
        console.error(e);
    }
    return value;
}

/**
 * Convert links to HTML
 *
 * @param {string} text Text
 * @param {boolean} includeIcon Should the link include an icon
 * @return {string}
 */
export const convertLinksToHTML = (text, includeIcon = false) => {
    try {
        return text.replace(
            /(?<!href="|">)(https?:\/\/[^\s<]+?)([.,;:!?)\]}\s]*)(?=\s|$)/g,
            (match, url, punctuation) => {
                // Icon
                const icon = includeIcon ? `<svg class="lbaic-settings-table-card-body-i lbaic-settings-table-card-body-input-i" fill="none" viewBox="0 0 24 24"><use href="#lbaic-settings-external-arrow" class="lbaic-settings-external-arrow"></use><use href="#lbaic-external-box"></use></svg>` : '';

                return `<a href="${url}" target="_blank" rel="noopener">${url}${icon}</a>${punctuation}`;
            }
        );
    } catch (e) {
        handleError(e);
    }

    return text;
}

/**
 * Is settings has error
 *
 * @param {function} addNotification Add notification
 * @return {boolean} Is settings has error
 */
export const isAllSettingsHasError = (addNotification) => {
    // Check for all errors
    const allErrors = getAllSettingsErrors();
    if (!isEmptyObject(allErrors)) {
        const pageRootPath = getPageRootPath();
        const tabsUrlsParams = Object.keys(allErrors);
        // Loop on tabs and show notifications individually
        for (const tabUrlParam of tabsUrlsParams) {
            handleError(false, addNotification, {
                title: __("Please check errors.", 'limb-chatbot'),
                description: sprintf(
                    __("You have errors here: %s.", 'limb-chatbot'),
                    `<a href="${pageRootPath + '&' + tabUrlParam + '&check_errors=1'}">${allErrors[tabUrlParam].title}</a>`
                ),
            });
        }

        return true;
    }

    return false;
}

/**
 * Get all settings errors
 *
 * @return {object|null}
 */
export const getChatbotCPTSettingsErrors = (id) => {
    try {
        return JSON.parse(sessionStorage.getItem(CHATBOT_CPT_ERRORS_DATA_KEY.replace('{id}', id)) || '{}') || {};
    } catch (e) {
        console.error(e);
        return null;
    }
}

/**
 * Get ai provider form config relation
 *
 * @param {string} relatedTo Config relation to AI provider
 * @return {null|string}
 */
export const getProviderFromConfigRelation = (relatedTo) => {
    try {
        if (relatedTo.endsWith('Open_Ai')) {
            return 'open-ai';
        }
        if (relatedTo.endsWith('Gemini')) {
            return 'gemini';
        }
        if (relatedTo.endsWith('Deep_Seek')) {
            return 'deep-seek';
        }
        if (relatedTo.endsWith('Claude')) {
            return 'claude';
        }
        if (relatedTo.endsWith('Grok')) {
            return 'grok';
        }
        if (relatedTo.endsWith('Open_Router')) {
            return 'open-router';
        }
        if (relatedTo.endsWith('Slack')) {
            return 'slack';
        }
        if (relatedTo.endsWith('Telegram')) {
            return 'telegram';
        }
    } catch (e) {
        handleError(e);
    }
    return null;
}

/**
 * Get utility key prefix
 *
 * @param aiProviderId
 * @param utility
 * @return {{aiGrouped: string, default: string, localStorage: {aiGrouped: string, default: string}}}
 */
export const getUtilityKeyPrefix = (aiProviderId, utility) => {
    return {
        aiGrouped: `lbaic.ai_providers.${aiProviderId}.utilities.${utility}`,
        default: `lbaic.utilities.${utility}.default`,
        localStorage: {
            aiGrouped: `lbaic.settings.ai_providers.${aiProviderId}.utilities.${utility}`,
            default: `lbaic.settings.utilities.${utility}.default`,
        }
    }
}

/**
 * Handle pagination after deletion
 *
 * Uses the relationship between current page, old total pages, and new total pages to determine the action:
 * 1. If current page is empty and no items left: update to empty state
 * 2. If current page is empty but beyond new page count: navigate to last available page
 * 3. If current page is empty: refetch current page (shows items from next page)
 * 4. If current page is now the last page and pages decreased: refetch to get items from old next pages
 * 5. Otherwise: update items and pagination locally
 *
 * @param {object} params Parameters object
 * @param {number} params.currentItemsCount Current number of items on the page before deletion
 * @param {number} params.deletedCount Number of items being deleted
 * @param {object} params.pagination Current pagination state (page, perPage, total)
 * @param {function} params.fetchFunction Function to fetch data (page, perPage, order)
 * @param {object} params.order Current order state
 * @param {function} params.setItems Function to update items state
 * @param {function} params.setPagination Function to update pagination state
 * @param {array} params.deletedIds Array of deleted item IDs
 * @return {void}
 */
export const handlePaginationAfterDeletion = ({
                                                  currentItemsCount,
                                                  deletedCount,
                                                  pagination,
                                                  fetchFunction,
                                                  order,
                                                  setItems,
                                                  setPagination,
                                                  deletedIds
                                              }) => {
    const remainingItems = currentItemsCount - deletedCount;
    const currentPage = pagination.page;
    const perPage = pagination.perPage;
    const totalPages = Math.ceil(pagination.total / perPage);
    const newTotal = pagination.total - deletedCount;
    const newTotalPages = Math.ceil(newTotal / perPage);

    // Scenario 1: Current page becomes empty
    if (remainingItems === 0) {
        if (newTotalPages === 0) {
            // All items deleted - update to empty state
            if (setItems) {
                setItems([]);
            }
            if (setPagination) {
                setPagination(prevState => ({
                    ...prevState,
                    total: 0,
                    page: 1
                }));
            }
        } else if (currentPage > newTotalPages) {
            // Current page is beyond new page count - go to last available page
            fetchFunction(newTotalPages, perPage, order);
        } else {
            // Fetch current page (will show items from what was the next page)
            fetchFunction(currentPage, perPage, order);
        }
    }
    // Scenario 2: Current page has remaining items but is now the last page after page count decreased
    else if (currentPage === newTotalPages && newTotalPages < totalPages) {
        // We're now on the last page and there were pages after us - refetch to fill up with items from old next pages
        fetchFunction(currentPage, perPage, order);
    }
    // Scenario 3: Normal case - just update locally
    else {
        if (setItems && deletedIds) {
            setItems(prevState => prevState.filter(item => !deletedIds.includes(item.id)));
        }
        if (setPagination) {
            setPagination(prevState => ({
                ...prevState,
                total: newTotal
            }));
        }
    }
}

/**
 * Strip html tags from html string
 *
 * @param {string} html HTML
 * @return {string}
 */
export const stripHtml = (html) => {
    try {
        const tmp = document.createElement('div');
        tmp.innerHTML = html;
        return tmp.textContent || tmp.innerText || '';
    } catch (e) {
        handleError(e);
        return '';
    }
}

/**
 * Remove style attributes from html string
 *
 * @param {string} html HTML
 * @return {string}
 */
export const removeStyleAttributes = (html) => {
    try {
        return html.replace(/\s+(style|class|id|width|height|align|valign|bgcolor|color|border|cellpadding|cellspacing|background)=(['"])[^\2]*?\2/g, '');
    } catch (e) {
        handleError(e);
        return '';
    }
}