/**
 * Copyright © schukai GmbH and all contributing authors, {{copyRightYear}}. All rights reserved.
 * Node module: @schukai/monster
 *
 * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
 * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
 *
 * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
 * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
 * For more information about purchasing a commercial license, please contact schukai GmbH.
 *
 * SPDX-License-Identifier: AGPL-3.0
 */

import {instanceSymbol} from "../../constants.mjs";
import {internalSymbol} from "../../constants.mjs";
import {CustomControl} from "../../dom/customcontrol.mjs";
import {Observer} from "../../types/observer.mjs";
import {ProxyObserver} from "../../types/proxyobserver.mjs";

import {addAttributeToken} from "../../dom/attributes.mjs";
import {
    assembleMethodSymbol,
    registerCustomElement,
    updaterTransformerMethodsSymbol,
} from "../../dom/customelement.mjs";
import {isObject, isFunction} from "../../types/is.mjs";
import {ToggleSwitchStyleSheet} from "./stylesheet/toggle-switch.mjs";
import {
    ATTRIBUTE_ERRORMESSAGE,
    ATTRIBUTE_ROLE,
} from "../../dom/constants.mjs";

export {ToggleSwitch};

/**
 * @private
 * @type {symbol}
 */
const switchElementSymbol = Symbol("switchElement");

/**
 * @type {string}
 */
export const STATE_ON = "on";

/**
 * @type {string}
 */
export const STATE_OFF = "off";

/**
 * A simple toggle switch
 *
 * @fragments /fragments/components/form/toggle-switch
 *
 * @example /examples/components/form/toggle-switch-simple
 *
 * @since 3.57.0
 * @copyright schukai GmbH
 * @summary A beautiful switch element
 * @fires monster-options-set
 * @fires monster-selected
 * @fires monster-change
 * @fires monster-changed
 */
class ToggleSwitch extends CustomControl {
    /**
     * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
     * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
     *
     * The individual configuration values can be found in the table.
     *
     * @property {string} value=current value of the element
     * @property {Boolean} disabled=disabled=false Disabled state
     * @property {Object} classes
     * @property {string} classes.on=specifies the class for the on state.
     * @property {string} classes.off=specifies the class for the off state.
     * @property {Object} values
     * @property {string} values.off=specifies the value of the element if it is not selected
     * @property {Object} labels
     * @property {string} labels.on=specifies the label for the on state.
     * @property {string} labels.off=specifies the label for the off state.
     * @property {string} actions
     * @property {string} actions.on=specifies the action for the on state.
     * @property {string} actions.off=specifies the action for the off state.
     * @property {Object} templates
     * @property {string} templates.main=specifies the main template used by the control.
     */
    get defaults() {
        return Object.assign({}, super.defaults, {
            value: null,
            disabled: false,
            classes: {
                on: "monster-theme-on",
                off: "monster-theme-off",
                handle: "monster-theme-primary-1",
            },
            values: {
                on: "on",
                off: "off",
            },
            labels: {
                toggleSwitchOn: "✔",
                toggleSwitchOff: "✖",
            },
            templates: {
                main: getTemplate(),
            },
            actions: {
                on: () => {
                    throw new Error("the on action is not defined");
                },
                off: () => {
                    throw new Error("the off action is not defined");
                },
            },
        });
    }

    /**
     * @return {ToggleSwitch}
     */
    [assembleMethodSymbol]() {
        const self = this;
        super[assembleMethodSymbol]();
        initControlReferences.call(this);
        initEventHandler.call(this);

        /**
         * init value to off
         * if the value was not defined before inserting it into the HTML
         */
        if (self.getOption("value") === null) {
            self.setOption("value", self.getOption("values.off"));
        }

        /**
         * value from attribute
         */
        if (self.hasAttribute("value")) {
            self.setOption("value", self.getAttribute("value"));
        }

        /**
         * validate value
         */
        validateAndSetValue.call(self);

        if (this.state === STATE_ON) {
            toggleClassOn.call(self);
        } else {
            toggleClassOff.call(self);
        }

        /**
         * is called when options changed
         */
        self[internalSymbol].attachObserver(
            new Observer(function () {
                if (isObject(this) && this instanceof ProxyObserver) {
                    validateAndSetValue.call(self);
                    toggleClass.call(self);
                }
            }),
        );

        return this;
    }

    /**
     * updater transformer methods for pipe
     *
     * @return {function}
     */
    [updaterTransformerMethodsSymbol]() {
        return {
            "state-callback": (Wert) => {
                return this.state;
            },
        };
    }

    /**
     * @return [CSSStyleSheet]
     */
    static getCSSStyleSheet() {
        return [ToggleSwitchStyleSheet];
    }

    /**
     * toggle switch
     *
     * ```
     * e = document.querySelector('monster-toggle-switch');
     * e.click()
     * ```
     */
    click() {
        toggleValues.call(this);
    }

    /**
     * toggle switch on/off
     *
     * ```
     * e = document.querySelector('monster-toggle-switch');
     * e.toggle()
     * ```
     *
     * @return {ToggleSwitch}
     */
    toggle() {
        this.click();
        return this;
    }

    /**
     * toggle switch on
     *
     * ```
     * e = document.querySelector('monster-toggle-switch');
     * e.toggleOn()
     * ```
     *
     * @return {ToggleSwitch}
     */
    toggleOn() {
        this.setOption("value", this.getOption("values.on"));
        return this;
    }

    /**
     * toggle switch off
     *
     * ```
     * e = document.querySelector('monster-toggle-switch');
     * e.toggleOff()
     * ```
     *
     * @return {ToggleSwitch}
     */
    toggleOff() {
        this.setOption("value", this.getOption("values.off"));
        return this;
    }

    /**
     * returns the status of the element
     *
     * ```
     * e = document.querySelector('monster-toggle-switch');
     * console.log(e.state)
     * // ↦ off
     * ```
     *
     * @return {string}
     */
    get state() {
        return this.getOption("value") === this.getOption("values.on")
            ? STATE_ON
            : STATE_OFF;
    }

    /**
     * The current value of the Switch
     *
     * ```
     * e = document.querySelector('monster-toggle-switch');
     * console.log(e.value)
     * // ↦ on
     * ```
     *
     * @return {string}
     */
    get value() {
        return this.state === STATE_ON
            ? this.getOption("values.on")
            : this.getOption("values.off");
    }

    /**
     * Set value
     *
     * ```
     * e = document.querySelector('monster-toggle-switch');
     * e.value="on"
     * ```
     *
     * @property {string} value
     */
    set value(value) {
        this.setOption("value", value);
    }

    /**
     * This method is called by the `instanceof` operator.
     * @returns {symbol}
     */
    static get [instanceSymbol]() {
        return Symbol.for(
            "@schukai/monster/components/form/toggle-switch@@instance",
        );
    }

    static getTag() {
        return "monster-toggle-switch";
    }
}

/**
 * @private
 */
function initControlReferences() {
    this[switchElementSymbol] = this.shadowRoot.querySelector(
        `[${ATTRIBUTE_ROLE}=switch]`,
    );
}

/**
 * @private
 */
function toggleClassOn() {
    this[switchElementSymbol].classList.remove(this.getOption("classes.off")); // change color
    this[switchElementSymbol].classList.add(this.getOption("classes.on")); // change color
}

/**
 * @private
 */
function toggleClassOff() {
    this[switchElementSymbol].classList.remove(this.getOption("classes.on")); // change color
    this[switchElementSymbol].classList.add(this.getOption("classes.off")); // change color
}

/**
 * @private
 */
function toggleClass() {
    if (this.getOption("value") === this.getOption("values.on")) {
        toggleClassOn.call(this);
    } else {
        toggleClassOff.call(this);
    }
}

/**
 * @private
 */
function toggleValues() {
    if (this.getOption("disabled") === true) {
        return;
    }

    let callback, value;

    if (this.getOption("value") === this.getOption("values.on")) {
        value = this.getOption("values.off");
        callback = this.getOption("actions.off");
    } else {
        value = this.getOption("values.on");
        callback = this.getOption("actions.on");
    }

    this.setOption("value", value);
    this?.setFormValue(value);

    if (isFunction(callback)) {
        callback.call(this);
    }

    this.setOption("state", this.state);
}

/**
 * @private
 */
function validateAndSetValue() {
    const value = this.getOption("value");

    const validatedValues = [];
    validatedValues.push(this.getOption("values.on"));
    validatedValues.push(this.getOption("values.off"));

    if (validatedValues.includes(value) === false) {
        addAttributeToken(
            this,
            ATTRIBUTE_ERRORMESSAGE,
            'The value "' +
            value +
            '" must be "' +
            this.getOption("values.on") +
            '" or "' +
            this.getOption("values.off"),
        );
        this.setOption("disabled", true);
        this.formDisabledCallback(true);
    } else {
        this.setOption("disabled", false);
        this.formDisabledCallback(false);
    }
}

/**
 * @private
 * @return {initEventHandler}
 */
function initEventHandler() {
    const self = this;
    self.addEventListener("keyup", function (event) {
        if (event.code === "Space") {
            self[switchElementSymbol].click();
        }
    });
    self.addEventListener("click", function (event) {
        toggleValues.call(self);
    });
    return this;
}

/**
 * @private
 * @return {string}
 */
function getTemplate() {
    // language=HTML
    return `
        <div data-monster-role="control" part="control" tabindex="0">
            <div class="switch" data-monster-role="switch"
                 data-monster-attributes="data-monster-state path:value | call:state-callback">
                <div class="label on" data-monster-replace="path:labels.toggleSwitchOn"></div>
                <div class="label off" data-monster-replace="path:labels.toggleSwitchOff"></div>
                <div data-monster-attributes="class path:classes.handle | suffix:\\ switch-slider"></div>
            </div>
        </div>`;
}

registerCustomElement(ToggleSwitch);