/*
    Парус 8 - Панели мониторинга
    Ядро: Вспомогательные функции
*/

//---------------------
//Подключение библиотек
//---------------------

import { XMLParser, XMLBuilder } from "fast-xml-parser"; //Конвертация XML в JSON и JSON в XML
import dayjs from "dayjs"; //Работа с датами

//---------
//Константы
//---------

//Коды типовых размеров экранов
const DISPLAY_SIZE_CODE = {
    XS: "XS", //eXtra Small - супер маленький экран
    SM: "SM", //Small - маленький экран
    MD: "MD", //Middle - средний экран
    LG: "LG" //Large - большой экран
};

//Типовые размеры экранов
const DISPLAY_SIZE = {
    [DISPLAY_SIZE_CODE.XS]: { WIDTH_FROM: 0, WIDTH_TO: 767 }, //eXtra Small - супер маленький экран < 768px
    [DISPLAY_SIZE_CODE.SM]: { WIDTH_FROM: 768, WIDTH_TO: 991 }, //Small - маленький экран >= 768px
    [DISPLAY_SIZE_CODE.MD]: { WIDTH_FROM: 992, WIDTH_TO: 1199 }, //Middle - средний экран >= 992px
    [DISPLAY_SIZE_CODE.LG]: { WIDTH_FROM: 1200, WIDTH_TO: 1000000 } //Large - большой экран >= 1200px
};

//Типовые пути конвертации в массив (при переводе XML -> JSON)
const XML_ALWAYS_ARRAY_PATHS = [
    "XRESPOND.XPAYLOAD.XOUT_ARGUMENTS",
    "XRESPOND.XPAYLOAD.XDATA_GRID.rows",
    "XRESPOND.XPAYLOAD.XDATA_GRID.columnsDef",
    "XRESPOND.XPAYLOAD.XDATA_GRID.columnsDef.values",
    "XRESPOND.XPAYLOAD.XDATA_GRID.groups",
    "XRESPOND.XPAYLOAD.XGANTT.taskAttributes",
    "XRESPOND.XPAYLOAD.XGANTT.taskColors",
    "XRESPOND.XPAYLOAD.XGANTT.tasks",
    "XRESPOND.XPAYLOAD.XGANTT.tasks.dependencies",
    "XRESPOND.XPAYLOAD.XCHART.labels",
    "XRESPOND.XPAYLOAD.XCHART.datasets",
    "XRESPOND.XPAYLOAD.XCHART.datasets.data",
    "XRESPOND.XPAYLOAD.XCHART.datasets.items",
    "XRESPOND.XPAYLOAD.XCYCLOGRAM.taskAttributes",
    "XRESPOND.XPAYLOAD.XCYCLOGRAM.columns",
    "XRESPOND.XPAYLOAD.XCYCLOGRAM.groups",
    "XRESPOND.XPAYLOAD.XCYCLOGRAM.tasks"
];

//Типовые шаблоны конвертации в массив (при переводе XML -> JSON)
const XML_ALWAYS_ARRAY_PATH_PATTERNS = [
    /(.*)XDATA_GRID.rows$/,
    /(.*)XDATA_GRID.columnsDef$/,
    /(.*)XDATA_GRID.columnsDef.values$/,
    /(.*)XDATA_GRID.groups$/,
    /(.*)XGANTT.taskAttributes$/,
    /(.*)XGANTT.taskColors$/,
    /(.*)XGANTT.tasks$/,
    /(.*)XGANTT.tasks.dependencies$/,
    /(.*)XCHART.labels$/,
    /(.*)XCHART.datasets$/,
    /(.*)XCHART.datasets.data$/,
    /(.*)XCHART.datasets.items$/,
    /(.*)XCYCLOGRAM.taskAttributes$/,
    /(.*)XCYCLOGRAM.columns$/,
    /(.*)XCYCLOGRAM.groups$/,
    /(.*)XCYCLOGRAM.tasks$/
];

//Типовой постфикс тега для массива (при переводе XML -> JSON)
const XML_ALWAYS_ARRAY_POSTFIX = "__SYSTEM__ARRAY__";

//Типовые шаблоны конвертации значения атрибута в строку (при переводе XML -> JSON)
const XML_ATTR_ALWAYS_STR_PATH_PATTERNS = [
    /(.*)XDATA_GRID.columnsDef.name$/,
    /(.*)XDATA_GRID.columnsDef.caption$/,
    /(.*)XDATA_GRID.columnsDef.parent$/,
    /(.*)XDATA_GRID.groups.name$/,
    /(.*)XDATA_GRID.groups.caption$/,
    /(.*)XCYCLOGRAM.columns.name$/,
    /(.*)XCYCLOGRAM.groups.name$/
];

//-----------
//Тело модуля
//-----------

//Проверка существования значения
const hasValue = value => typeof value !== "undefined" && value !== null && value !== "";

//Проверка типа устройства
const getDisplaySize = () => {
    let res = DISPLAY_SIZE_CODE.MD;
    Object.keys(DISPLAY_SIZE).map(dspl => {
        if (window.innerWidth >= DISPLAY_SIZE[dspl].WIDTH_FROM && window.innerWidth <= DISPLAY_SIZE[dspl].WIDTH_TO) res = dspl;
    });
    return res;
};

//Глубокое копирование объекта
const deepCopyObject = obj => (structuredClone ? structuredClone(obj) : JSON.parse(JSON.stringify(obj)));

//Конвертация объекта в Base64 XML
const object2XML = (obj, builderOptions) => {
    const builder = new XMLBuilder(builderOptions);
    return builder.build(obj);
};

//Конвертация объекта в Base64 XML
const object2Base64XML = (obj, builderOptions) => btoa(unescape(encodeURIComponent(object2XML(obj, builderOptions))));

//Конвертация XML в JSON
const xml2JSON = ({ xmlDoc, isArray, transformTagName, tagValueProcessor, attributeValueProcessor, useDefaultPatterns = true }) => {
    return new Promise((resolve, reject) => {
        try {
            let opts = {
                ignoreDeclaration: true,
                ignoreAttributes: false,
                parseAttributeValue: true,
                attributeNamePrefix: ""
            };
            if (useDefaultPatterns) {
                opts.isArray = (name, jPath, isLeafNode, isAttribute) =>
                    XML_ALWAYS_ARRAY_PATHS.indexOf(jPath) !== -1 ||
                    XML_ALWAYS_ARRAY_PATH_PATTERNS.some(pattern => pattern.test(jPath)) ||
                    jPath.endsWith(XML_ALWAYS_ARRAY_POSTFIX) ||
                    (isArray ? isArray(name, jPath, isLeafNode, isAttribute) : undefined);
                opts.attributeValueProcessor = (name, val, jPath) =>
                    XML_ATTR_ALWAYS_STR_PATH_PATTERNS.some(pattern => pattern.test(`${jPath}.${name}`))
                        ? undefined
                        : attributeValueProcessor
                        ? attributeValueProcessor(name, val, jPath)
                        : val;
            } else {
                if (isArray) opts.isArray = isArray;
                if (attributeValueProcessor) opts.attributeValueProcessor = attributeValueProcessor;
            }
            if (transformTagName) opts.transformTagName = transformTagName;
            if (tagValueProcessor) opts.tagValueProcessor = tagValueProcessor;
            const parser = new XMLParser(opts);
            resolve(parser.parse(xmlDoc));
        } catch (e) {
            reject(e);
        }
    });
};

//Форматирование даты в формат РФ
const formatDateRF = value => (value ? dayjs(value).format("DD.MM.YYYY") : null);

//Форматирование даты и времени в формат РФ
const formatDateTimeRF = value => (value ? dayjs(value).format("DD.MM.YYYY HH:mm:ss") : null);

//Форматирование даты в формат JSON (только дата, без времени)
const formatDateJSONDateOnly = value => (value ? dayjs(value).format("YYYY-MM-DD") : null);

//Форматирование числа в "Денежном" формате РФ
const formatNumberRFCurrency = value => (hasValue(value) ? new Intl.NumberFormat("ru-RU", { minimumFractionDigits: 2 }).format(value) : null);

//Форматирование текста ошибки
const formatErrorMessage = errorMsg => {
    //Инициализируем текст заголовка ошибки
    let text = "";
    //Пробуем извлечь заголовок текста ошибки
    try {
        //Если это ошибка Oracle
        if (errorMsg.match(/^ORA-/)) {
            //Считываем первую строку с заголовочным текстом ошибки
            text = errorMsg.match(/^.*(?=(\nORA-))/)[0];
            //Убираем лишнюю информацию и пробелы
            text = text.replace(/ORA-\d*:/g, "").trim();
        }
        //Если это ошибка PG
        if (errorMsg.match(/^SQL Error/)) {
            //Считываем первую строку с заголовочным текстом ошибки
            text = errorMsg.match(/.*(?=(\n.*Where)|(.*Where))/)[0];
            //Убираем лишнюю информацию и пробелы
            text = text.replace(/SQL Error \[\d*\]: ERROR:/g, "").trim();
        }
    } catch {
        //Если произошла ошибка - оставляем полный текст ошибки
        text = errorMsg;
    }
    //Возвращаем результат
    return { text: text || errorMsg, fullErrorText: text ? errorMsg : null };
};

//Формирование уникального идентификатора
const genGUID = () =>
    "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
        (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
    );

//Формироваеие уникального идентификатора по текущему времени
const genUID = prefix => (prefix ? `${prefix}_${Date.now()}` : `${Date.now()}`);

//----------------
//Интерфейс модуля
//----------------

export {
    hasValue,
    getDisplaySize,
    deepCopyObject,
    object2XML,
    object2Base64XML,
    xml2JSON,
    formatDateRF,
    formatDateTimeRF,
    formatDateJSONDateOnly,
    formatNumberRFCurrency,
    formatErrorMessage,
    genGUID,
    genUID
};
