/**
 * Class for working with Synergy in the interpreter (Block Processes)
 * Keywords: [backend, interpeter, js]
 * Description: Quick and easy writing of business processes in the interpreter block-processes.
 * Docs: `http://gitlab.lan.arta.kz/community/synergy-components/blob/master/interpreter/jSynergyDocs.md`
 *
 * Belongs to `TOO Arta Software`
 * Last modified: 08.03.2022
 * Contributes users: none
 * Version: 1.0.9
 *
 * By yandexphp
 */
function __classSynergy() {
    // [Переменные не видны из вне] Переменные для работы с данными по классу
    let __CLASS__;
    let __CLIENT__;
    let __DICTS__;
    let __API_UTILS__;
    let __UTILS__;

    let __LOADFORM__;
    let __IS_SERVER__;

    let __HOST__;
    let __LOGIN__;
    let __PASSWORD__;
    let __PROTOCOL__;
    let __HOSTNAME__;
    let __BASIC_AUTH__;
    let __CONNECT_LIST__;
    let __LIBRARY_PREFIX__;

    let __MULTI_FORMS_DATA__;
    let __FORMDATA__;
    let __FULL_DATA__;
    let __DATA__;
    let __FORM_EXT__;
    let __DATA_NO_CHANGE__;

    // [Метод не доступен из вне] Полностью заменяет данные по форме
    function setData(data) {
        __DATA__ = data;
    };

    // [Метод не доступен из вне] Устанавливает заголовки к запросу
    function requestSetHeadersApi(api, headers) {
        if (headers && Object.keys(headers).length) {
            for (let header in headers) {
                api.setRequestHeader(header, headers[header]);
            }
        }
    }

    // [Метод не доступен из вне] Устанавливает заголовок по типу данных к запросу
    function requestSetDataType(api, dataType) {
        switch (dataType) {
            case 'json':
                api.setRequestHeader('Content-type', 'application/json; charset=utf-8');
                break;
            case 'x-www-form-urlencoded':
                api.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=utf-8');
                break;
            default:
                api.setRequestHeader('Content-type', 'text/plain; charset=utf-8');
        }
    }

    // [Метод не доступен из вне] При загрузке данных по форме происходит слияние компонентов которые отсутствуют в данных по форме но в конфигурации формы они есть
    function mergeFormExtToFormData(formExt, formData) {
        let updateFormCompList = (formExt.properties || []).filter(function (comp) {
            return !(formData.data || []).find(function (x) {
                return x.id === comp.id;
            });
        });

        if (updateFormCompList.length) {
            updateFormCompList.forEach(function (x) {
                let comp = JSON.parse(JSON.stringify(x));

                let compModel = comp.data || {
                    id: comp.id,
                    type: comp.type
                };

                if (compModel.type === 'table' && x.config.appendRows) {
                    compModel.type = 'appendable_table';
                }

                formData.data.push(compModel);
            });
        }
    }

    // [Метод не доступен из вне] Процедурный метод для работы с пред. установкой значения компоненту
    function compProcedure(compType, args, isDynTableComp) {
        let keyVal = isDynTableComp ? 'v' : 'vt';
        let val = args[keyVal];

        function compProcedureSetValue(value) {
            args[keyVal] = value;
        }

        switch (compType) {
            case 'textbox':
            case 'textarea': {
                compProcedureSetValue({
                    value: val
                });
                break;
            }
            case 'reglink': {
                if (typeof val === 'string' || val instanceof String) {
                    compProcedureSetValue({
                        key: val,
                        value: null
                    });
                } else if (Array.isArray(val)) {
                    val = val.filter(function (x, k) {
                        return val.indexOf(x) === k;
                    });

                    compProcedureSetValue({
                        key: val.join(';'),
                        value: null
                    });
                }
                break;
            }
            case 'date': {
                if (__UTILS__.isObject(val)) {
                    return;
                }

                let dateFormat = '';
                let fullDate = '';
                let compConfig = isDynTableComp ? __CLIENT__.getConfigByComp(args.c, args.vt) : __CLIENT__.getConfigByComp(args.c);

                if (val instanceof Date) {
                    let date = [val.getFullYear(), val.getMonth() + 1, val.getDate()].map(function (x) {
                        return __UTILS__.strAddZero(x);
                    }).join('-');

                    let dateTime = [val.getHours(), val.getMinutes(), val.getSeconds()].map(function (x) {
                        return __UTILS__.strAddZero(x);
                    }).join(':');

                    fullDate = date + ' ' + dateTime;
                } else {
                    val = val.toString().replace(/\./g, '-');
                    fullDate = val.split(' ').length > 1 ? val : val + ' 00:00:00';
                }

                dateFormat = __CLIENT__.api('formPlayer/formatDate?date=' + encodeURIComponent(fullDate) + '&format=' + encodeURIComponent(compConfig.dateFormat), { dataType: 'text' });

                compProcedureSetValue({
                    key: fullDate,
                    value: dateFormat
                });
                break;
            }
            case 'numericinput': {
                let compConfig = (isDynTableComp ? __CLIENT__.getConfigByComp(args.c, args.vt) : __CLIENT__.getConfigByComp(args.c) || {});

                function numberWithSpaces(x) {
                    let parts = x.toString().split('.');
                    parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, '' + compConfig.TS_VALUE);

                    return parts.join('.');
                }

                if (val && (typeof val === 'string' || typeof val === 'number')) {
                    val = val.toString().replace(/\s*/g, '');

                    compProcedureSetValue({
                        key: val,
                        value: compConfig.TS_ACTIVE ? numberWithSpaces(val) : val
                    });
                }
                break;
            }
        }
    }

    // [Метод не доступен из вне] До сохранения данных происходит процедурная манипуляция с данными
    function beforeDoSaveFormData() {
        let tmp = {
            reglinkMeanings: [],
            reglinkValueComponents: {}
        };

        function checkCompReglink(cmp, table) {
            if (cmp.type !== 'reglink') {
                return;
            }

            if (table) {
                tmp.reglinkValueComponents[table.id + '_' + cmp.id] = {
                    component: cmp,
                    tableId: table.id,
                    index: cmp.id.substr(cmp.id.lastIndexOf('-b') + 2)
                };
            } else {
                tmp.reglinkValueComponents[cmp.id] = cmp;
            }

            let keys = cmp.key ? cmp.key.toString().split(';') : [];

            keys.forEach(function (docId) {
                let docIdPush = 'documentId=' + docId;

                if (!tmp.reglinkMeanings.includes(docIdPush)) {
                    tmp.reglinkMeanings.push(docIdPush);
                }
            });
        }

        /**
         * Сбор данных для построения API запросов
         */
        __DATA__.forEach(function (x) {
            if (x.type === 'appendable_table') {
                (x.data || []).forEach(function (x2) {
                    checkCompReglink(x2, x);
                });
            } else {
                checkCompReglink(x);
            }
        });

        if (tmp.reglinkMeanings.length) {
            tmp.reglinkMeanings = __CLIENT__.api('formPlayer/getDocMeaningContents?' + tmp.reglinkMeanings.join('&')); // TODO - подумать про Документ и кейс

            for (let key in tmp.reglinkValueComponents) {
                let model = JSON.parse(JSON.stringify(tmp.reglinkValueComponents[key]));
                let modelDynTable = model.hasOwnProperty('tableId') && model.hasOwnProperty('index');
                let keys = (modelDynTable ? model.component.key : model.key) || '';
                let documentIds = keys ? ('' + keys).split(';') : [];
                let meaningKeysArr = [];
                let meaningValueArr = [];

                if (modelDynTable) {
                    let bIndex = model.component.id.lastIndexOf('-b');
                    model.component.id = model.component.id.substr(0, bIndex);
                }

                let compConfig = modelDynTable ? __CLIENT__.getConfigByComp(model.component.id, model.tableId) : __CLIENT__.getConfigByComp(model.id);

                let config = {
                    key: '',
                    value: ''
                };

                if (compConfig && !compConfig.multi && documentIds.length) {
                    documentIds = [documentIds[0]];
                }

                documentIds.forEach(function (documentId) {
                    let documentMeaningInfo = tmp.reglinkMeanings.find(function (x) {
                        return x.documentId === documentId;
                    });

                    meaningKeysArr.push(documentId);
                    meaningValueArr.push(documentMeaningInfo.meaning);
                });

                config.key = meaningKeysArr.join(';');
                config.value = meaningValueArr.join(';');

                if (config.key && config.value) {
                    if (modelDynTable) {
                        __CLIENT__.setValue(model.component.id, model.tableId, model.index, config);
                    } else {
                        __CLIENT__.setValue(model.id, config);
                    }
                }
            }
        }
    }

    /**
     * Установка подключения к контейнеру
     * @param {String} host     С каким хостом будем работать. Пример: https://site.arta.pro/Synergy/
     * @param {String} login    Логин или $session или $key пользователя с кем будем работать
     * @param {String} password Пароль или sso_hash или access_token пользователя
     */
    this.setConnection = function (host, login, password) {
        if (arguments.length < 3) {
            return __CLIENT__;
        }

        if (!__UTILS__.isValidURL(host)) {
            console.error(__LIBRARY_PREFIX__ + 'Invalid host adress - "' + host + '"');
            return __CLIENT__;
        }

        let match = typeof host === 'boolean' ? null : host.toString().match(/((https?):\/\/(.*?))\/Synergy\/?/i);

        __HOST__ = match && match[1] || null;
        __PROTOCOL__ = match && match[2] || null;
        __HOSTNAME__ = match && match[3] || null;
        __LOGIN__ = login;
        __PASSWORD__ = password;
        __IS_SERVER__ = typeof btoa === 'undefined';
        __BASIC_AUTH__ = (typeof btoa === 'undefined' ? encodeURIComponent(__LOGIN__ + ':' + __PASSWORD__) : btoa(unescape(encodeURIComponent(__LOGIN__ + ':' + __PASSWORD__))));

        __LOADFORM__ = true;

        let connectToList = {
            host: __HOST__,
            protocol: __PROTOCOL__,
            hostname: __HOSTNAME__,
            login: __LOGIN__,
            password: __PASSWORD__,
            basic: __BASIC_AUTH__
        };

        let rewrite = __CONNECT_LIST__[__HOSTNAME__] && JSON.stringify(__CONNECT_LIST__[__HOSTNAME__]).replace(/"index".*?,/, '') != JSON.stringify(connectToList);

        if (!__CONNECT_LIST__[__HOSTNAME__] || rewrite) {
            connectToList.index = (Object.keys(__CONNECT_LIST__).length + (rewrite ? 0 : 1));
            __CONNECT_LIST__[__HOSTNAME__] = connectToList;
        }

        return __CLIENT__;
    }

    /**
     * Лист всех подключенных контейнеров
     */
    this.getListConnection = function () {
        return __CONNECT_LIST__;
    }

    /**
     * Изменить подключение на указанное из существующих
     * @param   {Int}   index   Индекс контейнера из листа контейнеров
     */
    this.selectConnection = function (index) {
        if (!__CONNECT_LIST__.length || !__CONNECT_LIST__[index]) {
            return __CLIENT__;
        }

        __HOST__ = __CONNECT_LIST__[index].host;
        __PROTOCOL__ = __CONNECT_LIST__[index].protocol;
        __HOSTNAME__ = __CONNECT_LIST__[index].hostname;
        __LOGIN__ = __CONNECT_LIST__[index].login;
        __PASSWORD__ = __CONNECT_LIST__[index].password;
        __BASIC_AUTH__ = __CONNECT_LIST__[index].basic;

        return __CLIENT__;
    }

    /**
     * Получения данных о текущем подключении к контейнеру
     */
    this.getConnection = function () {
        if (!__LOADFORM__) {
            return null;
        }

        return {
            host: __HOST__,
            protocol: __PROTOCOL__,
            hostname: __HOSTNAME__,
            login: __LOGIN__,
            password: __PASSWORD__,
            basic: __BASIC_AUTH__
        };
    }

    /**
     * Провести проверку подключения к контейнеру
     */
    this.testConnect = function () {
        try {
            return !!__CLIENT__.api('person/auth').hasOwnProperty('userid');
        } catch (_err) {
            return false;
        }
    }

    /**
     * Методы работ на стороне клиента
     */
    this.client = {
        /**
         * Получение данных по форме после подгрузки формы
         */
        getFormData: function () {
            return __DATA__;
        },

        /**
         * Получение дефотлных данных по форме после подгрузки формы
         */
        getDefaultFormData: function () {
            return __DATA_NO_CHANGE__;
        },

        /**
         * Получение изменных данных по форме после подгрузки формы
         */
        getModifiedFormdata: function () {
            if (!__LOADFORM__) {
                return null;
            }

            let arr = __CLIENT__.getDefaultFormData();

            if (!Array.isArray(arr)) {
                arr = [];
            }

            return __CLIENT__.getDefaultFormData().reduce(function (modifyListComponents, component) {
                let modifyComponent = __DATA__.find(function (x) {
                    return x.id === component.id;
                });

                if (modifyComponent && JSON.stringify(component) !== JSON.stringify(modifyComponent)) {
                    modifyListComponents.push(modifyComponent);
                }

                return modifyListComponents;
            }, []);
        },

        /**
         * Получение конфигурации данных по форме после подгрузки формы
         */
        getExtFormData: function () {
            return __FORM_EXT__;
        },

        /**
         * Получение идентификатора документа `documentID` после подгрузки формы
         */
        getDocumentID: function () {
            return __FULL_DATA__.documentID;
        },

        /**
         * Получение идентификатора данных по форме `dataUUID` после подгрузки формы
         */
        getDataUUID: function () {
            return __FULL_DATA__.uuid;
        },

        /**
         * Получение идентификатора ноды `nodeUUID` после подгрузки формы
         */
        getNodeUUID: function () {
            return __FULL_DATA__.nodeUUID;
        },

        /**
         * Получение идентификатора формы `formUUID` после подгрузки формы
         */
        getFormUUID: function () {
            return __FULL_DATA__.form;
        },

        /**
         * Получение код формы `formCode` после подгрузки формы
         */
        getFormCode: function () {
            return __CLIENT__.getExtFormData().code;
        },

        /**
         * Получение версии конфигурации формы `formVersion` после подгрузки формы
         */
        getFormVersion: function () {
            return __FULL_DATA__.formVersion;
        },

        /**
         * Получение версии документа `version` после подгрузки формы
         */
        getDocumentVersion: function () {
            return __FULL_DATA__.version;
        },

        /**
         * Получение `registryID` после подгрузки формы
         */
        getRegistryID: function () {
            return __FULL_DATA__.registryID;
        },

        /**
         * Метод загрузки формы
         * @param {String | Integer} dataUUID   Идентификатор данных по форме
         */
        load: function (dataUUID, version) {
            if (!__LOADFORM__ || !dataUUID) {
                return __CLIENT__;
            }

            let data = __CLIENT__.api('asforms/data/' + dataUUID + (version ? '?version=' + version : ''));
            let dataExt = __CLIENT__.api('asforms/form_ext?formID=' + data.form + (version ? '&version=' + version : ''));
            let document = __CLIENT__.api('asforms/data/document?dataUUID=' + dataUUID);

            mergeFormExtToFormData(dataExt, data);

            if (document[dataUUID]) {
                data.documentID = document[dataUUID].documentID;
                data.registryID = document[dataUUID].registryID;
            }

            data.dataExt = dataExt;
            data.formCode = dataExt.code;
            data.dataNoChange = JSON.parse(JSON.stringify(data.data));

            __FULL_DATA__ = data;
            __DATA__ = data.data;
            __FORM_EXT__ = data.dataExt;
            __DATA_NO_CHANGE__ = data.dataNoChange;

            __MULTI_FORMS_DATA__[dataUUID] = __FULL_DATA__;

            if (!__FORMDATA__) {
                __FORMDATA__ = __MULTI_FORMS_DATA__[dataUUID];
            }

            return __CLIENT__;
        },

        /**
         * Метод загрузки нескольких форм
         * @param {Array | String | Integer} dataUUIDs   идентификатор(ы) данных по форме
         */
        multiLoad: function (dataUUIDs) {
            if (!__LOADFORM__ || !dataUUIDs) {
                return __CLIENT__;
            }

            if (!Array.isArray(dataUUIDs)) {
                dataUUIDs = [dataUUIDs];
            }

            let queryGetDocIds = dataUUIDs.map(function (x) {
                return 'dataUUID=' + x;
            }).join('&');

            let documentIds = __CLIENT__.api('asforms/data/document?' + queryGetDocIds);

            dataUUIDs.forEach(function (dataUUID) {
                let data = __CLIENT__.api('asforms/data/' + dataUUID);
                let dataExt = __CLIENT__.api('asforms/form_ext?formID=' + data.form);

                if (documentIds[dataUUID]) {
                    data.documentID = documentIds[dataUUID].documentID;
                    data.registryID = documentIds[dataUUID].registryID;
                }

                data.dataExt = dataExt;
                data.formCode = dataExt.code;
                data.dataNoChange = JSON.parse(JSON.stringify(__DATA__));

                __MULTI_FORMS_DATA__[dataUUID] = data;
            });

            return __CLIENT__;
        },

        /**
         * Получение данных загруженных форм
         */
        getFormsList: function () {
            return __MULTI_FORMS_DATA__;
        },

        /**
         * Выбрать с какой формой будем работать
         */
        selectForm: function (dataUUID) {
            if (!Object.keys(__MULTI_FORMS_DATA__).length || !__MULTI_FORMS_DATA__[dataUUID]) {
                return __CLIENT__;
            }

            __FULL_DATA__ = __MULTI_FORMS_DATA__[dataUUID];
            __DATA__ = __MULTI_FORMS_DATA__[dataUUID].data;
            __FORM_EXT__ = __MULTI_FORMS_DATA__[dataUUID].dataExt;
            __DATA_NO_CHANGE__ = __MULTI_FORMS_DATA__[dataUUID].dataNoChange;

            return __CLIENT__;
        },

        /**
         * Вернуть самую первую форму загруженную не с мультизагрузки (Если такая была)
         */
        selectFirstLoadForm: function () {
            if (!__FORMDATA__) {
                return;
            }

            __FULL_DATA__ = __FORMDATA__;
            __DATA__ = __FORMDATA__.data;
            __FORM_EXT__ = __FORMDATA__.dataExt;
            __DATA_NO_CHANGE__ = __FORMDATA__.dataNoChange;

            return __CLIENT__;
        },

        /**
         * Метод получения значения компонента
         * @param   {String}    arg1    ID компонента
         * @param   {String}    arg2    Если компонент вне дин таблицы то ключ *не обязательно* иначе ID таблицы
         * @param   {String}    arg3    Если компонент вне дин таблицы *не обязательно* иначе номер строки (ряда)
         * @param   {String}    arg4    Если компонент в дин таблицы то ключ иначе *не обязательно*
         */
        getValue: function () {
            let data;
            let args = {
                c: arguments[0],
                ot: arguments[1],
                b: arguments[2],
                o: arguments[3]
            };

            let isCompToTable = typeof (args.c) === 'string' && (typeof (args.b) === 'number' || (typeof (+args.b) === 'number' && !isNaN(+args.b)));

            if (
                isCompToTable && !__CLIENT__.isExistsComp(args.c, args.ot, args.b) ||
                !__CLIENT__.isExistsComp(args.c)
            ) {
                return null;
            }

            __DATA__.forEach(function (item) {
                if (isCompToTable && item.id == args.ot) {
                    /**
                     * Динамические компоненты по форме в дин.таблице
                     */
                    if (args.b.toString() == '0') {
                        args.b = 1;
                    } else if (!isNaN(args.b)) {
                        args.b = +args.b;
                    }

                    if (!item.hasOwnProperty('data')) {
                        item.data = [];
                    }

                    item.data.forEach(function (bItem) {
                        if (bItem.id === args.c + '-b' + args.b) {
                            data = args.o ? (bItem[args.o] ? bItem[args.o] : undefined) : bItem;
                            return;
                        }
                    });

                    return;
                } else if (!isCompToTable && item.id == args.c) {
                    data = args.ot ? (item[args.ot] ? item[args.ot] : undefined) : item;

                    return;
                }
            });

            if (data === undefined) {
                switch (args.ot) {
                    case 'data':
                        data = [];
                        break;
                    case 'value':
                    case 'key':
                        data = '';
                        break;
                    default:
                        data = false;
                }
            }

            return data;
        },

        /**
         * Метод установки нового значения компоненту
         * @param   {String}    arg1    ID компонента
         * @param   {String}    arg2    Если компонент вне дин таблицы то значение или объект иначе ID таблицы
         * @param   {String}    arg3    Если компонент вне дин таблицы то ключ для значения *не обязательно* иначе номер строки (ряда)
         * @param   {String}    arg4    Если компонент в дин таблицы то значение или объект иначе *не обязательно*
         * @param   {String}    arg5    Если компонент в дин таблицы то ключ для значения иначе *не обязательно*
         *
         * Пример:
         *      Вне дин.таблицы: setValue('component1','Текст');  - "Текст" запишется в ключ "value" по умолчанию
         *      Вне дин.таблицы: setValue('component2',{
         *          key: 'xxxx-xxx-xxxx',
         *          value:  'xxxx'
         *      },'value');  - "Object" запишется в ключ "value" а если не передавать ключ то пойдет перезаписать данных компонента
         *
         *
         *      Дин.таблица: setValue('component1','table',1,'Текст');  - "Текст" запишется в ключ "value" по умолчанию
         *      Дин.таблица: setValue('component2','table',1,45000,'key');  - "45000" в скобках или без запишется в ключ "key"
         *      Дин.таблица: setValue('component3','table',1,{
         *          key: 'xxxx-xxx-xxxx',
         *          value:  'xxxx'
         *      },'value');  - "Object" запишется в ключ "value" а если не передавать ключ то пойдет перезаписать данных компонента
         */
        setValue: function () {
            let args = {
                c: arguments[0], // Идентификатор компонента
                vt: arguments[1], // Значение или идентификатор таблицы
                ob: arguments[2], // Ключ для установки значения или номер строки в дин.таблице
                v: arguments[3], // Значение если работаем с дин.таблицей
                o: arguments[4] // Ключ для установки значения если работаем с дин.таблицей
            };

            let isCompToTable = typeof (args.c) === 'string' && (typeof (args.ob) === 'number' || (typeof (+args.ob) === 'number' && !isNaN(+args.ob)));

            if (
                isCompToTable && !__CLIENT__.isExistsComp(args.c, args.vt, args.ob) ||
                !__CLIENT__.isExistsComp(args.c)
            ) {
                return __CLIENT__;
            }

            __DATA__.forEach(function (item) {
                if (isCompToTable && item.id == args.vt) {
                    /**
                     * Динамические компоненты по форме в дин.таблице
                     */
                    if (args.ob.toString() == '0') {
                        args.ob = 1;
                    } else if (!isNaN(args.ob)) {
                        args.ob = +args.ob;
                    }

                    if (!item.hasOwnProperty('data')) {
                        item.data = [];
                    }

                    item.data.forEach(function (bItem) {
                        if (bItem.id === args.c + '-b' + args.ob) {
                            compProcedure(bItem.type, args, isCompToTable);

                            bItem = Object.assign(bItem, args.v);
                            return;
                        }
                    });

                    return;
                } else if (!isCompToTable && item.id == args.c) {
                    /**
                     * Статические компоненты по форме
                     */
                    compProcedure(item.type, args, isCompToTable);

                    item = Object.assign(item, args.vt);
                    return;
                }
            });

            return __CLIENT__;
        },

        /**
         * Метод получения конвертированной таблицы
         * @param   {String}    tableID     Идентификатор (id) таблицы
         */
        convertTable: function (tableID) {
            if (!__CLIENT__.isExistsComp(tableID)) {
                return null;
            }

            let arr = [];
            let data = __CLIENT__.getValue(tableID, 'data');

            data.forEach(function (item) {
                let idx = item.id.lastIndexOf('-b');
                let bIndex = item.id.substr(idx + 2);
                let componentId = item.id.substr(0, idx);

                if (~item.id.lastIndexOf('-b') && bIndex && !isNaN(bIndex)) {
                    if ((bIndex in arr) === false) {
                        arr[bIndex] = {};
                    }

                    arr[bIndex][componentId] = item;
                    return;
                }
            });

            for (let i = 0; i < arr.length; i++) {
                if (arr[i] == null) {
                    arr.splice(i--, 1);
                }
            }

            return arr;
        },

        /**
         * Метод получения всех компонентов в виде списка
         */
        getAsfData: function () {
            if (!__LOADFORM__) {
                return null;
            }

            let arr = [];

            __DATA__.forEach(function (item) {
                if (item.type == 'appendable_table') {
                    let tmp_data = JSON.parse(JSON.stringify((!item.data ? [] : item.data)));

                    tmp_data.forEach(function (item_tbl) {
                        let idx = item_tbl.id.lastIndexOf('-b');

                        if (~idx) {
                            let compId = item_tbl.id;

                            item_tbl.id = compId.substr(0, idx);
                            item_tbl.____tableID____ = item.id;
                            item_tbl.____tableType____ = item.type;
                            item_tbl.____tableBlockIndex____ = compId.substr(idx + 2);

                            arr.push(item_tbl);
                        }
                    });
                }

                arr.push(item);
            });

            return arr;
        },

        /**
         * Метод добавляет данные в `dataList`
         * @param   {Object}    object    Объект или массив объектов
         */
        addAsfData: function (object) {
            if (!__LOADFORM__) {
                return __CLIENT__;
            }

            if (!Array.isArray(object)) {
                object = [object];
            }

            let oldList = __CLIENT__.getAsfData();

            object.forEach(function (item) {
                oldList.push(item);
            });

            __CLIENT__.setAsfData(oldList);

            return __CLIENT__;
        },

        /**
         * Метод изменения всех компонентов из листа
         * @param   {Array}     newList    Массив - лист объектов
         */
        setAsfData: function (newList) {
            if (!__LOADFORM__) {
                return __CLIENT__;
            }

            let arr = [];
            let objList = {};

            newList.forEach(function (item) {
                if (item.____tableType____ && item.____tableID____ && item.____tableBlockIndex____) {
                    let tmpItem = JSON.parse(JSON.stringify(item));

                    tmpItem.id += '-b' + item.____tableBlockIndex____;

                    delete tmpItem.____tableID____;
                    delete tmpItem.____tableType____;
                    delete tmpItem.____tableBlockIndex____;

                    if (!objList.hasOwnProperty(item.____tableID____)) {
                        objList[item.____tableID____] = [];
                    }

                    objList[item.____tableID____].push(tmpItem);

                    return;
                }

                arr.push(item);
            });

            Object.keys(objList).forEach(function (tableId) {
                let table = arr.find(function (x) {
                    return x.id === tableId;
                });

                if (table) {
                    table.data = objList[tableId];
                }
            });

            setData(arr);

            return __CLIENT__;
        },

        /**
         * Проверяет существует ли компонент в данных по форме
         */
        isExistsComp: function (componentId, tableId, index) {
            if (!__LOADFORM__) {
                return null;
            }

            let isComp = !!__CLIENT__.getAsfData().find(function (x) {
                if (tableId && index && x.____tableID____ && x.____tableBlockIndex____) {
                    return x.____tableID____ === tableId && x.____tableBlockIndex____ == index && x.id === componentId;
                } else if ((!tableId || !index) && !x.____tableID____ && !x.____tableBlockIndex____) {
                    return x.id === componentId;
                }
            });

            return isComp;
        },

        /**
         * Получение всех свойств конфигурации компонента после подгрузки формы
         * @param   {String}        componentId     Идентификатор компонента
         * @param   {tableId}       tableId         Идентификатор дин.таблицы (если компонент в дин.таблице)
         */
        getPropsByComp: function (componentId, tableId) {
            if (
                tableId && componentId && __CLIENT__.isExistsComp(tableId) ||
                componentId && __CLIENT__.isExistsComp(componentId)
            ) {
                return __CLIENT__;
            }

            let data = (__FORM_EXT__.properties || []);

            function getPropByCompId(data, componentId) {
                let comp = data.find(function (x) {
                    return x.id === componentId;
                });

                return comp && comp;
            }

            let compProp = tableId ? getPropByCompId((getPropByCompId(data, tableId).properties || []), componentId) : getPropByCompId(data, componentId);

            return compProp;
        },

        /**
         * Получение свойства `config` конфигурации компонента после подгрузки формы
         * @param   {String}        componentId     Идентификатор компонента
         * @param   {tableId}       tableId         Идентификатор дин.таблицы (если компонент в дин.таблице)
         */
        getConfigByComp: function (componentId, tableId) {
            let compConfig = __CLIENT__.getPropsByComp(componentId, tableId);

            return compConfig && (compConfig.config || {});
        },

        /**
         * [Add] Метод добавления строк в дин.таблицу
         * @param   {String}    tableID     Идентификатор (id) таблицы
         * @param   {Int}       count       кол-во строк по умолчанию `1`
         * @param   {Boolean}   isVisual      Если данный параметр true то ряд(ы) не добавятся в таблицу по умолчанию `false`
         */
        addRowTable: function (tableID, count, isVisual) {
            if (!__CLIENT__.isExistsComp(tableID)) {
                return null;
            }

            let row = false;

            if (!(+count) || !count) {
                count = 1;
            }

            let tableRowCount = __CLIENT__.getRowsCount(tableID);
            let tableConfig = __CLIENT__.getConfigByComp(tableID);
            let tableProps = __CLIENT__.getPropsByComp(tableID);

            if (tableConfig && tableConfig.appendRows) {
                if (!row) {
                    row = [];
                }

                for (let i = 0; i < count; i++) {
                    tableRowCount++;

                    tableProps.properties.forEach(function (x) {
                        let comp = JSON.parse(JSON.stringify(x));

                        let compModel = comp.data || {
                            id: comp.id,
                            type: comp.type
                        };

                        compModel.____tableID____ = tableID;
                        compModel.____tableType____ = 'appendable_table';
                        compModel.____tableBlockIndex____ = '' + tableRowCount;

                        row.push(compModel);
                    });
                }

                if (!isVisual && row && row.length > 0) {
                    __CLIENT__.addAsfData(row);
                }

                return;
            }

            return row;
        },

        /**
         * [Remove] Метод удаления строк из дин.таблицы
         * @param {String} tableID  Идентификатор (id) таблицы
         * @param {Int | Array} index  номер строки
         */
        removeRowTable: function (tableID, index) {
            if (!__CLIENT__.isExistsComp(tableID)) {
                return __CLIENT__;
            }

            let dataList = __CLIENT__.getAsfData();
            let newList = [];
            let visualRows = [];
            let tmp = {
                t1: [],
                t2: []
            };

            if (!Array.isArray(index)) {
                index = [index];
            }

            dataList.forEach(function (item) {
                if (item.____tableID____ == tableID && item.____tableBlockIndex____ && (~index.indexOf('' + item.____tableBlockIndex____) || ~index.indexOf(+item.____tableBlockIndex____))) {
                    return;
                }

                newList.push(item);

                if (item.____tableID____ == tableID && item.____tableBlockIndex____) {
                    visualRows.push({
                        textIdx: +item.____tableBlockIndex____,
                        idx: newList.length - 1
                    });
                }
            });

            visualRows
                .sort(function (i, j) {
                    return i.textIdx > j.textIdx;
                })
                .forEach(function (i) {
                    if (!tmp.t1[i.textIdx]) {
                        tmp.t1[i.textIdx] = [];
                    }

                    tmp.t1[i.textIdx].push(i.idx);
                });

            tmp.t1.forEach(function (item) {
                tmp.t2.push(item);
            });

            tmp.t2.forEach(function (iArr, k) {
                iArr.forEach(function (i) {
                    newList[i].____tableBlockIndex____ = '' + (k + 1);
                });
            });

            __CLIENT__.setAsfData(newList);

            return __CLIENT__;
        },

        /**
         * Метод получения строк (рядов) в дин.таблице
         * @param   {String}    tableID     Идентификатор (id) таблицы
         */
        getRowsCount: function (tableID) {
            return __CLIENT__.isExistsComp(tableID) && __CLIENT__.convertTable(tableID).length;
        },

        /**
         * Метод очищает содержимое дин.таблицы
         * @param {String} tableID  Идентификатор (id) таблицы
         */
        clearTable: function (tableID) {
            if (tableID) {
                __DATA__.forEach(function (item) {
                    if (item.id === tableID) {
                        item.data = [];
                        return;
                    }
                });
            }

            return __CLIENT__;
        },

        /**
         * Метод выполнения API Synergy
         * @param {String}    method       Метод запроса /rest/api/asforms/data/404 или asforms/data/404
         * @param {Object}    options      Это объект в который входят такие параметры как:
         *       @param {String}    url         Пользовательский URL адрес запроса; пример: http://127.0.0.1:8080/Synergy/rest/api/
         *       @param {String}    type        Тип запроса: GET или POST по умолчанию `GET`
         *       @param {Object}    data        Объект в которой идет ключ и значение например { key: 'xxx', key2: 'xxx' }
         *       @param {String}    dataType    Тип данных `text|json|x-www-form-urlencoded` по умолчанию `json`
         *       @param {Object}    headers     Заголовки запроса
         */
        api: function (method, options) {
            if (!__LOADFORM__) {
                return;
            }

            if (!options) {
                options = {}
            }

            if (__IS_SERVER__) {
                let api;
                let result;
                let _p = [];

                method = options.url ? method : method.replace(/(.*rest\/api\/)/, '');

                let type = options.type || 'GET';
                let host = options.url || 'http://127.0.0.1:8080/Synergy/rest/api/';
                let client = new org.apache.commons.httpclient.HttpClient();
                let creds = new org.apache.commons.httpclient.UsernamePasswordCredentials(__LOGIN__, __PASSWORD__);

                let fullURL = '';

                client.getParams().setAuthenticationPreemptive(true);
                client.getState().setCredentials(org.apache.commons.httpclient.auth.AuthScope.ANY, creds);

                switch (type) {
                    case 'GET':
                        if (!options.dataType) {
                            options.dataType = 'json';
                        }

                        if (options && options.data) {
                            for (let p in options.data) {
                                _p.push(p + '=' + options.data[p]);
                            }
                        }

                        fullURL = host + method;

                        if (_p.length) {
                            fullURL += (~fullURL.indexOf('?') ? '&' : '?') + _p.join('&');
                        }

                        api = new org.apache.commons.httpclient.methods.GetMethod(fullURL);

                        requestSetDataType(api, options.dataType);
                        requestSetHeadersApi(api, options.headers);

                        client.executeMethod(api);
                        result = api.getResponseBodyAsString();
                        break;
                    case 'POST':
                        if (!options.dataType) {
                            options.dataType = 'x-www-form-urlencoded';
                        }

                        fullURL = host + method;
                        api = new org.apache.commons.httpclient.methods.PostMethod(fullURL);

                        requestSetDataType(api, options.dataType);
                        requestSetHeadersApi(api, options.headers);

                        if (options && options.data) {
                            let headerContentType = api.getRequestHeader('Content-Type');
                            headerContentType = headerContentType && headerContentType.getValue();

                            if (typeof options.data === 'string' && ~headerContentType.indexOf('application/json')) {
                                let entity = new org.apache.commons.httpclient.methods.StringRequestEntity(options.data, 'application/json', 'UTF-8');
                                api.setRequestEntity(entity);
                            } else {
                                for (let p in options.data) {
                                    api.addParameter(p, options.data[p]);
                                }
                            }
                        }

                        client.executeMethod(api);
                        result = api.getResponseBodyAsString().toString();
                        break;
                    default:
                }

                if (api.getStatusCode() !== 200) {
                    console.info('\n\n');
                    console.error(
                        __LIBRARY_PREFIX__ + 'Request failed.' +
                        '\nURL: ' + fullURL +
                        '\nStatus: ' + api.getStatusCode() +
                        '\nStatusCode: ' + api.getStatusLine().toString().split(' ').slice(-2).join(' ') +
                        '\nContent-Type: ' + api.getResponseHeader('Content-Type').getValue() +
                        '\nResponse Body: ' + JSON.stringify(JSON.parse(result), null, 4) + '\n\n'
                    );
                }

                api.releaseConnection();

                return (options && options.dataType == 'text' ? result : JSON.parse(result));
            } else {
                if (!options.dataType) {
                    options.dataType = 'json';
                }

                try {
                    const config = {
                        url: options.url ? options.url + method : __HOST__ + '/Synergy/rest/api/' + method.replace(/(.*rest\/api\/)/, ''),
                        type: (!options || !options.hasOwnProperty('type') ? 'GET' : options.type),
                        async: false,
                        headers: options.headers || {},
                        data: (!options || !options.hasOwnProperty('data') ? null : options.data),
                        dataType: options.dataType
                    };

                    if (config.type === 'GET' && config.data) {
                        const argsParams = Object.keys(config.data).reduce(function (acc, x) {
                            acc.push([x, config.data[x]].join('='));

                            return acc;
                        }, []);

                        if (argsParams.length) {
                            config.url += (~config.url.indexOf('?') ? '&' : '?') + argsParams.join('&');
                        }
                    }

                    const xhr = new XMLHttpRequest();

                    xhr.open(config.type, config.url, config.async);

                    xhr.withCredentials = true;

                    xhr.onreadystatechange = function () {
                        if (xhr.readyState == XMLHttpRequest.DONE) {
                            try {
                                xhr.responseJSON = JSON.parse(xhr.responseText);
                            } catch (e) {
                                xhr.responseJSON = null;
                            }
                        }
                    }

                    xhr.onerror = function () {
                        console.error('{}', {
                            url: config.url,
                            status: this.status,
                            error: xhr.responseText || 'Network request failed'
                        });
                    }

                    xhr.setRequestHeader('Authorization', 'Basic ' + __BASIC_AUTH__);

                    requestSetDataType(xhr, config.dataType);
                    requestSetHeadersApi(xhr, config.headers);

                    xhr.send(config.data);

                    return xhr[(options.dataType && options.dataType == 'text' ? 'responseText' : 'responseJSON')];
                } catch (err) {
                    return err;
                }
            }
        },

        /**
         * Метод сохранения формы
         */
        save: function () {
            if (!__LOADFORM__) {
                return;
            }

            /**
             * Изменить данные до сохранения данных по форме
             */
            beforeDoSaveFormData();

            let modifiedComponentsList = __CLIENT__.getModifiedFormdata();

            if (modifiedComponentsList.length) {
                let save = __CLIENT__.api('asforms/data/merge', {
                    type: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    data: JSON.stringify({
                        uuid: __CLIENT__.getDataUUID(),
                        data: modifiedComponentsList
                    })
                });

                if (save && +save.errorCode === 0) {
                    modifiedComponentsList.forEach(function(modifyComponent) {
                        const compIndex = __CLIENT__.getDefaultFormData().findIndex(function(component) {
                            return component.id === modifyComponent.id
                        })

                        if(compIndex !== -1) {
                            __CLIENT__.getDefaultFormData()[compIndex] = Object.assign({}, modifyComponent);
                        }
                    });

                    console.info('\n\n' + __LIBRARY_PREFIX__ + '' + JSON.stringify(modifiedComponentsList));
                    console.info(
                        __LIBRARY_PREFIX__ + 'Data changed successfully!' +
                        '\nNumber of components changed: ' + modifiedComponentsList.length +
                        '\ndataUUID: ' + __CLIENT__.getDataUUID() +
                        '\ndocumentID: ' + __CLIENT__.getDocumentID()
                    );
                }

                return save;
            }

            console.info(
                __LIBRARY_PREFIX__ + 'No modified form data' +
                '\ndataUUID: ' + __CLIENT__.getDataUUID() +
                '\ndocumentID: ' + __CLIENT__.getDocumentID()
            );

            return false;
        },

        /**
         * Вспомогательные методы для взаимодействия с API Synergy
         */
        ApiUtils: {
            /**
             * Возвращает `documentID` по dataUUID
             * @param   {String | Int}   dataUUID    Идентификатор данных по форме
             */
            getDocumentID: function (dataUUID) {
                if (!dataUUID) {
                    return;
                }

                return __CLIENT__.api('formPlayer/documentIdentifier?dataUUID=' + dataUUID, {
                    dataType: 'text'
                });
            },

            /**
             * Возвращает `dataUUID` по documentID
             * @param   {String}   documentID    Идентификатор документа
             */
            getDataUUID: function (documentID) {
                if (!documentID) {
                    return;
                }

                return __CLIENT__.api('formPlayer/getAsfDataUUID?documentID=' + documentID);
            },

            /**
             * Возвращает ход выполнения документа
             * @param   {String | Int}   dataUUID    Идентификатор данных по форме
             */
            getProcessExecution: function (dataUUID) {
                if (!dataUUID) {
                    return;
                }

                return __CLIENT__.api('formPlayer/getProcessExecution?dataUUID=' + dataUUID);
            },

            /**
             * Возвращает список реестров (доступных пользователю)
             */
            getRegistryList: function () {
                return __CLIENT__.api('registry/list');
            }
        }
    }

    /**
     * Методы для работ со справочниками
     */
    this.dicts = {
        /**
         * Метод конвертирует данные справочника в читаемый разработчику вид
         */
        convert: function (dictData) {
            let res = {};

            let columnCodes = dictData.columns.reduce(function (obj, x) {
                obj[x.columnID] = x.code;

                return obj;
            }, {});

            dictData.items.forEach(function (x, k) {
                res[k] = {};

                x.values.forEach(function (x) {
                    res[k][columnCodes[x.columnID]] = x.value;
                });
            });

            return res;
        }
    }

    /**
     * Вспомогательные методы и значения
     */
    this.utils = {
        /**
         * UTF-8 encode
         * @param   {String}    str     строка
         */
        utf8_encode: function (str) {
            return unescape(encodeURIComponent(str));
        },

        /**
         * UTF-8 decode
         * @param   {String}    str     строка
         */
        utf8_decode: function (str) {
            return decodeURIComponent(escape(str));
        },

        /**
         * Является ли переданное значение именно объектом
         * @param   {any}     v     любой тип данных
         * @return {Boolean}
         */
        isObject: function (v) {
            return !!v && v.constructor === Object;
        },

        /**
         * Проверяет URL на валидность
         * @param {String} url
         */
        isValidURL: function (url) {
            const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol
                '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|' + // domain name
                '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
                '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
                '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
                '(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator

            return pattern.test(url);
        },

        /**
         * Добавляет ноль в начало числа или строки
         * @param {Int | String} num число
         * @param {Int} len кол-во нулей по умолчанию `2`
         */
        strAddZero: function (num, len) {
            if (!len) {
                len = 2;
            }

            let s = '' + num;

            while (s.length < len) {
                s = '0' + s;
            }

            return s;
        },

        /**
         * Склонение слова
         * @param {Array} arr массив слов
         * @param {Int} i число по которому будем склонять и выдавать 1 из N слов
         * Example: ['Кружк[у]','Кружк[И]','Круже[К]'], 58 = 58 Круже[К]
         */
        getWordDeclination: function (arr, i) {
            let index = i % 10 == 1 && i % 100 != 11 ? 0 : (i % 10 >= 2 && i % 10 <= 4 && (i % 100 < 10 || i % 100 >= 20) ? 1 : 2);

            return {
                num: i || -1,
                index0: (arr[index] && index ? index : -1),
                index1: (arr[index] && index ? index + 1 : -1),
                value: (arr[index] ? '' + arr[index] : '')
            };
        }
    }

    /**
     * Информация о данном классе
     */
    this.about = {
        name: 'jSynergy',
        type: 'Library',
        version: '1.0.9 Release',
        created: '17.10.2018',
        update: '08.03.2022',
        company: 'Arta Software',
        platform_versions: {
            '3.12': '[Not support]',
            '3.14': '[Not support]',
            '3.15': '[Not support] - not verified',
            '4.0': true,
            '4.1': true,
        },
        git: 'https://gitlab.lan.arta.kz/community/synergy-components/-/blob/master/interpreter/jSynergyDocs.md',
        contributes: {},
        author: {
            username: 'yandexphp',
            telegram: '@yandexphp',
        },
    }

    /**
     * Очистить работу
     */
    this.destroy = function () {
        __LOGIN__ = null;
        __PASSWORD__ = null;
        __HOST__ = null;
        __HOSTNAME__ = null;
        __PROTOCOL__ = null;
        __BASIC_AUTH__ = null;

        __FULL_DATA__ = null;
        __DATA__ = null;
        __FORM_EXT__ = null;
        __DATA_NO_CHANGE__ = null;
        __FORMDATA__ = null;

        __LOADFORM__ = false;

        __CONNECT_LIST__ = {};
        __MULTI_FORMS_DATA__ = {};

        __CLIENT__ = __CLASS__.client;
        __DICTS__ = __CLASS__.dicts;
        __API_UTILS__ = __CLIENT__.ApiUtils;
        __UTILS__ = __CLASS__.utils;
    }

    __CLASS__ = this;

    (function __constructor() {
        __LIBRARY_PREFIX__ = '\n[jSynergy ' + __CLASS__.about.type + ' ' + __CLASS__.about.version + ']: ';

        if (!Object.assign) {
            Object.defineProperty(Object, 'assign', {
                enumerable: false,
                configurable: true,
                writable: true,
                value: function (target, firstSource) {
                    'use strict';
                    if (target === undefined || target === null) {
                        throw new TypeError('Cannot convert first argument to object');
                    }

                    let to = Object(target);

                    for (var i = 1; i < arguments.length; i++) {
                        let nextSource = arguments[i];

                        if (nextSource === undefined || nextSource === null) {
                            continue;
                        }

                        let keysArray = Object.keys(Object(nextSource));

                        for (let nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
                            let nextKey = keysArray[nextIndex];
                            let desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);

                            if (desc !== undefined && desc.enumerable) {
                                to[nextKey] = nextSource[nextKey];
                            }
                        }
                    }
                    return to;
                }
            });
        }

        if (!Array.prototype.find) {
            Object.defineProperty(Array.prototype, 'find', {
                value: function (predicate) {
                    if (this == null) {
                        throw new TypeError('"this" is null or not defined');
                    }

                    let o = Object(this);
                    let len = o.length >>> 0;

                    if (typeof predicate !== 'function') {
                        throw new TypeError('predicate must be a function');
                    }

                    let thisArg = arguments[1];
                    let k = 0;

                    while (k < len) {
                        let kValue = o[k];

                        if (predicate.call(thisArg, kValue, k, o)) {
                            return kValue;
                        }

                        k++;
                    }

                    return undefined;
                },
                configurable: true,
                writable: true
            });
        }

        if (!Array.prototype.includes) {
            Object.defineProperty(Array.prototype, 'includes', {
                value: function (searchElement, fromIndex) {
                    if (this == null) {
                        throw new TypeError('"this" is null or not defined');
                    }

                    let o = Object(this);
                    let len = o.length >>> 0;

                    if (len === 0) {
                        return false;
                    }

                    let n = fromIndex | 0;
                    let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);

                    function sameValueZero(x, y) {
                        return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
                    }

                    while (k < len) {
                        if (sameValueZero(o[k], searchElement)) {
                            return true;
                        }

                        k++;
                    }

                    return false;
                }
            });
        }

        if (!Date.prototype.getListWeekDays) {
            Object.defineProperty(Date.prototype, 'getListWeekDays', {
                value: function () {
                    const list = {
                        sunday: 0,      // Воскресенье
                        monday: 1,      // Понедельник
                        tuesday: 2,     // Вторник
                        wednesday: 3,   // Среда
                        thursday: 4,    // Четверг
                        friday: 5,      // Пятница
                        saturday: 6,    // Суббота
                    };

                    return list;
                }
            });
        }

        if (!Date.prototype.getWeekDay) {
            Object.defineProperty(Date.prototype, 'getWeekDay', {
                value: function () {
                    return Object.keys(this.getListWeekDays())[this.getDay()];
                }
            });
        }

        __CLASS__.destroy();
    })();

    return __CLASS__;
}

/**
 * Создаем класс jSynergy
 */
let jSynergy = new __classSynergy();
jSynergy.setConnection(false, login, password);