/*
 * @package    ECLCDEK shipping plugin СДЭК to RadicalMart
 * @subpackage    ECLCDEK
 * @version    1.0.1
 * @author Econsult Lab.
 * @copyright  Copyright (c) 2025 Econsult Lab. All rights reserved.
 * @license    GNU/GPL license: https://www.gnu.org/copyleft/gpl.html
 * @link       https://econsultlab.ru
 */

/**
 * Класс параметров формирования и вывода результатов запроса AJAX
 * @since 1.0.0
 */
class Result {
    /**
     * Доступность кнопок показ карты и сброс
     *
     * @type {{map_btn: boolean, reset_btn: boolean}}
     *
     * @since 1.0.0
     */
    #disabled_btns = {
        map_btn: true,
        reset_btn: true,
    };

    /**
     * Объект сообщения пользователю. Тип,текст и html сообщения
     *
     * @type {{type: string, text: string, formatted: string}}
     *
     * @since 1.0.0
     */
    #message = {
        type: "warning",
        text: "",
        formatted: ""
    };

    /**
     * Клиент, куда выводится сообщение. Сайт или административная часть.
     *
     * @type {string}
     *
     * @since 1.0.0
     */
    #client = "admin";

    /**
     * Задача для которой выполняется формирование сообщения.
     *
     * @type {string}
     *
     * @since 1.0.0
     */
    #task = "checkout";


    /**
     * Тип доставки.
     *
     * @type {string}
     *
     * @since 1.0.0
     */
    #deliveryType = "office"

    /**
     * Параметры получения результата
     *
     * @type {{calculation_params: {}, result: {}}}
     *
     * @since 1.0.0
     */
    #output_params = {
        calculation_params: {},
        result: {
            result: "",
        }
    };

    constructor(disabled_btns, message, client, task, deliveryType, output_params) {
        this.#disabled_btns = disabled_btns;
        this.#message = message;
        this.#client = client;
        this.#task = task;
        this.#deliveryType = deliveryType;
        this.#output_params = output_params;
    }

    /**
     * Возвращает структуру доступности кнопок
     *
     * @returns {{map_btn: boolean, reset_btn: boolean}}
     *
     * @since 1.0.0
     */
    get disabled_btns() {
        return this.#disabled_btns;
    }

    /**
     * Возвращает структуру сообщения
     *
     * @returns {{type: string, text: string, formatted: string}}
     * @since 1.0.0
     */
    get message() {
        return this.#message;
    }

    /**
     * Возвращает клиента
     *
     * @returns {string}
     * @since 1.0.0
     */
    get client() {
        return this.#client;
    }

    /**
     * Возвращает задачу
     *
     * @returns {string}
     * @since 1.0.0
     */
    get task() {
        return this.#task;
    }

    /**
     * Возвращает тип доставки
     *
     * @returns {string}
     * @since 1.0.0
     */
    get deliveryType() {
        return this.#deliveryType;
    }

    /**
     * Возвращает структуру параметров запроса
     *
     * @returns {{calculation_params: {}, result: {}}}
     * @since 1.0.0
     */
    get output_params() {
        return this.#output_params;
    }

    /**
     * Валидация параметров
     *
     * @returns {boolean}
     * @since 1.0.0
     */
    validate() {
        return typeof this.disabled_btns === "object" &&
            typeof this.disabled_btns.map_btn !== "undefined" &&
            typeof this.disabled_btns.reset_btn !== "undefined" &&
            typeof this.message === "object" &&
            typeof this.message.type !== "undefined" &&
            typeof this.message.text !== "undefined" &&
            typeof this.deliveryType !== "undefined" &&
            typeof this.task !== "undefined" &&
            typeof this.client !== "undefined";
    }

    /**
     * Возвращает объект
     *
     * @returns {{disabled_btns: {map_btn: boolean, reset_btn: boolean}, message: {type: string, text: string, formatted: string}, deliveryType: string, client: string, task: string, output_params: {calculation_params: {}, result: {}}}}
     * @since 1.0.0
     */
    toObject() {
        return {
            disabled_btns: this.#disabled_btns,
            message: this.#message,
            deliveryType: this.#deliveryType,
            client: this.#client,
            task: this.#task,
            output_params: this.#output_params
        }
    }

    /**
     * Устанавливает структуру доступности кнопок
     * @param {object} value
     * @since 1.0.0
     */
    set disabled_btns(value) {
        this.#disabled_btns = value;
    }

    /**
     * Устанавливает клиента
     * @param {string} value
     * @since 1.0.0
     */
    set client(value) {
        this.#client = value;
    }

    /**
     * Устанавливает задачу
     * @param {string} value
     * @since 1.0.0
     */
    set task(value) {
        this.#task = value;
    }

    /**
     * Устанавливает тип доставки
     * @param {string} value
     * @since 1.0.0
     */
    set deliveryType(value) {
        this.#deliveryType = value;
    }

    /**
     * Устанавливает структуру сообщения
     * @param {object} value
     * @since 1.0.0
     */
    set message(value) {
        this.#message = value;
    }

    /**
     * Устанавливает структуру параметров запроса
     * @param {object} value
     * @since 1.0.0
     */
    set output_params(value) {
        this.#output_params = value;
    }
}

/**
 * Базовый класс работы со СДЭК
 *
 * @since 1.0.0
 */
class ECLCDEK extends Core {
    /**
     * Параметры инициализации
     * @type {object}
     * @since 1.0.0
     */
    #params;

    /**
     * Объект карты (виджета)
     * @type {object}
     *
     * @since 1.0.0
     */
    widget = null;


    /**
     * Элемент списка городов
     *
     * @type {HTMLElement}
     *
     * @since 1.0.0
     */
    city_fld = null;

    /**
     * Элемент списка стран
     *
     * @type {HTMLElement}
     *
     * @since 1.0.0
     */
    country_fld = null;

    /**
     * Элемент списка регионов
     *
     * @type {HTMLElement}
     *
     * @since 1.0.0
     */
    region_fld = null;

    /**
     * Название города (текущее значение)
     *
     * @type {string}
     *
     * @since 1.0.0
     */
    city_name = ""

    /**
     * Код города (текущее значение)
     *
     * @type {string}
     *
     * @since 1.0.0
     */
    city_code = ""

    /**
     * Название региона (текущее значение)
     *
     * @type {string}
     *
     * @since 1.0.0
     */
    region_name = ""

    /**
     * Код региона (текущее значение)
     *
     * @type {string}
     *
     * @since 1.0.0
     */
    region_code = ""

    /**
     * Название страны (текущее значение)
     *
     * @type {string}
     *
     * @since 1.0.0
     */
    country_name = ""

    /**
     * Код страны (текущее значение)
     *
     * @type {string}
     *
     * @since 1.0.0
     */
    country_code = ""
    /**
     * Структура блока вывода результатов запроса
     *
     * @type {{show_map_btn: null, reset_btn: null, message_container: null, result_input: null}}
     * @since 1.0.0
     */
    results_block = {
        show_map_btn: null, // Кнопка отображения виджета карты
        reset_btn: null, // Кнопка очистки результатов
        message_container: null,  // Контейнер для отображаемых сообщений
        result_input: null // Input для передачи в форму выбранного адреса
    }

    /**
     * Текущие параметры запроса
     *
     * @type {Result|{}}
     * @since 1.0.0
     */
    current_results = {};

    constructor(params) {
        const _defaultParams = {
            core: {
                debug: true,
                ajaxLoader: document.getElementById('checkout_shipping_loading'),
                disabled_class: "uk-disabled",
            },
            city_field_id: "jform_shipping_city",
            country_field_id: "jform_shipping_country",
            region_field_id: "jform_shipping_region",
            calculationParams: {},
            deliveryType: "office",
            task: "",
            client: "",
            results_block: "",
        };
        params ??= _defaultParams;
        super(params.core);
        this.className = "ECLCDEK";
        this.#params = params;
        this.debug('constructor', "params", params);
        this.widget = null;
        this.city_fld = document.getElementById(this.#params.city_field_id);
        this.region_fld = document.getElementById(this.#params.region_field_id);
        this.country_fld = document.getElementById(this.#params.country_field_id);
        this.city_name = this.getSelectedText(this.city_fld);
        this.city_code = this.getSelectedValue(this.city_fld);
        this.region_name = this.getSelectedText(this.region_fld);
        this.region_code = this.getSelectedValue(this.region_fld);
        this.country_name = this.getSelectedText(this.country_fld);
        this.country_code = this.getSelectedValue(this.country_fld);

        this.initializeResultsBlock();
        this.eventBinder();
        this.successCallBack = this.updateResult;
        this.errorCallBack = this.showError;
    }

    /**
     * Получает параметры объекта
     * @returns {Object}
     *
     * @since 1.0.0
     */
    get params() {
        return this.#params;
    }

    /**
     * Устанавливает параметры объекта
     * @param {object} value
     *
     * @since 1.0.0
     */
    set params(value) {
        this.#params = value;
    }

    /**
     * Инициализирует обработчики событий
     *
     * @since 1.0.0
     */
    eventBinder() {
        this.debug('eventBinder', "started", '...');
        this.city_fld.addEventListener("change", event => this.prepareMapSettings((event.currentTarget), this));
        this.country_fld.addEventListener("change", event => this.prepareMapSettings(event.currentTarget, this));
        this.region_fld.addEventListener("change", event => this.prepareMapSettings(event.currentTarget, this));
        this.results_block.show_map_btn.addEventListener("click", event => this.showMap(this));
        this.results_block.reset_btn.addEventListener("click", event => this.clearResult(this));
    }

    /**
     * Формирует параметры инициализации карты (виджета)
     *
     * @param {HTMLElement} fld Поле, вызвавшее изменения
     * @param {ECLCDEK}_this
     *
     * @since 1.0.0
     */
    prepareMapSettings(fld, _this) {
        _this.debug('prepareMapSettings', "started", '...');
        let _el = fld;
        let _value = _this.getSelectedValue(_el);
        let _text = _this.getSelectedText(_el);
        let _fld_id = _el.id;
        _this.debug('prepareMapSettings', "_el", _el);
        _this.debug('prepareMapSettings', "_value", _value);
        _this.debug('prepareMapSettings', "_text", _text);
        _this.debug('prepareMapSettings', "_fld_id", _fld_id);
        switch (_fld_id) {
            case _this.#params.city_field_id:
                _this.debug('prepareMapSettings', "_this.city_code", _this.city_code);
                if (_value !== "" && _this.city_code !== _value) {
                    _this.city_name = _text;
                    _this.city_code = _value;

                    if (_this.city_code) {
                        if (_this.isMapLoaded()) {
                            /**
                             * Изменено формирование адреса позиционирования карты
                             * @since 1.0.3
                             */
                            _this.updateMapSettings({city: _this.buildDefaultLocation()});
                            _this.clearResult(_this);
                        } else {
                            _this.initializeMap();
                        }
                    }
                } else {
                    _this.city_name = _text;
                    _this.city_code = _value;
                }
                break;
            case _this.#params.country_field_id:
                _this.debug('prepareMapSettings', "_this.country_code", _this.country_code);
                if (_value !== "" && _this.country_code !== _value) {
                    _this.country_name = _text;
                    _this.country_code = _value;
                    _this.successCallBack = _this.updateSelectOptions;
                    _this.getRegions({country_codes: _this.country_code});
                    if (typeof _this.#params.calculationParams === "object" && _this.#params.calculationParams !== null)
                        _this.#params.calculationParams.to.country_codes = _this.country_code;
                } else {
                    _this.country_name = _text;
                    _this.country_code = _value;
                    _this.clearSelectOptions(_this.region_fld, false);
                    _this.clearSelectOptions(_this.city_fld, false);
                }
                break;
            case _this.#params.region_field_id:
                _this.debug('prepareMapSettings', "_this.region_code", _this.region_code);
                if (_value !== "" && _this.region_code !== _value) {
                    _this.region_name = _text;
                    _this.region_code = _value;
                    _this.successCallBack = _this.updateSelectOptions;
                    _this.getCities({country_codes: _this.country_code, region_code: _this.region_code});
                } else {
                    _this.region_name = _text;
                    _this.region_code = _value;
                    _this.clearSelectOptions(_this.city_fld, false);
                }
                break;
        }
    }

    /**
     * Обрабатывает событие окончания загрузки карты (виджета)
     *
     * @param {ECLCDEK} _this Текущий объект
     *
     * @since 1.0.0
     */
    onReadyMap(_this) {
        _this.debug("onReady", "started", '...');
        _this.hideLoader();
        this.debug('onReady', "_this.widget", _this.widget);
        if (!_this.#validateWidget()) {
            _this.debug('onReady', "Не корректная инициализация виджета", "...");
            _this.widget = null;
        }
    }

    /**
     * Обрабатывает событие выбора адреса (ПВЗ) на карте (виджета)
     *
     * @param {string} delivery_type    Тип доставки (office , door)
     * @param {object} tariff    Тариф доставки
     * @param {object} address   Адрес доставки
     * @param {ECLCDEK} _this Текущий объект
     *
     * @since 1.0.0
     */
    onChooseMap(delivery_type, tariff, address, _this) {
        _this.debug('onChooseMap', "started", '...');
        _this.debug("onChoose", "delivery_type", delivery_type);
        _this.debug("onChoose", "tariff", tariff);
        _this.debug("onChoose", "address", address);
        _this.closeMap(_this);
        if (typeof address === "object") {
            _this.successCallBack = _this.prepareMapAddress;
            _this.errorCallBack = _this.showError;
            _this.getResultFormatted(_this.preProcessAddress(address));
        } else {
            // Ошибка возвращаемых данных
            _this.debug('onChoose', "widget error", 'Не получены данные адреса', 'error');
        }
    }

    /**
     * Предварительная обработка адреса
     *
     * @param {object} address  Адрес
     *
     * @returns {Result}
     *
     * @since 1.0.0
     */
    preProcessAddress(address) {
        this.debug('preprocessAddress', "started", '...');
        this.debug('preprocessAddress', "address", address);
        let _result = this.getBaseResult();
        _result.disabled_btns = {
            map_btn: true,
            reset_btn: false,
        };
        if (typeof address.components !== "undefined" && Array.isArray(address.components)) {
            // Есть компоненты адреса, собираем адрес
            let _street = "";
            let _house = "";
            address.components.forEach(function (el) {
                switch (el.kind) {
                    case "street":
                        _street = el.name;
                        break;
                    case "house":
                        _house = el.name;
                        break;
                }
            });
            address.address = _street + ', ' + _house;
        }
        _result.output_params.result.address = address;
        this.current_results = _result;
        return _result;
    }

    /**
     * Обработка выбранных компонентов адреса
     *
     * @param {Result} results
     *
     * @since 1.0.0
     */
    prepareMapAddress(results) {
        this.debug('prepareMapAddress', "started", '...');
        this.debug('prepareMapAddress', "results", results);
        if (typeof results.output_params.result.address !== "undefined") {

            const _address = results.output_params.result.address;
            let _this = this;

            if (_address.country_code !== this.country_code || this.country_fld.value === "") {
                // Страна поменялась или не выбрана в элементе
                this.country_code = _address.country_code;
                this.country_name = this.getSelectTextByValue(this.country_fld, _address.country_code);
                // Изменить регион в поле
                this.setOptionSelected(this.country_fld, 'value', this.country_code);
                // Обновляем список регионов без пересчета стоимости и устанавливаем выбранный в виджете регион
                this.successCallBack = this.recheckSelectFields;
                this.getRegions({country_codes: this.country_code});
                this.debug('prepareMapAddress', "done", '...');
                return;
            }

            let _new_region_code = '';

            if (typeof _address.region === "undefined") {
                // Получен не ПВЗ, а адрес
                if (typeof _address.components === "object") {
                    Object.entries(_address.components).forEach(function ([key, component]) {
                        if (component.kind === 'province') {
                            _new_region_code = _this.getSelectValueByText(_this.region_fld, component.name);
                            if (_new_region_code !== "") {
                                _this.debug('prepareMapAddress', "components: _new_region_code", _new_region_code);
                                return;
                            }
                        }
                    });
                }
            } else {
                _new_region_code = this.getSelectValueByText(this.region_fld, _address.region);
                this.debug('prepareMapAddress', "_new_region_code", _new_region_code);
            }

            if (_new_region_code !== "" && _new_region_code !== this.region_code || this.region_fld.value === "") {
                // Регион поменялся или не выбран в элементе
                this.region_code = _new_region_code
                this.region_name = _address.region;
                // Изменить регион в поле
                this.setOptionSelected(this.region_fld, 'value', this.region_code);
                // Обновляем список городов без пересчета стоимости и устанавливаем выбранный в виджете город
                this.successCallBack = this.recheckSelectFields;
                this.getCities({country_codes: this.country_code, region_code: this.region_code});
                this.debug('prepareMapAddress', "done", '...');
                return;
            }

            if (typeof _address.city_code === "undefined") {
                // Получен не ПВЗ, а адрес
                if (typeof _address.city === "string") {
                    let city_code = this.getSelectValueByText(this.city_fld, _address.city);
                    this.city_name = _address.city;
                    this.city_code = city_code;
                    this.debug('prepareMapAddress', "this.city_name", this.city_name);
                    this.debug('prepareMapAddress', "this.city_code", this.city_code);
                }
            } else {
                if (this.city_code !== _address.city_code || this.city_fld.value === "") {
                    // Город поменялся или не выбран в элементе
                    this.city_code = _address.city_code;
                    this.city_name = _address.city;
                    // Изменить регион в поле
                }
            }

            let _result = this.preProcessAddress(_address);
            _result.output_params.calculation_params = this.getCalculationParams(_address);
            this.debug('prepareMapAddress', "_result.output_params.calculation_params", _result.output_params.calculation_params, 'debug');
            this.setOptionSelected(this.city_fld, 'value', this.city_code);
            this.successCallBack = this.updateResult;
            this.getResultFormatted(_result);
        } else {
            // Не корректный формат результата
            this.debug('prepareMapAddress', "Не корректный формат результата", results, 'error');
        }
        this.debug('prepareMapAddress', "done", '...');
    }

    /**
     * Формирует параметры расчета стоимости доставки.
     * Если он нужен, например при оформлении заказа, переопределяется в соответствующем классе.
     *
     * @returns {object}
     *
     * @since 1.0.0
     */
    getCalculationParams(address) {
        return {};
    }

    /**
     * Обновляет опции элемента select и продолжает обработку выбранного адреса (callback)
     *
     * @param {HTMLElement} fld Элемент select
     * @param {object} valList  Объект списка значений
     * @param {string} value_key  Ключ значения
     * @param {string} text_key Ключ текста
     *
     * @since 1.0.0
     */
    recheckSelectFields(fld, valList, value_key, text_key) {
        this.debug('recheckSelectFields', "started", '...');
        this.updateSelectOptions(fld, valList, value_key, text_key);
        this.prepareMapAddress(this.current_results);

    }

    /**
     * Инициализация виджета карты
     *
     * @since 1.0.0
     */
    initializeMap() {
        this.debug('initializeMap', "started", '...');
        if (typeof window.CDEKWidget === "undefined") {
            this.debug('initializeMap', "window.CDEKWidget", "undefined", 'error');
            return;
        }

        if (!this.city_name) {
            this.debug('initializeMap', "Не выбран город назначения ,инициализация отменена", "...");
            return;
        }
        let _this = this;
        if (typeof this.#params.widgetParams === "object") {
            this.showLoader();
            let _wp = this.#params.widgetParams;
            /** Формируем адрес позиционирования карты
             *  @since 1.0.3
             */
            _wp.defaultLocation = _this.buildDefaultLocation();

            _wp.onReady = function () {
                _this.onReadyMap(_this);
            }
            _wp.onChoose = function (delivery_type, tariff, address) {
                _this.onChooseMap(delivery_type, tariff, address, _this);
            };
            this.debug('initializeMap', "_wp", _wp);
            try {
                this.widget = new window.CDEKWidget(_wp);
            } catch (error) {
                this.hideLoader();
                this.debug('initializeMap', "error", error);
            }
            this.debug('initializeMap', "widget", this.widget);
        } else {
            this.debug('initializeMap', "Отсутствуют параметры виджета", '...');
        }
        this.debug('initializeMap', "done", "...");
    }

    /**
     * Отображает виджет карты
     *
     * @param {ECLCDEK} _this   Текущий объект
     *
     * @since 1.0.0
     */
    showMap(_this) {
        _this.debug('showMap', "started", '...');
        if (_this.#validateWidget()) {
            try {
                _this.widget.open();
            } catch (ex) {
                _this.debug('showMap', 'ex', ex, 'error');
            }
        }
        _this.debug('showMap', "done", '...');
    }

    /**
     * Закрывает виджет карты
     *
     * @param {ECLCDEK} _this   Текущий объект
     *
     * @since 1.0.0
     */
    closeMap(_this) {
        this.debug('closeMap', "started", '...');
        if (_this.#validateWidget()) {
            try {
                _this.widget.close();
            } catch (ex) {
                _this.debug('closeMap', 'ex', ex, 'error');
            }
        }
        _this.debug('closeMap', "done", '...');
    }

    /**
     * Обновляет параметры инициализации карты без пересчета стоимости
     *
     * @param {object} settings  Настройки карты
     *
     * @since 1.0.0
     */
    updateMapSettings(settings) {
        this.debug('updateMapSettings', "started", '...');
        this.debug('updateMapSettings', "settings", settings);
        if (typeof this.widget.updateLocation === "function")
            this.widget.updateLocation(settings.city);
        this.debug('updateMapSettings', "done", '...');
    }

    /**
     * Проверка загруженности виджета карты.
     * Может быть переопределена
     *
     * @returns {boolean}
     *
     * @since 1.0.0
     */
    isMapLoaded() {
        return !!this.widget;
    }

    /**
     * Инициализирует блок результата выбора адреса.
     * Может быть переопределен
     *
     * @since 1.0.0
     */
    initializeResultsBlock() {
        this.debug('initializeResultsBlock', "started", '...');
        const _container = document.getElementById(this.#params.result);
        this.debug('initializeResultsBlock', "_container", _container);
        if (_container) {
            this.results_block.result_input = _container.querySelector("input[type='hidden']");
            this.results_block.message_container = _container.querySelector('div');
            this.results_block.show_map_btn = _container.querySelector('#cdek-btn');
            this.results_block.show_map_btn.text = Joomla.Text._("PLG_RADICALMART_SHIPPING_ECLCDEK_CHOOSE_" + this.#params.deliveryType.toUpperCase() + "_DELIVERY");
            this.results_block.reset_btn = _container.querySelector('#cdek-btn-reset');
            _container.addEventListener('change', event => this.onChangeTariff(event.target, this))
        }
        this.debug('initializeResultsBlock', "this.results_block", this.results_block);
        this.debug('initializeResultsBlock', "done", '...');
    }

    /**
     * Обработчик смены пользователем тарифа доставки.
     * Переопределяется в потомках
     *
     * @param {HTMLElement} el  Элемент Input с выбранным тарифом
     * @param {ECLCDEK} _this Текущий объект
     *
     * @since 1.0.0
     */
    onChangeTariff(el, _this) {
    }

    /**
     * Очищает блок результата выбора адреса
     *
     * @param {ECLCDEK} _this
     *
     * @since 1.0.0
     */
    clearResult(_this) {
        _this.debug("clearResults", "started", '...');
        _this.city_name = "";
        _this.city_code = "";
        _this.country_code = "";
        _this.country_name = "";
        _this.region_code = "";
        _this.region_name = "";
        _this.setOptionSelected(_this.country_fld, "value", "");
        _this.clearSelectOptions(_this.region_fld, false);
        _this.clearSelectOptions(_this.city_fld, false);

        let _result = _this.getBaseResult();
        _result.disabled_btns = {
            map_btn: false,
            reset_btn: true,
        };
        _result.message = {
            type: "warning",
            text: Joomla.Text._("PLG_RADICALMART_SHIPPING_ECLCDEK_SETTINGS_MESSAGE_NEED_ADDRESS"),
            formatted: ""
        }
        _this.successCallBack = _this.updateResult;
        _this.errorCallBack = _this.showError;
        _this.getResultFormatted(_result);
    }

    /**
     * Вывод ошибки (callback). Может быть переопределена
     *
     * @param error Ошибка
     *
     * @since 1.0.0
     */
    showError(error) {
        this.debug("showError", "error", error, 'error');
    }

    /**
     * Обновляет блок вывода результата
     *
     * @param {Result} result   Результат для вывода в блок
     *
     * @since 1.0.0
     */
    updateResult(result) {
        this.debug("updateResult", "started", '...');
        this.debug('updateResult', "result", result);
        let _disabled = typeof result.disabled_btns.map_btn !== "undefined" ? result.disabled_btns.map_btn : true;
        if (this.results_block.show_map_btn) {
            if (!_disabled && this.widget === null) {
                this.initializeMap();
            } else {
                this.setDisabledBtn(this.results_block.show_map_btn, _disabled);
            }
        }

        _disabled = typeof result.disabled_btns.reset_btn !== "undefined" ? result.disabled_btns.reset_btn : true;

        if (this.results_block.reset_btn) {
            this.setDisabledBtn(this.results_block.reset_btn, _disabled);
        }

        let _txt = typeof result.message.formatted !== "undefined" ? result.message.formatted : "";
        if (this.results_block.message_container) {
            this.results_block.message_container.innerHTML = _txt;
        }
        _txt = "";
        if (typeof result.output_params.result !== "undefined"
            && typeof result.output_params.result.tariff !== "undefined" && result.output_params.result.tariff !== null) {
            _txt = JSON.stringify(result.output_params.result);
        }
        if (this.results_block.result_input) {
            this.results_block.result_input.value = _txt;
            this.results_block.result_input.dispatchEvent(new Event('change'));
        }

    }

    /**
     * Получает результат для вывода в блоке
     *
     * @param {Result} result_obj Параметры для формирования вывода
     *
     * @since 1.0.0
     */
    getResultFormatted(result_obj) {
        this.debug("getResultFormatted", "result_obj", result_obj);
        let _result = new Result(result_obj.disabled_btns, result_obj.message, result_obj.client, result_obj.task, result_obj.deliveryType, result_obj.output_params);

        let _this = this;
        if (this.ajaxRequestLock) {
            return;
        }

        if (_result.validate()) {
            let _data = result_obj.toObject()
            this.debug("getResultFormatted", "_result", _result);
            this.showLoader();
            this.ajaxRequestLock = true;
            this.sendAjax('getResultFormatted', _data).then(result => {
                _this.ajaxRequestLock = false;
                result = Object.assign(_result, result);
                _this.debug("getResultFormatted success", "result", result);
                _this.hideLoader();
                if (typeof _this.successCallBack === "function") {
                    _this.successCallBack(result);
                }
            }).catch(error => {
                this.ajaxRequestLock = false;
                _this.hideLoader();
                _this.debug("getResultFormatted error", "error", error, 'error');
                if (typeof _this.errorCallBack === "function") {
                    _this.errorCallBack(error);
                }
            });
        } else {
            // Не корректная структура параметра запроса - ошибка
            if (typeof _this.errorCallBack === "function") {
                _this.errorCallBack(Joomla.Text._("PLG_RADICALMART_SHIPPING_ECLCDEK_MESSAGE_ERROR_REQUEST_VALIDATION"));
            }
        }
    }

    /**
     * Получает список регионов по фильтру
     *
     * @param {object} filter    Фильтр для отбора региона. Параметры фильтра см. ниже
     * @see LocationRegions
     *
     * @since 1.0.0
     */
    getRegions(filter) {
        this.debug('getRegions', 'filter', filter, 'debug');
        let _this = this;

        if (this.ajaxRequestLock) {
            return;
        }

        this.showLoader();
        let _validate = this.#validateRegionsFilter(filter);
        if (_validate === true) {
            this.ajaxRequestLock = true;
            this.sendAjax('getRegions', filter).then(result => {
                _this.ajaxRequestLock = false;
                _this.debug("getRegions success", "result", result);
                _this.hideLoader();
                if (typeof _this.successCallBack === "function") {
                    _this.successCallBack(this.region_fld, result, "region", "region_code");
                }
            }).catch(error => {
                this.ajaxRequestLock = false;
                _this.hideLoader();
                _this.debug("getRegions error", "error", error, 'error');
                if (typeof _this.errorCallBack === "function") {
                    _this.errorCallBack(error);
                }
            });
        } else {
            // Не корректные значения фильтра
            if (typeof _this.errorCallBack === "function") {
                _this.errorCallBack(_validate);
            }
        }
    }

    /**
     * Получает список городов с фильтрацией по параметрам
     *
     * @param {object} filter    Фильтр для отбора городов. Параметры фильтра см. ниже
     * @see LocationCities
     *
     * @since 1.0.0
     */
    getCities(filter) {
        this.debug('getCitiesList', 'filter', filter, 'debug');
        let _this = this;

        if (this.ajaxRequestLock) {
            return;
        }

        this.showLoader();

        let _validate = this.#validateCitiesFilter(filter);
        if (_validate === true) {
            this.ajaxRequestLock = true;
            this.sendAjax('getCities', filter).then(result => {
                _this.ajaxRequestLock = false;
                _this.debug("getCities success", "result", result);
                if (_this.ajaxLoader) {
                    _this.ajaxLoader.style.display = 'none';
                }
                if (typeof _this.successCallBack === "function") {
                    _this.successCallBack(_this.city_fld, result, "city", "code");
                }
            }).catch(error => {
                this.ajaxRequestLock = false;
                if (_this.ajaxLoader) {
                    _this.ajaxLoader.style.display = 'none';
                }
                _this.debug("getCities error", "error", error, 'error');
                if (typeof _this.errorCallBack === "function") {
                    _this.errorCallBack(error);
                }
            });
        } else {
            // Не выбрана страна - ошибка
            if (typeof _this.errorCallBack === "function") {
                _this.errorCallBack(_validate);
            }
        }
    }

    /**
     * Формирует базовые параметры объекта для вывода результата выбора адреса и сообщений
     *
     * @returns {Result}
     *
     * @since 1.0.0
     */
    getBaseResult() {
        return new Result(
            {
                map_btn: false,
                reset_btn: false,
            },
            {
                type: "",
                text: "",
                formatted: ""
            },
            this.#params.client,
            this.#params.task,
            this.#params.deliveryType,
            {
                calculation_params: {},
                result: {},
            });
    }

    /**
     * Проверяет корректность создания виджета
     * TODO: Необходимо проработать механизм проверки
     *
     * @returns {boolean}
     *
     * @since 1.0.0
     */
    #validateWidget() {
        this.debug("#validateWidget", "this.widget", this.widget);
        return true;
    }

    /**
     * Проверка корректности фильтра отбора регионов
     *
     * @param {object} filter   Фильтр отбора регионов
     *
     * @returns {boolean|string} true - если все в порядке или описание ошибки
     *
     * @since 1.0.0
     */
    #validateRegionsFilter(filter) {
        this.debug("#validateRegionsFilter", "filter", filter);
        if (typeof filter !== "object") {
            this.debug("#validateRegionsFilter", "error", Joomla.Text._("PLG_RADICALMART_SHIPPING_ECLCDEK_MESSAGE_ERROR_FILTER_WRONG_TYPE"), 'error');
            return Joomla.Text._("PLG_RADICALMART_SHIPPING_ECLCDEK_MESSAGE_ERROR_FILTER_WRONG_TYPE");
        }
        let _this = this;

        const _validKeys = {
            country_codes: true,
            region_code: true,
            kladr_region_code: true,
            fias_region_guid: true,
            size: true,
            page: true,
            lang: true,
        };

        Object.entries(filter).forEach(function ([key, value]) {
            if (typeof _validKeys[key] === "undefined" || !_validKeys[key]) {
                _this.debug("#validateRegionsFilter", "Не корректное значение key", key, 'error');
                return Joomla.Text._("PLG_RADICALMART_SHIPPING_ECLCDEK_MESSAGE_ERROR_FILTER_WRONG_PARAMETER").replace('%s', key);
            }
        });
        this.debug("#validateRegionsFilter", "done", 'success');
        return true;
    }

    /**
     * Проверка корректности фильтра отбора городов
     *
     * @param {object} filter   Фильтр отбора городов
     *
     * @returns {boolean|string} true - если все в порядке или описание ошибки
     *
     * @since 1.0.0
     */
    #validateCitiesFilter(filter) {
        this.debug("#validateCitiesFilter", "filter", filter);
        if (typeof filter !== "object") {
            this.debug("#validateCitiesFilter", "error", Joomla.Text._("PLG_RADICALMART_SHIPPING_ECLCDEK_MESSAGE_ERROR_FILTER_WRONG_TYPE"), 'error');
            return Joomla.Text._("PLG_RADICALMART_SHIPPING_ECLCDEK_MESSAGE_ERROR_FILTER_WRONG_TYPE");
        }
        let _this = this;

        const _validKeys = {
            country_codes: true,
            region_code: true,
            kladr_region_code: true,
            fias_region_guid: true,
            kladr_code: true,
            fias_guid: true,
            postal_code: true,
            code: true,
            city: true,
            size: true,
            page: true,
            lang: true,
            payment_limit: true
        };

        Object.entries(filter).forEach(function ([key, value]) {
            if (typeof _validKeys[key] === "undefined" || !_validKeys[key]) {
                _this.debug("#validateCitiesFilter", "Не корректное значение key", key, 'error');
                return Joomla.Text._("PLG_RADICALMART_SHIPPING_ECLCDEK_MESSAGE_ERROR_FILTER_WRONG_PARAMETER").replace('%s', key);
            }
        });
        this.debug("#validateCitiesFilter", "done", 'success');
        return true;
    }

    /**
     * Валидация данных при checkout.
     * Переопределяется в потомках.
     *
     * @param {Event} event Событие нажатия кнопки оформить заказ
     * @param {ECLCDEK} _this Текущий объект
     *
     * @since 1.0.0
     */
    validateCheckout(event, _this) {

    }

    /**
     * Формирует адрес позиционирования карты
     *
     * @returns {string}
     *
     * @since 1.0.3
     */
    buildDefaultLocation() {
        let _result = "";
        if (this.country_name)
            _result += this.country_name;
        if (this.region_name)
            _result += (_result ? ', ' : '') + this.region_name;
        if (this.city_name)
            _result += (_result ? ', ' : '') + this.city_name;
        return _result;
    }
}