/*
    Парус 8 - Панели мониторинга - ТОиР - Прогнозирование технического состояния
    Закладка прогнозирования
*/

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

import React, { useState, useContext } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import { Box, Grid } from "@mui/material"; //Интерфейсные компоненты
import { RichTreeView } from "@mui/x-tree-view/RichTreeView"; //Дерево
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с серверомs
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
import { P8PDataGrid, P8P_DATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../config_wrapper"; //Подключение компонентов к настройкам приложения
import { APP_BAR_HEIGHT, TABS_HEIGHT, TABLE_FILTERS_HEIGHT, TABLE_MORE_HEIGHT, SCROLL_STYLES } from "./eqs_tech_cond_forecast_layout"; //Общие Вспомогательные компоненты и вёрстка
import {
    TechObjCard,
    eqConfigTechObjTableValueFormatter,
    eqConfigTechObjTableDataCellRender,
    TechObjChangeEqTchSrv,
    TechObjMakeEqRpSheet,
    TechObjMakeDataSet
} from "./forecast_tab_layout"; //Вспомогательные компоненты и вёрстка
import {
    useEqConfigTree,
    useEqConfigTechObjTable,
    useEqConfigTechObjCard,
    useTechObjModelsList,
    useTechObjForecastHistList,
    needLoadLevel
} from "./forecast_tab_hooks"; //Вспомогательные хуки
import { xml2JSON, object2Base64XML } from "../../core/utils"; //Вспомогательные функции

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

//Начальное состояние флагов обновления
const REFRESH_INITIAL = {
    techObjSpec: 0,
    techObjCard: 0,
    techObjForecastHistList: 0
};

//Начальное состояние диалогов
const DIALOGS_INITIAL = { makeEqRpSheet: false, makeEqDataSet: false, techObjCard: true, changeEqTchSrv: false };

//Начальное состояние спецификации технических объектов
const TECH_OBJ_SPEC_INITIAL = {
    parent: null,
    filters: [],
    orders: [],
    pageNumber: 1
};

//Начальное состояние выбранного технического объекта
const TECH_OBJ_INITIAL = { id: null, objKind: null, measureUnit: null };

//Стили
const STYLES = {
    LEFT_SIDE_GRID: {},
    RIGHT_SIDE_GRID: {},
    TREE_BOX: {
        minHeight: `calc(100vh - ${APP_BAR_HEIGHT} - ${TABS_HEIGHT})`,
        maxHeight: `calc(100vh - ${APP_BAR_HEIGHT} - ${TABS_HEIGHT})`,
        overflowX: "hidden",
        overflowY: "auto",
        padding: "3px",
        ...SCROLL_STYLES
    },
    TECH_OBJ_TABLE: (morePages, filters) => ({
        height: `calc(100vh - ${APP_BAR_HEIGHT} - ${TABS_HEIGHT} - ${morePages ? TABLE_MORE_HEIGHT : "0px"} - ${
            filters ? TABLE_FILTERS_HEIGHT : "0px"
        })`,
        ...SCROLL_STYLES
    })
};

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

//Закладка прогнозирования
const ForecastTab = ({ onGoToAdmin }) => {
    //Подключение к контексту взаимодействия с сервером
    const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);

    //Подключение к контексту сообщений
    const { InlineMsgInfo, showLoader, hideLoader, showMsgErr } = useContext(MessagingСtx);

    //Подключение к контексту приложения
    const { pOnlineShowUnit, pOnlineShowDictionary, pOnlineUserProcedure } = useContext(ApplicationСtx);

    //Собственное состояние - текущая загружаемая ветка
    const [loadingTreeItem, setLoadingTreeItem] = useState(0);

    //Собственное состояние - спецификация технических объектов
    const [techObjSpec, setTechObjSpec] = useState(TECH_OBJ_SPEC_INITIAL);

    //Собственное состояния - карточка технического объекта
    const [techObj, setTechObj] = useState(TECH_OBJ_INITIAL);

    //Собственное состояние - отображаемые диалоги
    const [dialogs, setDialogs] = useState(DIALOGS_INITIAL);

    //Собственное состояния - флаги перезагрузки
    const [refresh, setRefresh] = useState(REFRESH_INITIAL);

    //Загрузчик веток дерева
    const { eQconfigTree } = useEqConfigTree(loadingTreeItem);

    //Загрузчик технических объектов для выбранной ветки
    const { techObjsDataGrid, techObjsDataGridIsLoading } = useEqConfigTechObjTable(
        techObjSpec.parent,
        techObjSpec.filters,
        techObjSpec.orders,
        techObjSpec.pageNumber,
        refresh.techObjSpec
    );

    //Загрузчик карточки выбранного технического объекта
    const { techObjCard } = useEqConfigTechObjCard(techObj.id, refresh.techObjCard);

    //Загрузчик моделей выбранного технического объекта
    const { techObjModelsList } = useTechObjModelsList(techObj.id);

    //Загрузчик истории прогнозов выбранного технического объекта
    const { techObjForecastHistList } = useTechObjForecastHistList(techObj.id, refresh.techObjForecastHistList);

    //Принудительное обновление текущей спецификации технических обхектов
    const refreshTechObjSpec = () => {
        if (techObjSpec.pageNumber == 1) setRefresh(pv => ({ ...pv, techObjSpec: pv.techObjSpec + 1 }));
        else setTechObjSpec(pv => ({ ...pv, pageNumber: 1 }));
    };

    //Обработка развёртывания/свёртывания уровня дерева
    const handleTreeItemExpansionToggle = (event, itemId, isExpanded) =>
        isExpanded && needLoadLevel(eQconfigTree, itemId) ? setLoadingTreeItem(parseInt(itemId)) : null;

    //Обработка фокусировки на элементе дерева
    const handleTreeItemFocus = (event, itemId) => setTechObjSpec({ ...TECH_OBJ_SPEC_INITIAL, parent: parseInt(itemId) });

    //При изменении состояния фильтров спецификации технических объектов
    const handleTechObjSpecFilterChanged = ({ filters }) => setTechObjSpec(pv => ({ ...pv, filters: [...filters], pageNumber: 1 }));

    //При изменении состояния сортировки спецификации технических объектов
    const handleTechObjSpecOrderChanged = ({ orders }) => setTechObjSpec(pv => ({ ...pv, orders: [...orders], pageNumber: 1 }));

    //При изменении количества отображаемых страниц спецификации технических объектов
    const handleTechObjSpecPagesCountChanged = () => setTechObjSpec(pv => ({ ...pv, pageNumber: pv.pageNumber + 1 }));

    //При выполнении действия в колоке "Модели" таблицы технических объектов
    const handleCMMLStatusClick = (event, row) => {
        if (row.NCMML_STATUS == 0) {
            setTechObj(pv => ({ ...pv, objKind: String(row.SOBJ_KIND_FULL_CODE), measureUnit: String(row.SUMEAS_RES) }));
            setDialogs(pv => ({ ...pv, makeEqDataSet: true }));
        } else {
            setTechObj(pv => ({ ...pv, id: parseInt(row.NRN) }));
            setDialogs(pv => ({ ...pv, techObjCard: true }));
        }
    };

    //При переходе к закладке администрирования из карточки технического объекта
    const handleTechObjCardGoToModel = model => {
        handleTechObjCardClose();
        if (onGoToAdmin) onGoToAdmin(model.NEQUIPDS, model.NEQUIPDSCM);
    };

    //При запросе прогноза по модели из карточки технического объекта
    const handleTechObjCardGetPrediction = async model => {
        //Подготовим данные о наработках
        pOnlineUserProcedure({
            code: model.SUSERPROCS_FRCST_DATA,
            inputParameters: [{ name: "NEQCONFIG", value: techObj.id }],
            callBack: async res => {
                //Данные подготовлены успешно
                if (res.success) {
                    //Проверим наличие необходимых выходных параметров
                    try {
                        if (!res.outParameters.NDATASET_IDENT)
                            throw new Error(
                                'Процедура подготовки данных о наработках не вернула идентификатор буфера набора данных (проверьте наличие и значение выходного параметра "NDATASET_IDENT").'
                            );
                        if (!res.outParameters.NDATASET_CONFIG_IDENT)
                            throw new Error(
                                'Процедура подготовки данных о наработках не вернула идентификатор буфера описания набора данных (проверьте наличие и значение выходного параметра "NDATASET_CONFIG_IDENT").'
                            );
                        if (!res.outParameters.NREQUEST_CONFIG_IDENT)
                            throw new Error(
                                'Процедура подготовки данных о наработках не вернула идентификатор буфера параметров запроса на прогнозирование (проверьте наличие и значение выходного параметра "NREQUEST_CONFIG_IDENT").'
                            );
                    } catch (e) {
                        showMsgErr(e.message);
                        return;
                    }
                    //Подготовим запрос к фреймворку прогнозирования
                    const forecastReqData = await executeStored({
                        stored: "UDO_PKG_EQUIPTCF.EQCONFIG_THOBJ_FORECAST_PRLG",
                        args: {
                            NEQUIPDSCMML: model.NRN,
                            NDATASET_IDENT: res.outParameters.NDATASET_IDENT,
                            NDATASET_CONFIG_IDENT: res.outParameters.NDATASET_CONFIG_IDENT,
                            NREQUEST_CONFIG_IDENT: res.outParameters.NREQUEST_CONFIG_IDENT
                        },
                        tagValueProcessor: (name, value) => (name === "VALUE" ? undefined : value)
                    });
                    forecastReqData.CDATASET_CONFIG = (await xml2JSON({ xmlDoc: forecastReqData.CDATASET_CONFIG })).data_config.item.map(itm => ({
                        col_name: itm.col_code,
                        data_type: itm.data_type
                    }));
                    //Выполним запрос прогноза
                    try {
                        const body = {
                            dataset_ID: forecastReqData.SDATASET_ID,
                            data_config: forecastReqData.CDATASET_CONFIG,
                            data_url: forecastReqData.SDATASET_URL,
                            class_machine: forecastReqData.SCLASS_MACHINE,
                            task: forecastReqData.STASK,
                            precision: 100
                        };
                        //Выполняем запрос
                        showLoader("Прогнозирование...");
                        let response = null;
                        try {
                            response = await fetch(forecastReqData.SURL, {
                                method: "POST",
                                body: JSON.stringify(body),
                                headers: {
                                    Authorization: `Basic ${forecastReqData.SAUTH_TOKEN}`,
                                    Accept: "*/*",
                                    "Content-Type": "application/json"
                                }
                            });
                        } catch (e) {
                            throw new Error("Сетевая ошибка или ошибка внешнего сервера.");
                        }
                        //Обработаем результаты
                        if (response) {
                            if (response?.status === 200) {
                                let responseJson;
                                try {
                                    responseJson = await response.json();
                                } catch (e) {
                                    throw new Error("Неожиданный ответ системы прогнозирования: ответ неверного формата.");
                                }
                                if (responseJson) {
                                    if (responseJson?.status && responseJson.status == "ERR")
                                        throw new Error(`Ошибка формирования прогноза: ${responseJson.message}`);
                                    if (responseJson?.forecast && Array.isArray(responseJson?.forecast)) {
                                        await executeStored({
                                            stored: "UDO_PKG_EQUIPTCF.EQCONFIG_THOBJ_FORECAST_EPLG",
                                            args: {
                                                NDATASET_IDENT: res.outParameters.NDATASET_IDENT,
                                                NDATASET_CONFIG_IDENT: res.outParameters.NDATASET_CONFIG_IDENT,
                                                NREQUEST_CONFIG_IDENT: res.outParameters.NREQUEST_CONFIG_IDENT,
                                                NEQCONFIG: techObj.id,
                                                NEQUIPDSCMML: model.NRN,
                                                CFORECAST: {
                                                    VALUE: object2Base64XML(
                                                        { XDATA: { XFORECAST: responseJson.forecast.map(f => ({ STIME: f.time, VALUE: f.value })) } },
                                                        { arrayNodeName: "XFORECAST" }
                                                    ),
                                                    SDATA_TYPE: SERV_DATA_TYPE_CLOB
                                                }
                                            },
                                            loader: false
                                        });
                                        hideLoader();
                                        setRefresh(pv => ({ ...pv, techObjForecastHistList: pv.techObjForecastHistList + 1 }));
                                    } else throw new Error("Неожиданный ответ системы прогнозирования: ответ не содержит данных прогноза.");
                                } else throw new Error("Неожиданный ответ системы прогнозирования: ответ не содержит данных.");
                            } else throw new Error(`Ошибка (${response.status}) при взаимодейтсвии с системой прогнозирования.`);
                        } else throw new Error("Система прогнозирования не вернула ответ.");
                    } catch (e) {
                        hideLoader();
                        showMsgErr(e.message);
                        executeStored({
                            stored: "UDO_PKG_EQUIPTCF.EQCONFIG_THOBJ_FORECAST_BCLR",
                            args: {
                                NDATASET_IDENT: res.outParameters.NDATASET_IDENT,
                                NDATASET_CONFIG_IDENT: res.outParameters.NDATASET_CONFIG_IDENT,
                                NREQUEST_CONFIG_IDENT: res.outParameters.NREQUEST_CONFIG_IDENT
                            },
                            loader: false,
                            showErrorMessage: false,
                            throwError: false
                        });
                    }
                }
            }
        });
    };

    //При закрытии карточки технического объекта
    const handleTechObjCardClose = () => {
        refreshTechObjSpec();
        setDialogs(pv => ({ ...pv, techObjCard: false }));
        setTechObj(TECH_OBJ_INITIAL);
    };

    //При нажатии на "Изменить график ТО" в карточке технического объекта
    const handleTechObjCardChangeEqTchSrv = () => setDialogs(pv => ({ ...pv, changeEqTchSrv: true }));

    //При нажатии на "Ремонтировать" в карточке технического объекта
    const handleTechObjCardMakeEqRpSheet = () => setDialogs(pv => ({ ...pv, makeEqRpSheet: true }));

    //При необходимости обновления карточки технического объекта
    const handleCardDataRefresh = () =>
        setRefresh(pv => ({ ...pv, techObjCard: pv.techObjCard + 1, techObjForecastHistList: pv.techObjForecastHistList + 1 }));

    //При нажатии "ОК" в диалоге формирования ремонтной ведомости
    const handleTechObjMakeEqRpSheetOk = async values => {
        const data = await executeStored({
            stored: "UDO_PKG_EQUIPTCF.EQCONFIG_THOBJ_MAKE_EQRPSHEET",
            args: {
                NEQCONFIG: techObj.id,
                SACATALOG: values.acatalog,
                SEQTECSRVKIND: values.eQTecSrvKind,
                DPLANDATE_FROM: new Date(values.planDateFrom)
            }
        });
        setRefresh(pv => ({ ...pv, techObjForecastHistList: pv.techObjForecastHistList + 1, techObjCard: pv.techObjCard + 1 }));
        setDialogs(pv => ({ ...pv, makeEqRpSheet: false }));
        pOnlineShowDictionary({
            unitCode: "EquipRepairSheets",
            showMethod: "main_selected",
            inputParameters: [{ name: "in_Ident", value: data.NIDENT }],
            callBack: handleCardDataRefresh
        });
    };

    //При нажитии "Отмена" в диалоге формирования ремонтной ведомости
    const handleTechObjMakeEqRpSheetCancel = () => setDialogs(pv => ({ ...pv, makeEqRpSheet: false }));

    //При нажатии "ОК" в диалоге корректировки графика ТО и ремонтов
    const handleTechObjChangeEqTchSrvOk = async values => {
        await executeStored({
            stored: "UDO_PKG_EQUIPTCF.EQCONFIG_THOBJ_CHNG_EQTCHSRV",
            args: {
                NEQCONFIG: techObj.id,
                NEQTCHSRV: values.eQTchSrv,
                DPLANDATE_FROM: new Date(values.planDateFrom)
            }
        });
        setRefresh(pv => ({ ...pv, techObjForecastHistList: pv.techObjForecastHistList + 1, techObjCard: pv.techObjCard + 1 }));
        setDialogs(pv => ({ ...pv, changeEqTchSrv: false }));
        if (values?.showEqTchSrv == 1) {
            const data = await executeStored({ stored: "UDO_PKG_EQUIPTCF.EQCONFIG_THOBJ_SELECT_EQTCHSRV", args: { NEQTCHSRV: values.eQTchSrv } });
            if (data.NIDENT)
                pOnlineShowUnit({
                    unitCode: "EquipTechServices",
                    inputParameters: [{ name: "in_Ident", value: data.NIDENT }]
                });
        }
    };

    //При нажитии "Отмена" в диалоге корректировки графика ТО и ремонтов
    const handleTechObjChangeEqTchSrvCancel = () => setDialogs(pv => ({ ...pv, changeEqTchSrv: false }));

    //При нажатии "ОК" в диалоге формирования выборки данных
    const handleTechObjMakeDataSetOk = async values => {
        const data = await executeStored({
            stored: "UDO_PKG_EQUIPDS.MAKE",
            args: {
                SEQUIPDS: values.dataSet,
                SEQOBJKIND: values.eqobjKind,
                SDICMUNTS: values.measureUnit,
                SUSERPROCS_DATA: values.userprocsData,
                SEXSSERVICE_UPLOAD: values.exsServiceUpload,
                SEXSSERVICEFN_UPLOAD: values.exsServiceFnUpload,
                SEXSSERVICE_SEND_MD: values.exsServiceSendMd,
                SEXSSERVICEFN_SEND_MD: values.exsServiceFnSendMd,
                SEXSSERVICE_SEND_MDCB: values.exsServiceSendMdCb,
                SEXSSERVICEFN_SEND_MDCB: values.exsServiceFnSendMdCb,
                SEXSSERVICE_SEND_RQ: values.exsServiceSendRq,
                SEXSSERVICEFN_SEND_RQ: values.exsServiceFnSendRq,
                SEXSSERVICE_SEND_RQCB: values.exsServiceSendRqCb,
                SEXSSERVICEFN_SEND_RQCB: values.exsServiceFnSendRqCb,
                SEXSSERVICE_FRCST_MD: values.exsServiceForecastMd,
                SEXSSERVICEFN_FRCST_MD: values.exsServiceFnForecastMd
            }
        });
        setDialogs(pv => ({ ...pv, makeEqDataSet: false }));
        if (onGoToAdmin) onGoToAdmin(data.NEQUIPDS, data.NEQUIPDSCM);
        refreshTechObjSpec();
    };

    //При нажитии "Отмена" в диалоге формирования выборки данных
    const handleTechObjMakeDataSetCancel = () => setDialogs(pv => ({ ...pv, makeEqDataSet: false }));

    //Текст при отсутствии данных в списке технических объектов
    const noDataFoundText =
        techObjsDataGridIsLoading || !techObjsDataGrid.init
            ? "Минуточку..."
            : techObjSpec.filters.length > 0
            ? "Нет данных, соответствующих условиям отбора"
            : "Выбранный уровень структуры не содержит данных о технических объектах";

    //Генерация содержимого
    return (
        <div>
            {dialogs.techObjCard ? (
                techObj.id && techObjCard?.SCODE ? (
                    <TechObjCard
                        cardData={techObjCard}
                        modelsList={techObjModelsList}
                        forecastHistList={techObjForecastHistList}
                        onClose={handleTechObjCardClose}
                        onGoToModel={handleTechObjCardGoToModel}
                        onGetPrediction={handleTechObjCardGetPrediction}
                        onChangeEqTchSrv={handleTechObjCardChangeEqTchSrv}
                        onMakeEqRpSheet={handleTechObjCardMakeEqRpSheet}
                        onCardDataRefresh={handleCardDataRefresh}
                    />
                ) : null
            ) : null}
            {dialogs.makeEqRpSheet ? <TechObjMakeEqRpSheet onCancel={handleTechObjMakeEqRpSheetCancel} onOk={handleTechObjMakeEqRpSheetOk} /> : null}
            {dialogs.makeEqDataSet ? (
                <TechObjMakeDataSet
                    eqobjKind={techObj.objKind}
                    measureUnit={techObj.measureUnit}
                    onCancel={handleTechObjMakeDataSetCancel}
                    onOk={handleTechObjMakeDataSetOk}
                />
            ) : null}
            {dialogs.changeEqTchSrv ? (
                <TechObjChangeEqTchSrv eQConfig={techObj.id} onCancel={handleTechObjChangeEqTchSrvCancel} onOk={handleTechObjChangeEqTchSrvOk} />
            ) : null}
            <Grid container>
                <Grid item xs={3} sx={STYLES.LEFT_SIDE_GRID}>
                    <Box sx={STYLES.TREE_BOX}>
                        <RichTreeView items={eQconfigTree} onItemExpansionToggle={handleTreeItemExpansionToggle} onItemFocus={handleTreeItemFocus} />
                    </Box>
                </Grid>
                <Grid item xs={9} sx={STYLES.RIGHT_SIDE_GRID}>
                    {techObjsDataGrid.init ? (
                        <P8PDataGrid
                            {...{ ...P8P_DATA_GRID_CONFIG_PROPS, noDataFoundText }}
                            containerComponentProps={{
                                sx: STYLES.TECH_OBJ_TABLE(techObjsDataGrid.morePages, techObjSpec.filters.length > 0),
                                elevation: 0
                            }}
                            filtersInitial={techObjSpec.filters}
                            columnsDef={techObjsDataGrid.columnsDef}
                            rows={techObjsDataGrid.rows}
                            size={P8P_DATA_GRID_SIZE.SMALL}
                            morePages={techObjsDataGrid.morePages}
                            fixedHeader={true}
                            reloading={false}
                            valueFormatter={eqConfigTechObjTableValueFormatter}
                            dataCellRender={prms => eqConfigTechObjTableDataCellRender({ ...prms, onCMMLStatus: handleCMMLStatusClick })}
                            onFilterChanged={handleTechObjSpecFilterChanged}
                            onOrderChanged={handleTechObjSpecOrderChanged}
                            onPagesCountChanged={handleTechObjSpecPagesCountChanged}
                        />
                    ) : (
                        <InlineMsgInfo okBtn={false} text={"Укажите элемент структуры состава оборудования для отображения данных"} />
                    )}
                </Grid>
            </Grid>
        </div>
    );
};

//Контроль свойств - Закладка прогнозирования
ForecastTab.propTypes = {
    onGoToAdmin: PropTypes.func
};

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

export { ForecastTab };
