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

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

const TPL = new Template(`
    <input is="emc-i18n-input" id="input" i18n-placeholder="Search..." autocomplete="off">
    <div id="view" mode="view">
        <emc-i18n-label id="placeholder"></emc-i18n-label>
        <div id="value">
            <div id="token-container"></div>
            <div id="overflow-counter"></div>
        </div>
    </div>
    <div id="button"></div>
    <div id="scroll-container">
        <div id="options-container"></div>
        <emc-i18n-label id="empty" i18n-value="no entries"></emc-i18n-label>
        <emc-i18n-label id="nomatch" i18n-value="no match"></emc-i18n-label>
    </div>
    <select id="native-select" multiple>
        <option is="emc-i18n-option" id="native-empty" i18n-value="no entries" disabled></option>
    </select>
    <slot id="options-slot"></slot>
    
`);

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

const STYLE = new GlobalStyle(`
    #input {
        position: relative;
        flex-grow: 1;
        height: 2em;
        padding: 0px calc(2em + 7px) 0px 7px;
        color: var(--input-text-color, #000000);
        background-color: var(--input-back-color, #ffffff);
        background-image: var(--input-back-image, none);
        border-style: solid;
        border-width: var(--input-border-width, 2px);
        border-color: var(--input-border-color, #222222);
        border-radius: var(--input-border-radius, 6px);
        font-size: 1em;
        z-index: -100;
    }
    
    #input.active {
        z-index: 0;
    }
    
    #input:focus {
        box-shadow: 0 0 2px 3px var(--input-focus-color, #06b5ff);
        outline: none;
        z-index: 1;
    }
    
    :host(:invalid) #input {
        color: var(--input-invalid-text-color, #000000);
        background-color: var(--input-invalid-back-color, #ffebeb);
        background-image: var(--input-invalid-back-image, none);
        border-color: var(--input-invalid-border-color, #ff4444);
    }
    
    :host(:invalid) #input:focus {
        box-shadow: 0 0 2px 3px var(--input-invalid-focus-color, #ff2500);
    }
    
    #input::placeholder {
        color: var(--input-placeholder-text-color, #757575);
    }
    
    #input:disabled {
        color: var(--input-disabled-text-color, #757575) !important;
        background-color: var(--input-disabled-back-color, #999999) !important;
        background-image: var(--input-disabled-back-image, none) !important;
        border-color: var(--input-disabled-border-color, #6a6a6a) !important;
        cursor: default !important;
        opacity: 1 !important;
    }
    
    /* --- */
    #button {
        position: absolute;
        display: flex;
        align-items: center;
        justify-content: center;
        flex-shrink: 0;
        width: 2em;
        height: 2em;
        right: 0px;
        padding: 0px;
        margin: 0 0 0 calc(-1 * var(--input-border-width, 2px));
        background-color: transparent;
        border-style: solid;
        border-width: var(--input-border-width, 2px);
        border-color: transparent;
        border-radius: 0 var(--input-border-radius, 6px) var(--input-border-radius, 6px) 0;
        border-left-style: none;
        font-size: 1rem;
        font-weight: bold;
        pointer-events: none;
        z-index: 2;
    }
    
    #button:disabled {
        opacity: 1 !important;
    }
    
    #button::before {
        position: relative;
        display: block;
        border: solid 0.25em transparent;
        border-bottom-color: var(--input-text-color, #000000);
        border-right-color: var(--input-text-color, #000000);
        transform: translateY(-0.125em) rotate(45deg);
        content: " ";
    }
    
    :host(:invalid) #button::before {
        border-bottom-color: var(--input-invalid-text-color, #000000);
        border-right-color: var(--input-invalid-text-color, #000000);
    }
    
    :host(:disabled) #button::before {
        border-bottom-color: var(--input-disabled-text-color, #757575) !important;
        border-right-color: var(--input-disabled-text-color, #757575) !important;
    }
    
    #button[readonly]:not([readonly="false"]) {
        opacity: 0.3;
    }
    
    /* --- */
    #view {
        position: absolute;
        display: flex;
        align-items: center;
        justify-content: flex-start;
        flex-grow: 1;
        top: 0;
        left: 0;
        bottom: 0;
        right: 0;
        padding: 0;
        color: var(--input-text-color, #000000);
        background-color: var(--input-back-color, #ffffff);
        background-image: var(--input-back-image, none);
        border-style: solid;
        border-width: var(--input-border-width, 2px);
        border-color: var(--input-border-color, #222222);
        border-radius: var(--input-border-radius, 6px);
        font-size: 1em;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        cursor: pointer;
        user-select: none;
    }
    
    :host(:invalid) #view {
        color: var(--input-invalid-text-color, #000000);
        background-color: var(--input-invalid-back-color, #ffebeb);
        background-image: var(--input-invalid-back-image, none);
        border-color: var(--input-invalid-border-color, #ff4444);
    }
    
    :host(:invalid) #view:focus {
        box-shadow: 0 0 2px 3px var(--input-invalid-focus-color, #ff2500);
    }
    
    #input:focus + #view {
        z-index: 1;
    }
    
    #input.active + #view {
        display: none;
    }
    
    #input[readonly]:not([readonly="false"]) + #view {
        cursor: default;
    }
    
    #input:disabled + #view {
        color: var(--input-disabled-text-color, #757575) !important;
        background-color: var(--input-disabled-back-color, #999999) !important;
        background-image: var(--input-disabled-back-image, none) !important;
        border-color: var(--input-disabled-border-color, #6a6a6a) !important;
        cursor: default !important;
        opacity: 1 !important;
        pointer-events: none !important;
    }
    
    /* --- */
    #placeholder,
    #value {
        height: 100%;
        width: 100%;
        justify-content: flex-start;
        border-radius: var(--input-border-radius, 6px);
    }
    
    #placeholder {
        padding: 0px calc(2em + 7px) 0px 7px;
        color: var(--input-placeholder-text-color, #757575);
    }
    
    #value {
        display: flex;
        padding: 1px calc(2em + 1px) 1px 1px;
        user-select: text;
        pointer-events: all;
        overflow: hidden;
    }
    
    /* --- */
    #scroll-container {
        position: fixed;
        display: none;
        max-height: 300px;
        margin-top: calc(-1 * var(--input-border-width, 2px));
        overflow-x: hidden;
        overflow-y: auto;
        color: var(--input-text-color, #000000);
        background-color: var(--input-back-color, #ffffff);
        background-image: var(--input-back-image, none);
        border-style: solid;
        border-width: var(--input-border-width, 2px);
        border-color: var(--input-border-color, #222222);
        border-radius: var(--input-border-radius, 6px);
        z-index: var(--z-index-element-popups, 1000);
        box-shadow: #000000 4px 4px 8px -4px;
    }
    
    #options-container {
        display: block;
        width: 100%;
    }
    
    /* --- */
    emc-select-entry {
        display: flex;
        align-items: center;
        white-space: normal;
        color: var(--select-option-text-color, #000000);
        background-color: var(--select-option-back-color, #ffffff);
        background-image: var(--select-option-back-image, none);
        font-size: 1rem;
        cursor: pointer;
    }
    
    emc-select-entry:not(:last-child) {
        border-bottom: solid 1px var(--select-option-divider-color, #949494);
    }
    
    emc-select-entry:hover,
    emc-select-entry.marked {
        color: var(--select-option-marked-text-color, #000000);
        background-color: var(--select-option-marked-back-color, #b1f4ff);
        background-image: var(--select-option-marked-back-image, none);
    }
    
    /* --- */
    #empty,
    #nomatch {
        display: none;
        align-items: center;
        justify-content: center;
        font-style: italic;
        min-height: 2rem;
        padding: 5px;
        margin: 5px 2px;
        white-space: normal;
    }
    
    #native-empty {
        font-style: italic;
        text-align: center;
    }
    
    .hidden,
    #options-slot {
        display: none !important;
    }
    
    /* --- */
    #native-select {
        display: none;
        flex-grow: 1;
        height: 2em;
        padding: 0px calc(2em + 7px) 0px 7px;
        color: var(--input-text-color, #000000);
        background-color: var(--input-back-color, #ffffff);
        background-image: var(--input-back-image, none);
        border-style: solid;
        border-width: var(--input-border-width, 2px);
        border-color: var(--input-border-color, #222222);
        border-radius: var(--input-border-radius, 6px);
        font-size: 1em;
    }
    
    #native-select:focus {
        box-shadow: 0 0 2px 3px var(--input-focus-color, #06b5ff);
        outline: none;
        z-index: 1;
    }
    
    :host(:invalid) #native-select {
        background-color: var(--input-invalid-back-color, #ffebeb);
        background-image: var(--input-invalid-back-image, none);
        border-color: var(--input-invalid-border-color, #ff4444);
    }
    
    :host(:invalid) #native-select:focus {
        box-shadow: 0 0 2px 3px var(--input-invalid-focus-color, #ff2500);
    }
    
    #native-select::placeholder {
        color: var(--input-placeholder-text-color, #757575);
    }
    
    #native-select:disabled {
        color: var(--input-disabled-text-color, #757575) !important;
        background-color: var(--input-disabled-back-color, #999999) !important;
        background-image: var(--input-disabled-back-image, none) !important;
        border-color: var(--input-disabled-border-color, #6a6a6a) !important;
        cursor: default !important;
        opacity: 1 !important;
    }
    
    @media (hover: none) {
    
        #input,
        #view {
            display: none;
        }
    
        #native-select {
            display: block;
        }
    }
    
    /* --- */
    #value .token {
        padding: 2px 2px 2px 6px;
        margin: 1px;
        color: var(--button-text-color, #000000);
        background-color: var(--button-back-color, #eaeaea);
        background-image: var(--button-back-image, none);
        border-style: solid;
        border-width: var(--button-border-width, 2px);
        border-color: var(--button-border-color, #222222);
        border-radius: var(--button-border-radius, 6px);
        font-size: 1em;
        line-height: 1em;
        white-space: pre;
        word-break: keep-all;
        cursor: pointer;
        pointer-events: all;
    }
    
    #value .token:hover {
        color: var(--button-primary-hover-text-color, #ffffff);
        background-color: var(--button-primary-hover-back-color, #353935);
        background-image: var(--button-primary-hover-back-image, none);
        border-color: var(--button-primary-hover-border-color, #000000);
    }
    
    #value .token::after {
        margin-left: 4px;
        content: "🗙";
    }
    
    #token-container {
        flex-grow: 1;
        overflow: hidden;
    }
    
    #overflow-counter {
        display: flex;
        align-items: center;
        flex-shrink: 0;
        height: 100%;
        padding-left: 8px;
        margin-left: auto;
    }
    
`);

const CONFIG_FIELDS = [
    {
        "type": "StringInput",
        "name": "placeholder",
        "label": "Placeholder",
        "value": ""
    },
    {
        "type": "SwitchInput",
        "name": "multiple",
        "label": "Multiple",
        "value": false
    },
    {
        "type": "SwitchInput",
        "name": "chooseonly",
        "label": "Choose only",
        "value": false
    },
    {
        "type": "StringInput",
        "name": "tokengroup",
        "label": "Token Group",
        "value": ""
    }
]
;

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

import AbstractFormElement from "../../AbstractFormElement.js";
import ResizeObserverMixin from "../../../../mixin/ResizeObserverMixin.js";
import FormElementRegistry from "../../../../../data/registry/form/FormElementRegistry.js";
import BusyIndicatorManager from "../../../../../util/BusyIndicatorManager.js";
import EventTargetManager from "../../../../../util/event/EventTargetManager.js";
import EventMultiTargetManager from "../../../../../util/event/EventMultiTargetManager.js";
import i18n from "../../../../../util/I18n.js";
import CharacterSearch from "../../../../../util/search/CharacterSearch.js";
import {deepClone} from "../../../../../util/helper/DeepClone.js";
import {nodeTextComparator} from "../../../../../util/helper/ui/NodeListSort.js";
import {debounce} from "../../../../../util/Debouncer.js";
import {registerFocusable} from "../../../../../util/helper/html/ElementFocusHelper.js";
import {safeSetAttribute} from "../../../../../util/helper/ui/NodeAttributes.js";
import MutationObserverManager from "../../../../../util/observer/manager/MutationObserverManager.js";
import TokenSelectedElementManager from "./manager/TokenSelectedElementManager.js";
import I18nOption from "../../../../i18n/builtin/I18nOption.js";
import SelectEntryManager from "../../components/SelectEntryManager.js";
import I18nOptionManager from "../../components/I18nOptionManager.js";
import "../../../../i18n/builtin/I18nInput.js";
import "../../../../i18n/I18nLabel.js";
import jsonParse from "../../../../../patches/JSONParser.js";

const ESCAPE_KEYS = [
    "Tab",
    "Escape"
];

const MUTATION_CONFIG = {
    attributes: true,
    characterData: true,
    attributeFilter: ["value", "label"]
};

// TODO add manage token option (?)
// TODO add token usage detection (?)
export default class TokenSelect extends ResizeObserverMixin(AbstractFormElement) {

    static get formConfigurationFields() {
        return [...super.formConfigurationFields, ...deepClone(CONFIG_FIELDS)];
    }

    static get changeDebounceTime() {
        return 0;
    }

    #isEditMode = false;

    #fieldEl;

    #inputEl;

    #nativeSelectEl;

    #nativeEmptyEl;

    #viewEl;

    #valueEl;

    #tokenContainerEl;

    #overflowCounterEl;

    #buttonEl;

    #placeholderEl;

    #scrollContainerEl;

    #optionsContainerEl;

    #emptyEl;

    #nomatchEl;

    #optionsSlotEl;

    #optionSelectEventManager = new EventMultiTargetManager();

    #i18nEventManager = new EventTargetManager(i18n);

    #mutationObserver = new MutationObserverManager(MUTATION_CONFIG, () => {
        this.#onSlotChange();
    });

    #tokenSelectedManager;

    #selectEntryManager;

    #i18nOptionManager;

    constructor() {
        super();
        this.#fieldEl = this.shadowRoot.getElementById("field");
        this.#fieldEl.append(TPL.generate());
        STYLE.apply(this.shadowRoot);
        /* --- */
        this.#optionSelectEventManager.set("mousedown", (event) => {
            event.preventDefault();
            event.stopPropagation();
        });
        this.#optionSelectEventManager.set("click", (event) => {
            const el = event.currentTarget;
            const value = el.getAttribute("value");
            this.#toggleToken(value, el);
            event.preventDefault();
            event.stopPropagation();
        });
        this.#optionSelectEventManager.set("mouseover", () => {
            const marked = this.#optionsContainerEl.querySelector(".marked");
            if (marked != null) {
                marked.classList.remove("marked");
            }
        });
        /* --- */
        this.#inputEl = this.shadowRoot.getElementById("input");
        this.#nativeSelectEl = this.shadowRoot.getElementById("native-select");
        this.#nativeEmptyEl = this.shadowRoot.getElementById("native-empty");
        this.#valueEl = this.shadowRoot.getElementById("value");
        this.#viewEl = this.shadowRoot.getElementById("view");
        this.#tokenContainerEl = this.shadowRoot.getElementById("token-container");
        this.#overflowCounterEl = this.shadowRoot.getElementById("overflow-counter");
        this.#emptyEl = this.shadowRoot.getElementById("empty");
        this.#nomatchEl = this.shadowRoot.getElementById("nomatch");
        this.#placeholderEl = this.shadowRoot.getElementById("placeholder");
        this.#scrollContainerEl = this.shadowRoot.getElementById("scroll-container");
        this.#buttonEl = this.shadowRoot.getElementById("button");
        this.#optionsContainerEl = this.shadowRoot.getElementById("options-container");
        this.#optionsSlotEl = this.shadowRoot.getElementById("options-slot");
        this.#optionsSlotEl.addEventListener("slotchange", () => {
            this.#onSlotChange();
        });
        /* --- */
        this.#scrollContainerEl.addEventListener("mousedown", (event) => {
            event.stopPropagation();
        });
        this.#scrollContainerEl.addEventListener("click", () => {
            this.focus();
        });
        /* --- */
        this.#viewEl.addEventListener("click", (event) => {
            if (!this.readonly && !this.#isEditMode) {
                this.#startEditMode();
                event.preventDefault();
                event.stopPropagation();
            }
        });
        this.#inputEl.addEventListener("click", (event) => {
            if (!this.readonly && !this.#isEditMode) {
                this.#startEditMode();
                event.preventDefault();
                event.stopPropagation();
            }
        });
        this.#inputEl.addEventListener("mousedown", (event) => {
            event.stopPropagation();
        });
        this.#inputEl.addEventListener("keydown", (event) => {
            if (!this.getBooleanAttribute("readonly")) {
                if (!this.#isEditMode) {
                    const {key} = event;
                    if (key === "Enter") {
                        this.#startEditMode();
                        event.preventDefault();
                        event.stopPropagation();
                    }
                } else {
                    const {key} = event;
                    if (key === "ArrowUp") {
                        this.#moveMarker(true);
                        event.preventDefault();
                        event.stopPropagation();
                    } else if (key === "ArrowDown") {
                        this.#moveMarker(false);
                        event.preventDefault();
                        event.stopPropagation();
                    } else if (key === "Enter") {
                        this.#createToken();
                        event.preventDefault();
                        event.stopPropagation();
                    } else if (key === " ") {
                        this.#toggleMarkedToken();
                        event.preventDefault();
                        event.stopPropagation();
                    } else if (ESCAPE_KEYS.includes(key)) {
                        this.#stopEditMode();
                        event.preventDefault();
                        event.stopPropagation();
                    }
                }
            }
        });
        this.#inputEl.addEventListener("blur", (event) => {
            if (event.relatedTarget != null && !event.relatedTarget.contains(this.#inputEl)) {
                this.#cancelSelection();
            }
            event.stopPropagation();
        });
        this.#inputEl.addEventListener("input", () => {
            if (!this.#isEditMode) {
                this.#startEditMode(true);
            }
            this.#applySearch();
        }, true);
        this.#scrollContainerEl.addEventListener("wheel", (event) => {
            event.stopPropagation();
        }, {passive: true});
        /* --- */
        this.#nativeSelectEl.addEventListener("mousedown", (event) => {
            if (this.readonly) {
                event.preventDefault();
                event.stopPropagation();
            }
        });
        this.#nativeSelectEl.addEventListener("change", () => {
            this.value = this.#nativeSelectEl.value;
        });
        /* --- */
        window.addEventListener("wheel", () => {
            if (this.#isEditMode) {
                this.#cancelSelection();
            }
        }, {passive: true});
        window.addEventListener("blur", () => {
            if (this.#isEditMode) {
                this.#cancelSelection();
            }
        }, {passive: true});
        window.addEventListener("mousedown", (event) => {
            if (!this.readonly && this.#isEditMode) {
                if (!this.#fieldEl.contains(event.target)) {
                    this.#cancelSelection();
                } else {
                    this.focus();
                }
            }
        }, {passive: true});
        /* --- */
        this.#tokenSelectedManager = new TokenSelectedElementManager(this.#tokenContainerEl);
        this.#tokenSelectedManager.registerSortFunction(this.#sortByNameFunction);
        this.#tokenSelectedManager.addEventListener("afterrender", () => {
            this.#handleOverflowItems();
        });
        this.#selectEntryManager = new SelectEntryManager(this.#optionsContainerEl, this.#optionSelectEventManager);
        this.#selectEntryManager.registerSortFunction(this.#sortByNameFunction);
        this.#selectEntryManager.addEventListener("afterrender", () => {
            this.#refreshSelect(this.#optionsContainerEl);
            this.renderValue(this.value);
        });
        this.#i18nOptionManager = new I18nOptionManager(this.#nativeSelectEl);
        this.#i18nOptionManager.registerSortFunction(this.#sortByNameFunction);
        this.#i18nOptionManager.addEventListener("afterrender", () => {
            this.#refreshSelect(this.#nativeSelectEl);
        });
        /* --- */
        this.#i18nEventManager.set("language", () => {
            this.#selectEntryManager.sort();
            this.#i18nOptionManager.sort();
            this.#tokenSelectedManager.sort();
        });
        this.#i18nEventManager.set("translation", () => {
            this.#selectEntryManager.sort();
            this.#i18nOptionManager.sort();
            this.#tokenSelectedManager.sort();
        });
    }

    resizeCallback() {
        this.#tokenSelectedManager.rerender();
        super.resizeCallback();
    }

    formDisabledCallback(disabled) {
        super.formDisabledCallback(disabled);
        this.#inputEl.disabled = disabled;
        this.#nativeSelectEl.disabled = disabled;
        this.#buttonEl.disabled = disabled;
    }

    focus(options) {
        const mediaQuery = window.matchMedia("(hover: none)");
        if (mediaQuery.matches) {
            this.#nativeSelectEl.focus(options);
        } else {
            this.#inputEl.focus(options);
        }
    }

    set defaultValue(value) {
        this.setJSONAttribute("value", value);
    }

    get defaultValue() {
        return this.getJSONAttribute("value") ?? [];
    }

    set value(value) {
        if (value == null) {
            value = [];
        }
        if (typeof value === "string") {
            value = jsonParse(value);
        }
        if (!Array.isArray(value)) {
            throw new TypeError("value must be an array or null");
        }
        if (!this.chooseonly) {
            for (const v of value) {
                const el = this.#optionsContainerEl.querySelector(`[value="${v}"]`);
                if (el == null) {
                    const optionEl = I18nOption.create();
                    optionEl.value = v;
                    optionEl.i18nValue = v;
                    this.append(optionEl);
                }
            }
        } else {
            value = value.filter((v) => {
                const el = this.#optionsContainerEl.querySelector(`[value="${v}"]`);
                return el != null;
            });
        }
        super.value = value;
    }

    get value() {
        return super.value;
    }

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

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

    set multiple(val) {
        this.setBooleanAttribute("multiple", val);
    }

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

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

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

    static get observedAttributes() {
        return [...super.observedAttributes, "placeholder", "readonly"];
    }

    attributeChangedCallback(name, oldValue, newValue) {
        super.attributeChangedCallback(name, oldValue, newValue);
        switch (name) {
            case "placeholder": {
                if (oldValue != newValue) {
                    safeSetAttribute(this.#placeholderEl, "i18n-value", newValue);
                }
            } break;
            case "readonly": {
                if (oldValue != newValue) {
                    safeSetAttribute(this.#inputEl, "readonly", newValue);
                    safeSetAttribute(this.#buttonEl, "readonly", newValue);
                }
            } break;
            case "multiple": {
                if (oldValue != newValue) {
                    if (!this.multiple) {
                        const value = this.value;
                        if (value.length > 1) {
                            this.value = [value[0]];
                        }
                    }
                }
            } break;
        }
    }

    renderValue(value) {
        if (value != null && value.length > 0) {
            const data = [];
            for (const val of value ?? []) {
                const selectedEl = this.#optionsContainerEl.querySelector(`[value="${val}"]`);
                if (selectedEl != null) {
                    data.push({
                        key: val,
                        label: selectedEl.label ?? selectedEl.innerText,
                        tokenAction: this.#tokenAction
                    });
                }
            }
            this.#valueEl.classList.remove("hidden");
            this.#placeholderEl.classList.add("hidden");
            this.#tokenSelectedManager.manage(data);
        } else {
            this.#nativeSelectEl.value = "";
            this.#tokenContainerEl.innerHTML = "";
            this.#valueEl.classList.add("hidden");
            this.#placeholderEl.classList.remove("hidden");
        }
    }

    #cancelSelection() {
        this.renderValue(this.value);
        this.#stopEditMode();
    }

    #startEditMode(keepSearchInput = false) {
        if (!this.readonly) {
            this.#isEditMode = true;
            if (!keepSearchInput) {
                this.#inputEl.value = "";
            }
            this.#inputEl.classList.add("active");
            this.focus();
            /* --- */
            const thisRect = this.#fieldEl.getBoundingClientRect();
            this.#scrollContainerEl.style.display = "block";
            this.#scrollContainerEl.style.left = `${thisRect.left}px`;
            this.#scrollContainerEl.style.width = `${thisRect.width}px`;
            this.#scrollContainerEl.style.zIndex = 200;
            const containerRect = this.#scrollContainerEl.getBoundingClientRect();
            if (thisRect.bottom + containerRect.height > window.innerHeight - 25) {
                this.#scrollContainerEl.style.bottom = `${window.innerHeight - thisRect.top}px`;
            } else {
                this.#scrollContainerEl.style.top = `${thisRect.bottom}px`;
            }
            const all = this.#optionsContainerEl.querySelectorAll(`[value]`);
            const value = this.value;
            for (const el of all) {
                el.style.display = "";
                if (value != null && value.includes(el.value)) {
                    el.selected = true;
                } else {
                    el.selected = false;
                }
            }
        }
    }

    #createToken() {
        const value = this.#inputEl.value;
        this.#inputEl.value = "";
        this.#nomatchEl.style.display = "";
        if (value != null && value !== "") {
            const valueBuffer = new Set(this.value);
            if (this.chooseonly) {
                const el = this.#optionsContainerEl.querySelector(`[value="${value}"]`);
                if (el != null) {
                    if (this.multiple) {
                        valueBuffer.add(value);
                    } else {
                        valueBuffer.clear();
                        valueBuffer.add(value);
                    }
                }
            } else if (this.multiple) {
                valueBuffer.add(value);
            } else {
                valueBuffer.clear();
                valueBuffer.add(value);
            }
            this.value = Array.from(valueBuffer);
        }
        this.#applySearch();
        this.focus();
    }

    #stopEditMode() {
        this.#isEditMode = false;
        this.#inputEl.classList.remove("active");
        this.#inputEl.value = "";
        this.#nomatchEl.style.display = "";
        /* --- */
        this.#scrollContainerEl.style.display = "";
        this.#scrollContainerEl.style.bottom = "";
        this.#scrollContainerEl.style.top = "";
        this.#scrollContainerEl.style.zIndex = "";
        const all = this.querySelectorAll(`[value]`);
        for (const el of all) {
            el.style.display = "";
        }
        const marked = this.#optionsContainerEl.querySelector(".marked");
        if (marked != null) {
            marked.classList.remove("marked");
        }
        this.#tokenSelectedManager.rerender();
    }

    #handleOverflowItems() {
        const els = [...this.#tokenContainerEl.children];
        let overflowItems = 0;
        this.#overflowCounterEl.innerText = "";
        for (let i = 0; i < els.length; ++i) {
            const el = els[i];
            if (el != null) {
                const containerWidth = this.#tokenContainerEl.clientWidth;
                const elLeft = el.offsetLeft;
                const elWidth = el.offsetWidth;
                const elRight = elLeft + elWidth;
                if (elRight > containerWidth) {
                    el.remove();
                    overflowItems++;
                    i -= 2;
                    this.#overflowCounterEl.innerText = `+${overflowItems}`;
                }
            }
        }
    }

    #moveMarker(modeUp = false) {
        const marked = this.#optionsContainerEl.querySelector(".marked");
        const el = this.#switchOption(marked, modeUp);
        if (el != null) {
            if (marked != null) {
                marked.classList.remove("marked");
            }
            el.classList.add("marked");
            const scrollOffset = this.#scrollContainerEl.offsetTop;
            const scrollHeight = this.#scrollContainerEl.offsetHeight;
            const targetOffset = el.offsetTop - scrollOffset;
            const targetHeight = el.offsetHeight;
            if (this.#scrollContainerEl.scrollTop > targetOffset - 20) {
                this.#scrollContainerEl.scrollTop = targetOffset - 20;
            } else if (this.#scrollContainerEl.scrollTop < targetOffset + targetHeight - (scrollHeight - 20)) {
                this.#scrollContainerEl.scrollTop = targetOffset + targetHeight - (scrollHeight - 20);
            }
        }
    }

    #switchOption(oldEl, modeUp = false) {
        let nextEl;
        if (oldEl != null) {
            if (modeUp) {
                nextEl = this.#getPrevOption(oldEl);
                if (nextEl == null && oldEl.style.display === "none") {
                    nextEl = this.#getNextOption(oldEl);
                }
            } else {
                nextEl = this.#getNextOption(oldEl);
                if (nextEl == null && oldEl.style.display === "none") {
                    nextEl = this.#getPrevOption(oldEl);
                }
            }
        } else {
            nextEl = this.#optionsContainerEl.querySelector(`[value="${this.value ?? ""}"]`);
            if (nextEl == null || nextEl.style.display === "none") {
                nextEl = this.#getFirstOption();
            }
        }
        return nextEl;
    }

    #getFirstOption() {
        let nextEl = this.#optionsContainerEl.firstElementChild;
        while (nextEl != null && (nextEl.style.display === "none" || !nextEl.matches("[value]"))) {
            nextEl = nextEl.nextElementSibling;
        }
        if (nextEl != null && nextEl.style.display !== "none" && nextEl.matches("[value]")) {
            return nextEl;
        }
    }

    #getPrevOption(oldEl) {
        let nextEl = oldEl.previousElementSibling;
        while (nextEl != null && (nextEl.style.display === "none" || !nextEl.matches("[value]"))) {
            nextEl = nextEl.previousElementSibling;
        }
        if (nextEl != null && nextEl.style.display !== "none" && nextEl.matches("[value]")) {
            return nextEl;
        }
    }

    #getNextOption(oldEl) {
        let nextEl = oldEl.nextElementSibling;
        while (nextEl != null && (nextEl.style.display === "none" || !nextEl.matches("[value]"))) {
            nextEl = nextEl.nextElementSibling;
        }
        if (nextEl != null && nextEl.style.display !== "none" && nextEl.matches("[value]")) {
            return nextEl;
        }
    }

    async #resolveSlottedElements() {
        await BusyIndicatorManager.busy();
        const data = [];
        const optionNodeList = this.#optionsSlotEl.assignedElements({flatten: true}).filter((el) => el.matches("option"));
        /* --- */
        const oldNodes = new Set(this.#mutationObserver.getObservedNodes());
        const newNodes = new Set();
        for (const el of optionNodeList) {
            data.push({
                key: el.value || el.innerText,
                label: el.i18nValue || el.label || el.innerText
            });
            /* --- */
            if (oldNodes.has(el)) {
                oldNodes.delete(el);
            } else {
                newNodes.add(el);
            }
        }
        for (const node of oldNodes) {
            this.#mutationObserver.unobserve(node);
        }
        for (const node of newNodes) {
            this.#mutationObserver.observe(node);
        }
        /* --- */
        this.#selectEntryManager.manage(data);
        this.#i18nOptionManager.manage(data);
        /* --- */
        if (newNodes.size > 0) {
            this.#emptyEl.style.display = "";
            this.#nativeEmptyEl.remove();
        } else if (this.#nativeSelectEl.children.length <= 0) {
            this.#emptyEl.style.display = "flex";
            this.#nativeSelectEl.append(this.#nativeEmptyEl);
        }
        /* --- */
        this.renderValue(this.value);
        await BusyIndicatorManager.unbusy();
    }

    #onSlotChange = debounce(() => {
        this.#resolveSlottedElements();
    });

    #tokenAction = (event) => {
        const el = event.currentTarget;
        if (el != null) {
            const value = el.dataset.value;
            this.#toggleToken(value, el);
        }
        event.stopPropagation();
    };

    #toggleMarkedToken() {
        const el = this.#optionsContainerEl.querySelector(".marked");
        if (el != null) {
            const value = el.getAttribute("value");
            this.#toggleToken(value, el);
        }
    }

    #toggleToken(value, toggledEl) {
        if (!this.readonly) {
            const valueBuffer = new Set(this.value);
            if (this.multiple) {
                if (valueBuffer.has(value)) {
                    valueBuffer.delete(value);
                    toggledEl.selected = false;
                } else {
                    valueBuffer.add(value);
                    toggledEl.selected = true;
                }
            } else if (valueBuffer.has(value)) {
                valueBuffer.delete(value);
                toggledEl.selected = false;
            } else {
                valueBuffer.clear();
                valueBuffer.add(value);
                const currentEls = this.#optionsContainerEl.querySelectorAll("[selected]:not([selected=false])");
                for (const el of currentEls) {
                    el.selected = false;
                }
                toggledEl.selected = true;
            }
            this.value = Array.from(valueBuffer);
        }
    }

    #sortByNameFunction(entry0, entry1) {
        const {element: el0} = entry0;
        const {element: el1} = entry1;
        return nodeTextComparator(el0, el1);
    }

    #refreshSelect(containerEl) {
        const all = containerEl.querySelectorAll(`[value]`);
        const value = this.value;
        for (const el of all) {
            el.style.display = "";
            if (value != null && value.includes(el.value)) {
                el.selected = true;
            } else {
                el.selected = false;
            }
        }
    }

    #applySearch = debounce(() => {
        const all = this.#optionsContainerEl.children;
        const regEx = new CharacterSearch(this.#inputEl.value);
        const elCount = all.length;
        if (elCount > 0) {
            let hiddenCount = 0;
            for (const el of all) {
                const testText = el.comparatorText ?? el.innerText;
                if (regEx.test(testText.trim())) {
                    el.style.display = "";
                } else {
                    el.style.display = "none";
                    hiddenCount++;
                }
            }
            if (elCount <= hiddenCount) {
                this.#nomatchEl.style.display = "flex";
            } else {
                this.#nomatchEl.style.display = "";
            }
        }
    }, AbstractFormElement.changeDebounceTime);

    static fromConfig(config) {
        const selectEl = new TokenSelect();
        const {
            options = {}, ...params
        } = config;

        for (const value in options) {
            const optionEl = I18nOption.create();
            optionEl.value = value;
            const label = options[value];
            if (typeof label === "string" && label !== "") {
                optionEl.i18nValue = label;
            } else if (value !== "") {
                optionEl.i18nValue = value;
            }
            selectEl.append(optionEl);
        }

        for (const name in params) {
            const value = params[name];
            safeSetAttribute(selectEl, name, value);
        }

        return selectEl;
    }

}

FormElementRegistry.register("TokenSelect", TokenSelect);
customElements.define("emc-select-token", TokenSelect);
registerFocusable("emc-select-token");
