/*
 * @package         RadicalMart Shipping Plugin ECLCDEK
 * @subpackage   RadicalMart Shipping Plugin ECLCDEK
 * @version           1.0.2
 * @author            ECL <info@econsultlab.ru>
 * @link                 https://econsultlab.ru
 * @copyright      Copyright © 2026 ECL All Rights Reserved
 * @license           http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/**
 * Класс работы с joomla-fancy-select
 * @since 1.0.2
 */
class ECLChoices {
    /**
     * Конструктор класса
     *
     * @param fieldSelector Селектор поля select
     *
     * @since 1.0.2
     *
     * @private
     */
    constructor(fieldSelector) {
        /**
         * Поле select
         * @type {HTMLAnchorElement | HTMLElement | HTMLAreaElement | HTMLAudioElement | HTMLBaseElement | HTMLQuoteElement | HTMLBodyElement | HTMLBRElement | HTMLButtonElement | HTMLCanvasElement | HTMLTableCaptionElement | HTMLTableColElement | HTMLDataElement | HTMLDataListElement | HTMLModElement | HTMLDetailsElement | HTMLDialogElement | HTMLDivElement | HTMLDListElement | HTMLEmbedElement | HTMLFieldSetElement | HTMLFormElement | HTMLHeadingElement | HTMLHeadElement | HTMLHRElement | HTMLHtmlElement | HTMLIFrameElement | HTMLImageElement | HTMLInputElement | HTMLLabelElement | HTMLLegendElement | HTMLLIElement | HTMLLinkElement | HTMLMapElement | HTMLMenuElement | HTMLMetaElement | HTMLMeterElement | HTMLObjectElement | HTMLOListElement | HTMLOptGroupElement | HTMLOptionElement | HTMLOutputElement | HTMLParagraphElement | HTMLPictureElement | HTMLPreElement | HTMLProgressElement | HTMLScriptElement | HTMLSelectElement | HTMLSlotElement | HTMLSourceElement | HTMLSpanElement | HTMLStyleElement | HTMLTableElement | HTMLTableSectionElement | HTMLTableCellElement | HTMLTemplateElement | HTMLTextAreaElement | HTMLTimeElement | HTMLTitleElement | HTMLTableRowElement | HTMLTrackElement | HTMLUListElement | HTMLVideoElement}
         * @since 1.0.2
         */
        this.field = document.querySelector(fieldSelector);
        /**
         * Элемент joomla-fancy-select
         * @type {Element | undefined}
         * @since 1.0.2
         */
        this.fancySelect = this.field?.closest('joomla-field-fancy-select');
        /**
         * Инстанс Choices
         * @since 1.0.2
         */
        this.choices = this.fancySelect?.choicesInstance;
        /**
         * Блок опций поля
         * @type {Element|null}
         * @since 1.0.2
         */
        this.dropdown = this.fancySelect ? this.fancySelect.querySelector('.choices__list--dropdown') : null;
        this.hiddenSelect = this.fancySelect ? this.fancySelect.querySelector('select') : null;
        /**
         * Флаг использования поля joomla-fancy-select для данного элемента
         * @type {boolean}
         * @since 1.0.2
         */
        this.isUsed = this.#validateUsed();
        /**
         * Флаг использования поля для поля joomla-fancy-select api Choices.js
         * @type {boolean}
         * @since 1.0.2
         */
        this.isChoices = this.#validateChoices();
    }

    /**
     * Обновляет список опций
     * Формат списка
     * [
     *     {
     *       value: value,
     *       label: text,
     *       selected: true|false,
     *       disabled: true|false
     *     },
     *     {...},
     * ]
     *
     * @param {Array} options   Список опций
     * @param {string} value Наименование поля значения
     * @param {string} label Наименование поля текста
     * @param {boolean} replaceChoices    Заменять опции
     * @returns {boolean}
     *
     * @public
     *
     * @since 1.0.3
     */
    updateChoices(options, value, label, replaceChoices) {
        try {
            this.choices.setChoices(options, value, label, replaceChoices);
            return true;
        } catch (error) {
            console.error('Choices ошибка:', error);
            return false;
        }
    }

    /**
     * Создает класс, если не используется joomla-fancy-select, то возвращает null
     *
     * @param fieldSelector Селектор поля select
     * @returns {ECLChoices|null}
     * @constructor
     *
     * @since 1.0.2
     */
    static Create(fieldSelector) {
        let _ech = new ECLChoices(fieldSelector);
        return _ech.isUsed ? _ech : null;
    }

    /**
     * Проверка на использование joomla-fancy-select
     * @returns {boolean}
     * @private
     * @since 1.0.2
     */
    #validateUsed() {
        return this.fancySelect !== null && this.field !== null;
    }

    /**
     * Проверка на использование api Choices.js
     * @returns {boolean}
     * @private
     * @since 1.0.2
     */
    #validateChoices() {
        if (!this.fancySelect) {
            console.error('fancy-select не найден');
            return false;
        }

        if (!this.choices) {
            console.warn('Choices инстанс не найден');
            return false;
        }

        if (!this.choices.containerOuter) {
            console.warn('Choices containerOuter не определен');
            return false;
        }

        if (!this.hiddenSelect) {
            console.warn('Скрытый select не найден');
            return true;
        }
        return true;
    }

    /**
     * Безопасное удаление всех опций
     * @param defaultValue Значение опции, которое не удаляется
     * @returns {*|boolean}
     * @public
     * @since 1.0.2
     */
    removeOptions(defaultValue = null) {
        if (!this.isUsed) return null;
        if (!this.isChoices) {
            return this.#fallbackRemoveOptions(defaultValue);
        }
        return this.#removeChoicesOptions(defaultValue);
    }

    /**
     * Удаление всех опций через Choices
     * @param defaultValue
     * @returns {*}
     * @private
     * @since 1.0.2
     */
    #removeChoicesOptions(defaultValue = null) {
        try {
            let _default = [];
            if (defaultValue !== null)
                _default = this.#getOptionByValue("");
            // Метод 1: Установить пустое значение
            this.choices.setValue([]);

            // Метод 2: Удалить активные элементы
            this.choices.removeActiveItems();

            // Метод 3: Сбросить к дефолтному состоянию
            this.choices.clearStore();

            _default.forEach(opt => {
                this.addOption(opt.value, opt.label, false, false, false);
                this.selectByValue(opt.value);
            });
            //this.choices.setValue(_default);
        } catch (error) {
            console.error('Choices ошибка:', error);
            return this.#removeChoicesOptions(defaultValue);
        }
    }

    /**
     * Удаление всех опций через DOM
     * @param defaultValue
     * @returns {*}
     * @private
     * @since 1.0.2
     */
    #fallbackRemoveOptions(defaultValue = null) {
        let _sel = ".choices__list div";
        let _this = this;
        this.dropdown.querySelectorAll(_sel).forEach(opt => {
            if (opt.getAttribute("data-value") !== defaultValue) {
                opt.remove();
            }
        });
        this.#fallbackSelectByValue(defaultValue);
    }

    /**
     * Безопасное добавление опции
     * @param value
     * @param label
     * @param disabled
     * @param selected
     * @param replaceChoices
     * @returns {boolean|boolean}
     * @public
     * @since 1.0.2
     */
    addOption(value, label, disabled = false, selected = false, replaceChoices = false) {
        if (!this.isUsed) return false;
        if (!this.isChoices) {
            return this.#fallbackAddOption(value, label, disabled, selected, replaceChoices);
        }
        return this.#addChoicesOption(value, label, disabled, selected, replaceChoices);
    }

    /**
     * Добавление опции через Choices
     * @param value
     * @param label
     * @param disabled
     * @param selected
     * @param replaceChoices
     * @returns {boolean|boolean}
     * @private
     * @since 1.0.2
     */
    #addChoicesOption(value, label, disabled = false, selected = false, replaceChoices = false) {
        try {
            this.choices.setChoices([{
                value: value,
                label: label,
                selected: selected,
                disabled: disabled
            }], 'value', 'label', replaceChoices);

            return true;
        } catch (error) {
            console.error('Choices ошибка:', error);
            return this.#fallbackAddOption(value, label, disabled, selected, replaceChoices);
        }
    }

    /**
     * Добавление опции через DOM
     * @param value
     * @param label
     * @param disabled
     * @param selected
     * @param replaceChoices
     * @returns {boolean|boolean}
     * @private
     * @since 1.0.2
     */
    #fallbackAddOption(value, label, disabled = false, selected = false, replaceChoices = false) {
        if (!this.fancySelect) return false;

        const dropdown = this.fancySelect.querySelector('.choices__list--dropdown');
        if (!dropdown) return false;

        // Создаем опцию через DOM
        const option = document.createElement('div');
        option.className = 'choices__item choices__item--choice';
        option.dataset.value = value;
        option.textContent = label;

        const list = dropdown.querySelector('.choices__list') || dropdown;
        list.appendChild(option);

        // Добавляем обработчик
        option.addEventListener('click', () => {
            this.selectValue(value, label);
        });

        return true;
    }

    /**
     * Безопасное удаление опции
     * @param value
     * @returns {boolean|boolean}
     * @public
     * @since 1.0.2
     */
    removeOption(value) {
        if (!this.isUsed) return false;
        if (!this.isChoices) return this.#fallbackRemoveOption(value);
        return this.#removeChoicesOption(value);
    }

    /**
     * Удаление опции через Choices
     * @param value
     * @returns {boolean|boolean}
     * @rivate
     * @since 1.0.2
     */
    #removeChoicesOption(value) {
        if (!this.isChoices) return this.#fallbackRemoveOption(value);

        try {

            if (typeof this.choices.setChoices === 'function') {
                const currentChoices = this.choices.getValue(true);
                const filteredChoices = currentChoices.filter(choice => choice.value !== value);
                this.choices.setChoices(filteredChoices, 'value', 'label', false);
                return true;
            }

            return this.#fallbackRemoveOption(value);

        } catch (error) {
            console.error('Ошибка удаления опции:', error);
            return this.#fallbackRemoveOption(value);
        }
    }

    /**
     * Удаление опции через Choices
     * @param value
     * @returns {boolean|boolean}
     * @rivate
     * @since 1.0.2
     */
    #fallbackRemoveOption(value) {
        if (!this.fancySelect) return false;

        // Удаляем из dropdown

        if (this.dropdown) {
            const option = this.dropdown.querySelector(`[data-value="${value}"]`);
            if (option) option.remove();
        }

        // Удаляем из выбранных
        const selectedList = this.fancySelect.querySelector('.choices__list--multiple');
        if (selectedList) {
            const selectedItem = selectedList.querySelector(`[data-value="${value}"]`);
            if (selectedItem) selectedItem.remove();
        }

        // Обновляем скрытый input
        this.#updateHiddenSelect();

        return true;
    }


    /**
     * Безопасная установка выбранного значения
     * @param value
     * @returns {*|boolean}
     * @public
     * @since 1.0.2
     */
    selectByValue(value) {
        if (!this.isUsed) return false;
        // Проверяем существование опции
        if (!this.#isOptionExists(value)) {
            console.warn(`Опция "${value}" не найдена`);
            return false;
        }

        // Пробуем через Choices API
        if (this.choices && this.choices.setChoiceByValue) {
            return this.#selectByValueChoices(value);
        }

        // Иначе через DOM
        return this.#fallbackSelectByValue(value);
    }

    /**
     * Установка выбранного значения через Choices
     * @param value
     * @returns {*|boolean}
     * @private
     * @since 1.0.2
     */
    #selectByValueChoices(value) {
        try {
            this.choices.setChoiceByValue(value);
            return true;
        } catch (error) {
            console.error('Choices API ошибка:', error);
            return this.#fallbackSelectByValue(value);
        }
    }

    /**
     * Установка выбранного значения через DOM
     * @param value
     * @returns {*|boolean}
     * @private
     * @since 1.0.2
     */
    #fallbackSelectByValue(value) {
        // Находим опцию
        const option = this.dropdown.querySelector(`[data-value="${value}"]`);
        if (!option) return false;

        const text = option.textContent.trim();

        // Устанавливаем значение
        this.hiddenSelect.value = value;

        // Обновляем видимую кнопку
        const button = this.fancySelect.querySelector('.choices__button');
        if (button) {
            button.textContent = text;
        }

        // Скрываем placeholder
        const placeholder = this.fancySelect.querySelector('.choices__placeholder');
        if (placeholder) {
            placeholder.style.display = 'none';
        }

        // Обновляем стили в dropdown
        this.#updateDropdownSelection(option);

        // Триггерим события
        this.#triggerEvents(value);

        return true;
    }

    /**
     * Выбор опции по значению текста
     * @param text  Текст который ищем
     * @param exactMatch Флаг полного соответствия значения
     * @returns {*|boolean}
     * @public
     * @since 1.0.2
     */
    selectByText(text, exactMatch = true) {
        if (!this.isUsed) return false;
        const option = this.findOptionByText(text, exactMatch);
        if (!option) {
            console.warn(`Опция с текстом "${text}" не найдена`);
            return false;
        }

        const value = option.dataset.value;
        return this.selectByValue(value);
    }

    /**
     * Поиск опции по значению текста
     * @param text  Текст который ищем
     * @param exactMatch Флаг полного соответствия значения
     * @returns {any|null}
     * @public
     * @since 1.0.2
     */
    findOptionByText(text, exactMatch) {
        if (!this.isUsed) return false;

        if (!this.dropdown) return null;

        const options = this.dropdown.querySelectorAll('.choices__item');

        for (const option of options) {
            const optionText = option.textContent.trim();

            if (exactMatch && optionText === text) {
                return option;
            }

            if (!exactMatch && optionText.includes(text)) {
                return option;
            }
        }

        return null;
    }

    /**
     * Проверка существует или нет опция с таким значением
     * @param value
     * @returns {boolean}
     * @private
     * @since 1.0.2
     */
    #isOptionExists(value) {
        if (!this.dropdown) return false;
        return !!this.dropdown.querySelector(`[data-value="${value}"]`);
    }

    /**
     * Выделяет значение
     * @param value Значение
     * @param label текст
     * @returns {boolean}
     * @since 1.0.2
     */
    selectValue(value, label) {
        if (!this.isUsed) return false;

        const hiddenSelect = this.fancySelect.querySelector('input[type="hidden"]');
        if (!hiddenSelect) return false;

        const isMultiple = this.fancySelect.hasAttribute('multiple');

        if (isMultiple) {
            // Для множественного выбора
            let values = hiddenSelect.value ? hiddenSelect.value.split(',') : [];
            if (!values.includes(value)) {
                values.push(value);
                hiddenSelect.value = values.join(',');

                // Добавляем визуальный элемент
                this.addSelectedItem(value, label);
            }
        } else {
            // Для одиночного выбора
            hiddenSelect.value = value;

            const button = this.fancySelect.querySelector('.choices__button');
            if (button) button.textContent = label;
        }

        // Триггерим события
        hiddenSelect.dispatchEvent(new Event('change', {bubbles: true}));

        return true;
    }

    /**
     * Добавляет выбранный элемент
     * @param value Значение
     * @param label текст
     * @returns {boolean}
     * @since 1.0.2
     */
    addSelectedItem(value, label) {
        if (!this.isUsed) return false;

        const selectedList = this.fancySelect.querySelector('.choices__list--multiple');
        if (!selectedList) return;

        const item = document.createElement('div');
        item.className = 'choices__item';
        item.dataset.value = value;
        item.innerHTML = `
            ${label}
            <button type="button" class="choices__button">×</button>
        `;

        selectedList.appendChild(item);
    }

    /**
     * @private
     * @since 1.0.2
     */
    #updateHiddenSelect() {
        const selectedItems = this.fancySelect.querySelectorAll('.choices__list--multiple .choices__item');
        const values = Array.from(selectedItems).map(item => item.dataset.value);
    }

    /**
     * Получает параметры опции по значению
     * @param value Значение
     * @returns {*[]}
     * @private
     * @since 1.0.2
     */
    #getOptionByValue(value) {
        const item = this.fancySelect.querySelectorAll('.choices__list--dropdown .choices__list div[data-value="' + value.toString() + '"]');
        //console.log('.choices__list--dropdown .choices__list div[data-value="' + value.toString() + '"]', item);
        let result = [];
        item.forEach(opt => {
            result.push({
                value: opt.getAttribute("data-value"),
                label: opt.textContent,
                selected: false,
                disabled: false
            });
        });
        return result;
    }


    /**
     * Обновляет список опций
     * @param selectedOption
     * @private
     * @since 1.0.2
     */
    #updateDropdownSelection(selectedOption) {
        if (!this.dropdown) return;

        // Снимаем выделение со всех
        const allOptions = this.dropdown.querySelectorAll('.choices__item');
        allOptions.forEach(opt => {
            opt.classList.remove('choices__item--selected');
            opt.removeAttribute('aria-selected');
        });

        // Выделяем выбранную
        selectedOption.classList.add('choices__item--selected');
        selectedOption.setAttribute('aria-selected', 'true');
    }

    /**
     * Вызывает событие изменения
     * @param value Значение
     * @private
     * @since 1.0.2
     */
    #triggerEvents(value) {
        // Стандартные события
        ['input', 'change'].forEach(eventType => {
            this.hiddenSelect.dispatchEvent(new Event(eventType, {bubbles: true}));
        });

        // Joomla события
        this.hiddenSelect.dispatchEvent(new CustomEvent('field:selected', {
            bubbles: true,
            detail: {value: value}
        }));
    }
}
