/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM 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.
 *
 * EspoCRM 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 EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module theme-manager */

/**
 * A theme manager.
 */
class ThemeManager {

    /**
     * @param {module:models/settings} config A config.
     * @param {module:models/preferences} preferences Preferences.
     * @param {module:metadata} metadata Metadata.
     * @param {?string} [name] A name. If not set, then will be obtained from config and preferences.
     */
    constructor(config, preferences, metadata, name) {
        /**
         * @private
         * @type {module:models/settings}
         */
        this.config = config;

        /**
         * @private
         * @type {module:models/preferences}
         */
        this.preferences = preferences;

        /**
         * @private
         * @type {module:metadata}
         */
        this.metadata = metadata;

        /**
         * @private
         * @type {?string}
         */
        this.name = name || null;
    }

    /**
     * @private
     */
    defaultParams = {
        screenWidthXs: 768,
        dashboardCellHeight: 155,
        dashboardCellMargin: 19,
    }

    /**
     * Get a theme name for the current user.
     *
     * @returns {string}
     */
    getName() {
        if (this.name) {
            return this.name;
        }

        if (!this.config.get('userThemesDisabled')) {
            let name = this.preferences.get('theme');

            if (name && name !== '') {
                return name;
            }
        }

        return this.config.get('theme');
    }

    /**
     * Get a theme name currently applied to the DOM.
     *
     * @returns {string|null} Null if not applied.
     */
    getAppliedName() {
        let name = window.getComputedStyle(document.body).getPropertyValue('--theme-name');

        if (!name) {
            return null;
        }

        return name.trim();
    }

    /**
     * Whether a current theme is applied to the DOM.
     *
     * @returns {boolean}
     */
    isApplied() {
        let appliedName = this.getAppliedName();

        if (!appliedName) {
            return true;
        }

        return this.getName() === appliedName;
    }

    /**
     * Get a stylesheet path for a current theme.
     *
     * @returns {string}
     */
    getStylesheet() {
        let link = this.getParam('stylesheet') || 'client/css/espo/espo.css';

        if (this.config.get('cacheTimestamp')) {
            link += '?r=' + this.config.get('cacheTimestamp').toString();
        }

        return link;
    }

    /**
     * Get an iframe stylesheet path for a current theme.
     *
     * @returns {string}
     */
    getIframeStylesheet() {
        let link = this.getParam('stylesheetIframe') || 'client/css/espo/espo-iframe.css';

        if (this.config.get('cacheTimestamp')) {
            link += '?r=' + this.config.get('cacheTimestamp').toString();
        }

        return link;
    }

    /**
     * Get an iframe-fallback stylesheet path for a current theme.
     *
     * @returns {string}
     */
    getIframeFallbackStylesheet() {
        let link = this.getParam('stylesheetIframeFallback') || 'client/css/espo/espo-iframe.css'

        if (this.config.get('cacheTimestamp')) {
            link += '?r=' + this.config.get('cacheTimestamp').toString();
        }

        return link;
    }

    /**
     * Get a theme parameter.
     *
     * @param {string} name A parameter name.
     * @returns {*} Null if not set.
     */
    getParam(name) {
        if (name !== 'params' && name !== 'mappedParams') {
            let varValue = this.getVarParam(name);

            if (varValue !== null) {
                return varValue;
            }

            let mappedValue = this.getMappedParam(name);

            if (mappedValue !== null) {
                return mappedValue;
            }
        }

        let value = this.metadata.get(['themes', this.getName(), name]);

        if (value !== null) {
            return value;
        }

        value = this.metadata.get(['themes', this.getParentName(), name]);

        if (value !== null) {
            return value;
        }

        return this.defaultParams[name] || null;
    }

    /**
     * @private
     * @param {string} name
     * @returns {*}
     */
    getVarParam(name) {
        let params = this.getParam('params') || {};

        if (!(name in params)) {
            return null;
        }

        let values = null;

        if (!this.config.get('userThemesDisabled') && this.preferences.get('theme')) {
            values = this.preferences.get('themeParams');
        }

        if (!values) {
            values = this.config.get('themeParams');
        }

        if (values && (name in values)) {
            return values[name];
        }

        if ('default' in params[name]) {
            return params[name].default;
        }

        return null;
    }

    /**
     * @private
     * @param {string} name
     * @returns {*}
     */
    getMappedParam(name) {
        let mappedParams = this.getParam('mappedParams') || {};

        if (!(name in mappedParams)) {
            return null;
        }

        let mapped = mappedParams[name].param;
        let valueMap = mappedParams[name].valueMap;

        if (mapped && valueMap) {
            let key = this.getParam(mapped);

            return valueMap[key];
        }

        return null;
    }

    /**
     * @private
     * @returns {string}
     */
    getParentName() {
        return this.metadata.get(['themes', this.getName(), 'parent']) || 'Espo';
    }

    /**
     * Whether a current theme is different from a system default theme.
     *
     * @returns {boolean}
     */
    isUserTheme() {
        if (this.config.get('userThemesDisabled')) {
            return false;
        }

        let name = this.preferences.get('theme');

        if (!name || name === '') {
            return false;
        }

        return name !== this.config.get('theme');
    }
}

export default ThemeManager;
