/* ===================== AUTOGENERATED SOURCE IMPORT ===================== */

import Template from "/emcJS/util/html/template/HTMLTemplate.js";

const TPL = new Template(`
    <emc-i18n-tooltip id="tooltip">
        <label id="label" for="input">
            <emc-i18n-label id="label-text"></emc-i18n-label>
        </label>
        <div id="field-container">
            <div id="field"></div>
            <emc-button id="reset" icon="↺" class="rotate-icon-270 inactive" tabindex="-1"></emc-button>
        </div>
        <emc-i18n-textbox id="error" aria-live="polite"></emc-i18n-textbox>
        <emc-i18n-textbox id="description"></emc-i18n-textbox>
    </emc-i18n-tooltip>
    
`);

import GlobalStyle from "/emcJS/util/html/template/CSSTemplate.js";

const STYLE = new GlobalStyle(`
    :host {
        display: flex;
        align-items: flex-start;
        justify-content: flex-start;
        flex-direction: column;
        flex-shrink: 0;
        flex-grow: 1;
        width: auto;
        min-width: 100px;
        padding: 6px;
        color: var(--form-text-color, #000000);
        font-size: 1rem;
    }
    
    :host(:not([nohover]):hover),
    :host([nohover="false"]:hover) {
        color: var(--form-hover-text-color, #000000);
        background-color: var(--form-hover-back-color, #eeeeee);
        background-image: var(--form-hover-back-image, none);
    }
    
    :host([nopad]:not([nopad="false"])) {
        padding: 0px;
    }
    
    #field-container {
        position: relative;
        display: flex;
        align-items: stretch;
        justify-content: flex-start;
        flex-grow: 1;
        width: 100%;
    }
    
    #field {
        position: relative;
        display: flex;
        align-items: stretch;
        justify-content: stretch;
        flex-shrink: 0;
        flex-grow: 1;
        max-width: 100%;
        width: 0px;
    }
    
    #reset {
        display: none;
        width: 2em;
        height: 2em;
        margin: 0px 0px 0px 5px;
    }
    
    :host([resettable]:not([resettable="false"])) #reset {
        display: block;
    }
    
    #reset.inactive {
        opacity: 0.5;
        pointer-events: none;
    }
    
    #label {
        display: flex;
        align-items: flex-start;
        justify-content: flex-start;
        flex-direction: column;
        flex-shrink: 0;
        flex-grow: 1;
        width: 100%;
        cursor: pointer;
        user-select: none;
        padding: 0 4px 4px;
    }
    
    :host(:disabled) #label {
        cursor: default;
    }
    
    #label:has(#label-text:empty) {
        display: none;
    }
    
    #error {
        display: block;
        flex-shrink: 0;
        max-width: 100%;
        padding: 0px 5px;
        margin: 4px 0 0;
        color: var(--form-invalid-text-color, #a70000);
        background-color: var(--form-invalid-back-color, #ffeeee);
        background-image: var(--form-invalid-back-image, none);
        font-size: 0.9em;
        line-height: 1.4em;
        font-style: italic;
        cursor: pointer;
        user-select: none;
    }
    
    :host([hideerrors]:not([hideerrors="false"])) #error,
    #error:empty {
        display: none;
    }
    
    #description {
        display: block;
        flex-shrink: 0;
        max-width: 100%;
        padding: 0px 5px;
        margin: 4px 0 0;
        font-size: 0.9em;
        line-height: 1.4em;
        font-style: italic;
        cursor: default;
        user-select: none;
    }
    
    #description:empty {
        display: none;
    }
    
`);

const CONFIG_FIELDS = [
    {
        "type": "StringInput",
        "name": "name",
        "label": "Name",
        "value": ""
    },
    {
        "type": "StringInput",
        "name": "label",
        "label": "Label",
        "value": ""
    },
    {
        "type": "TextInput",
        "name": "tooltip",
        "label": "Tooltip",
        "value": ""
    },
    {
        "type": "TextInput",
        "name": "description",
        "label": "Description",
        "value": ""
    },
    {
        "type": "SwitchInput",
        "name": "required",
        "label": "Required",
        "value": false
    },
    {
        "type": "SwitchInput",
        "name": "resettable",
        "label": "Resettable",
        "value": false
    },
    {
        "type": "BoolOrLogicInput",
        "name": "visible",
        "label": "Visible (will not influence preview)",
        "value": true
    },
    {
        "type": "BoolOrLogicInput",
        "name": "editable",
        "label": "Editable (will not influence preview)",
        "value": true
    },
    {
        "type": "BoolOrLogicInput",
        "name": "enabled",
        "label": "Enabled (will not influence preview)",
        "value": true
    },
    {
        "type": "SwitchInput",
        "name": "hideerrors",
        "label": "Hide Error-Label",
        "value": false
    },
    {
        "type": "SwitchInput",
        "name": "nohover",
        "label": "Remove hover background effect",
        "value": false
    },
    {
        "type": "SwitchInput",
        "name": "nopad",
        "label": "Remove padding",
        "value": false
    },
    {
        "type": "SwitchInput",
        "name": "novalidate",
        "label": "Don't include this input in the auto validation",
        "value": false
    }
]
;

/* ======================================================================= */

import CustomFormElement from "../../element/CustomFormElement.js";
import {deepClone} from "../../../util/helper/DeepClone.js";
import {debounce} from "../../../util/Debouncer.js";
import {isEqual} from "../../../util/helper/Comparator.js";
import {delimitInteger} from "../../../util/helper/number/Integer.js";
import "../button/Button.js";

// https://web.dev/more-capable-form-controls/#form-associated-custom-elements

function isValueSet(value) {
    if (value == null) {
        return false;
    }
    if (typeof value === "string") {
        return value !== "";
    }
    if (typeof value === "number") {
        return !isNaN(value);
    }
    if (typeof value === "object") {
        if (Array.isArray(value)) {
            return value.length > 0;
        }
        return Object.keys(value).length > 0;
    }
    return true;
}

export default class AbstractFormElement extends CustomFormElement {

    static #changeDebounceTime = 300;

    static get formConfigurationFields() {
        return deepClone(CONFIG_FIELDS);
    }

    static get changeDebounceTime() {
        return this.#changeDebounceTime;
    }

    static set changeDebounceTime(value) {
        this.#changeDebounceTime = delimitInteger(value, 0, 1000);
    }

    #value;

    #tooltipEl;

    #labelTextEl;

    #resetEl;

    #descriptionEl;

    #errorEl;

    #validators = new Set();

    #errorList = new Set();

    #customValidity = "";

    constructor() {
        if (new.target === AbstractFormElement) {
            throw new Error("can not construct abstract class");
        }
        super();
        this.shadowRoot.append(TPL.generate());
        STYLE.apply(this.shadowRoot);
        /* --- */
        this.#tooltipEl = this.shadowRoot.getElementById("tooltip");
        this.#labelTextEl = this.shadowRoot.getElementById("label-text");
        this.#resetEl = this.shadowRoot.getElementById("reset");
        this.#descriptionEl = this.shadowRoot.getElementById("description");
        this.#errorEl = this.shadowRoot.getElementById("error");
        this.#resetEl.addEventListener("click", (event) => {
            event.stopPropagation();
            event.preventDefault();
            this.formResetCallback();
        });
        this.#resetEl.addEventListener("keydown", (event) => {
            if (event.keyCode === 13) {
                event.stopPropagation();
                event.preventDefault();
                this.formResetCallback();
            }
        });
        this.#errorEl.addEventListener("click", () => {
            this.focus();
        });
        /* --- */
        this.addEventListener("validity", (event) => {
            this.#errorEl.i18nContent = event.message ?? "";
        });
        this.addEventListener("invalid", (event) => {
            event.preventDefault();
        });
        /* --- */
        const fieldContainerEl = this.shadowRoot.getElementById("field-container");
        fieldContainerEl.addEventListener("input", (event) => {
            event.stopPropagation();
        });
        fieldContainerEl.addEventListener("change", (event) => {
            event.stopPropagation();
        });
    }

    connectedCallback() {
        const isDefault = this.isDefault;
        const value = this.value;
        if (!isDefault) {
            this.renderValue(value);
        }
        this.onDisplayValueChange(value);
        this.refreshFormValue();
        this.revalidate();
        this.#setResetActive(!isDefault);
    }

    formDisabledCallback(disabled) {
        this.#resetEl.disabled = disabled;
    }

    formResetCallback() {
        this.#value = undefined;
        const value = this.value;
        this.renderValue(value);
        this.onDisplayValueChange(value);
        this.refreshFormValue();
        this.revalidate();
        this.#setResetActive(false);
        /* --- */
        const event = new Event("default", {
            bubbles: true,
            cancelable: true
        });
        event.value = this.value;
        event.name = this.name;
        event.fieldId = this.id;
        this.dispatchEvent(event);
    }

    formStateRestoreCallback(state/* , mode */) {
        this.value = state;
    }

    getSubmitValue() {
        return this.value;
    }

    get isInitial() {
        return this.#value === undefined;
    }

    get isEmpty() {
        if (this.#value == null) {
            return true;
        }
        if (typeof this.#value === "string" && this.#value === "") {
            return true;
        }
        if (Array.isArray(this.#value) && this.#value.length === 0) {
            return true;
        }
        return false;
    }

    get isDefault() {
        return isEqual(this.rawValue, this.defaultValue);
    }

    set value(value) {
        this.#onUpdateValue(value);
    }

    get value() {
        if (this.#value === undefined) {
            return super.value;
        }
        return this.#value;
    }

    get rawValue() {
        return this.#value;
    }

    set name(value) {
        this.setAttribute("name", value);
    }

    get name() {
        return this.getAttribute("name");
    }

    set label(value) {
        this.setAttribute("label", value);
    }

    get label() {
        return this.getAttribute("label");
    }

    set tooltip(value) {
        this.setAttribute("tooltip", value);
    }

    get tooltip() {
        return this.getAttribute("tooltip");
    }

    set description(value) {
        this.setAttribute("description", value);
    }

    get description() {
        return this.getAttribute("description");
    }

    set required(value) {
        this.setBooleanAttribute("required", value);
    }

    get required() {
        return this.getBooleanAttribute("required");
    }

    set resettable(value) {
        this.setBooleanAttribute("resettable", value);
    }

    get resettable() {
        return this.getBooleanAttribute("resettable");
    }

    set readonly(value) {
        this.setBooleanAttribute("readonly", value);
    }

    get readonly() {
        return this.getBooleanAttribute("readonly");
    }

    set disabled(value) {
        this.setBooleanAttribute("disabled", value);
    }

    get disabled() {
        return this.getBooleanAttribute("disabled");
    }

    set hideErrors(value) {
        this.setBooleanAttribute("hideerrors", value);
    }

    get hideErrors() {
        return this.getBooleanAttribute("hideerrors");
    }

    set noHover(value) {
        this.setBooleanAttribute("nohover", value);
    }

    get noHover() {
        return this.getBooleanAttribute("nohover");
    }

    set noPad(value) {
        this.setBooleanAttribute("nopad", value);
    }

    get noPad() {
        return this.getBooleanAttribute("nopad");
    }

    set noValidate(value) {
        this.setBooleanAttribute("novalidate", value);
    }

    get noValidate() {
        return this.getBooleanAttribute("novalidate");
    }

    static get observedAttributes() {
        return [...super.observedAttributes, "value", "required", "label", "tooltip", "description", "novalidate"];
    }

    attributeChangedCallback(name, oldValue, newValue) {
        super.attributeChangedCallback(name, oldValue, newValue);
        switch (name) {
            case "value": {
                if (oldValue != newValue) {
                    const defaultValue = this.defaultValue;
                    this.applyValueAttribute(defaultValue);
                    if (this.isInitial) {
                        this.onDisplayValueChange(defaultValue);
                        this.refreshFormValue();
                        this.revalidate();
                        this.#setResetActive(false);
                    } else {
                        this.#setResetActive(!this.isDefault);
                    }
                }
            } break;
            case "required": {
                if (oldValue != newValue) {
                    this.revalidate();
                }
            } break;
            case "label": {
                if (oldValue != newValue) {
                    this.#labelTextEl.i18nValue = newValue;
                }
            } break;
            case "tooltip": {
                if (oldValue != newValue) {
                    this.#tooltipEl.i18nTooltip = newValue;
                }
            } break;
            case "description": {
                if (oldValue != newValue) {
                    this.#descriptionEl.i18nContent = newValue;
                }
            } break;
            case "novalidate": {
                if (oldValue != newValue) {
                    this.revalidate();
                }
            } break;
        }
    }

    #onUpdateValue(value) {
        if (!isEqual(this.value, value)) {
            this.#value = value;
            const newValue = this.value;
            this.renderValue(newValue);
            this.onDisplayValueChange(newValue);
            this.refreshFormValue();
            this.revalidate();
            this.#setResetActive(!this.isDefault);
            this.dispatchEvent(new Event("input", {
                bubbles: true,
                cancelable: true
            }));
            this.#notifyChange();
        }
    }

    #notifyChange = debounce((newValue) => {
        if (!this.#errorList.size) {
            const event = new Event("value", {
                bubbles: true,
                cancelable: true
            });
            event.value = newValue;
            event.name = this.name;
            event.fieldId = this.id;
            this.dispatchEvent(event);
        }
        this.dispatchEvent(new Event("change", {
            bubbles: true,
            cancelable: true
        }));
    }, AbstractFormElement.changeDebounceTime);

    async revalidate() {
        if (!this.noValidate) {
            const value = this.value;
            this.#errorList.clear();
            const internalMessage = this.checkValid();
            if (typeof internalMessage === "string" && internalMessage !== "") {
                this.#errorList.add(internalMessage);
            }
            const validations = [];
            for (const validator of this.#validators) {
                validations.push(this.#doValidation(validator, value));
            }
            await Promise.all(validations);
            this.#showErrors();
            return [...this.#errorList];
        }
        const message = this.validationMessage;
        return message.length > 0 ? [message] : [];
    }

    checkValid() {
        const value = this.value;
        if (this.required && !isValueSet(value)) {
            return "This field is required";
        }
        return "";
    }

    setCustomValidity(message) {
        if (message == null) {
            message = "";
        }
        if (typeof message === "string") {
            this.#customValidity = message.trim();
            this.#showErrors();
        }
    }

    #showErrors = debounce(() => {
        if (!this.noValidate) {
            const message = [this.#customValidity, ...this.#errorList].join("\n").trim();
            super.setCustomValidity(message);
        } else {
            super.setCustomValidity(this.#customValidity);
        }
    });

    get errors() {
        return [...this.#errorList];
    }

    addValidator(validator) {
        if (typeof validator === "function" && !this.#validators.has(validator)) {
            this.#validators.add(validator);
            this.revalidate();
        }
    }

    removeValidator(validator) {
        if (typeof validator === "function" && this.#validators.has(validator)) {
            this.#validators.delete(validator);
            this.revalidate();
        }
    }

    async #doValidation(validator, value) {
        const message = await validator(value);
        if (typeof message === "string" && message !== "") {
            this.#errorList.add(message);
        }
    }

    static scalarToValue(value) {
        value.toString();
    }

    static valueToScalar(value) {
        return value.toString();
    }

    formContextAssociatedCallback(/* formContext */) {
        // ignore
    }

    applyValueAttribute(value) {
        if (this.isInitial) {
            this.renderValue(value);
        }
    }

    renderValue(/* value */) {
        // ignore
    }

    onDisplayValueChange(/* value */) {
        // ignore
    }

    #setResetActive(value) {
        if (!value) {
            this.#resetEl.classList.add("inactive");
            this.#resetEl.setAttribute("tabindex", "-1");
            this.#resetEl.blur();
        } else {
            this.#resetEl.classList.remove("inactive");
            this.#resetEl.removeAttribute("tabindex");
        }
    }

}
