/*
    Парус 8 - Панели мониторинга
    Компонент: Диаграмма Ганта
*/

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

import React, { useEffect, useState, useCallback, useRef } from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента
import {
    Box,
    IconButton,
    Icon,
    Typography,
    Dialog,
    DialogActions,
    DialogContent,
    TextField,
    Button,
    List,
    ListItem,
    ListItemText,
    Divider,
    Slider,
    Link
} from "@mui/material"; //Интерфейсные компоненты
import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке
import { useP8PGantt } from "./p8p_gantt_hooks"; //Хук для диаграммы Ганта

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

//Уровни масштаба
const P8P_GANTT_ZOOM = [0, 1, 2, 3, 4, 5];

//Уровни масштаба (строковые наименования в терминах библиотеки)
const P8P_GANTT_ZOOM_VIEW_MODES = {
    0: "Quarter Day",
    1: "Half Day",
    2: "Day",
    3: "Week",
    4: "Month",
    5: "Year"
};

//Структура задачи
const P8P_GANTT_TASK_SHAPE = PropTypes.shape({
    id: PropTypes.string.isRequired,
    rn: PropTypes.number.isRequired,
    numb: PropTypes.string.isRequired,
    name: PropTypes.string.isRequired,
    fullName: PropTypes.string.isRequired,
    start: PropTypes.string.isRequired,
    end: PropTypes.string.isRequired,
    progress: PropTypes.number,
    dependencies: PropTypes.array,
    readOnly: PropTypes.bool,
    readOnlyDates: PropTypes.bool,
    readOnlyProgress: PropTypes.bool,
    bgColor: PropTypes.string,
    textColor: PropTypes.string,
    bgProgressColor: PropTypes.string
});

//Структура динамического атрибута задачи
const P8P_GANTT_TASK_ATTRIBUTE_SHAPE = PropTypes.shape({
    name: PropTypes.string.isRequired,
    caption: PropTypes.string.isRequired,
    visible: PropTypes.bool.isRequired
});

//Структура описания цвета задачи
const P8P_GANTT_TASK_COLOR_SHAPE = PropTypes.shape({
    bgColor: PropTypes.string,
    textColor: PropTypes.string,
    bgProgressColor: PropTypes.string,
    desc: PropTypes.string.isRequired
});

//Высота заголовка
const TITLE_HEIGHT = "44px";

//Высота панели масштабирования
const ZOOM_HEIGHT = "56px";

//Стили
const STYLES = {
    TASK_EDITOR_CONTENT: { minWidth: 400, overflowX: "auto" },
    TASK_EDITOR_LIST: { width: "100%", minWidth: 300, maxWidth: 700, bgcolor: "background.paper" },
    GANTT_TITLE: { height: TITLE_HEIGHT },
    GANTT_ZOOM: { height: ZOOM_HEIGHT },
    GANTT: (noData, title, zoomBar) => ({
        height: `calc(100% - ${zoomBar ? ZOOM_HEIGHT : "0px"} - ${title ? TITLE_HEIGHT : "0px"})`,
        display: noData ? "none" : ""
    })
};

//--------------------------------
//Вспомогательные классы и функции
//--------------------------------

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

//Формирование описания для легенды
const taskLegendDesc = ({ task, taskColors }) => {
    if (Array.isArray(taskColors) && taskColors.length > 0) {
        const colorDesc = taskColors.find(
            color => task.bgColor === color.bgColor && task.textColor === color.textColor && task.bgProgressColor === color.bgProgressColor
        );
        if (colorDesc)
            return {
                text: colorDesc.desc,
                style: {
                    ...(colorDesc.bgProgressColor
                        ? {
                              background: `linear-gradient(to right, ${colorDesc.bgProgressColor} ,${
                                  colorDesc.bgColor ? colorDesc.bgColor : "transparent"
                              })`
                          }
                        : colorDesc.bgColor
                        ? { backgroundColor: colorDesc.bgColor }
                        : {}),
                    ...(colorDesc.textColor ? { color: colorDesc.textColor } : {})
                }
            };
        else return null;
    } else return null;
};

//Редактор задачи
const P8PGanttTaskEditor = ({
    task,
    taskAttributes,
    taskColors,
    onOk,
    onCancel,
    taskAttributeRenderer,
    taskDialogRenderer,
    taskDialogProps,
    numbCaption,
    nameCaption,
    startCaption,
    endCaption,
    progressCaption,
    legendCaption,
    okBtnCaption,
    cancelBtnCaption
}) => {
    //Собственное состояние
    const [state, setState] = useState({
        start: task.start,
        end: task.end,
        progress: task.progress
    });

    //Отображаемые атрибуты
    const dispTaskAttributes =
        Array.isArray(taskAttributes) && taskAttributes.length > 0 ? taskAttributes.filter(attr => attr.visible && hasValue(task[attr.name])) : [];

    //При сохранении
    const handleOk = () => (onOk && state.start && state.end ? onOk({ task, start: state.start, end: state.end, progress: state.progress }) : null);

    //При отмене
    const handleCancel = () => (onCancel ? onCancel() : null);

    //При изменении сроков
    const handlePeriodChanged = e => setState(prev => ({ ...prev, [e.target.name]: e.target.value }));

    //При изменении прогресса
    const handleProgressChanged = (e, newValue) => setState(prev => ({ ...prev, progress: newValue }));

    //Описание легенды для задачи
    const legendDesc = taskLegendDesc({ task, taskColors });
    let legend = legendDesc ? (
        <ListItemText
            secondaryTypographyProps={{
                p: 1,
                sx: legendDesc.style
            }}
            primary={legendCaption}
            secondary={legendDesc.text}
        />
    ) : null;

    //Генерация содержимого
    return (
        <Dialog open onClose={handleCancel} {...(taskDialogProps ? taskDialogProps : {})}>
            {taskDialogRenderer ? (
                taskDialogRenderer({ task, taskAttributes, taskColors, close: handleCancel })
            ) : (
                <>
                    <DialogContent sx={STYLES.TASK_EDITOR_CONTENT}>
                        <List sx={STYLES.TASK_EDITOR_LIST}>
                            <ListItem alignItems="flex-start">
                                <ListItemText primary={numbCaption} secondary={task.numb} />
                            </ListItem>
                            <Divider component="li" />
                            <ListItem alignItems="flex-start">
                                <ListItemText primary={nameCaption} secondary={task.fullName} />
                            </ListItem>
                            <Divider component="li" />
                            <ListItem alignItems="flex-start">
                                <ListItemText
                                    secondaryTypographyProps={{ component: "span" }}
                                    primary={startCaption}
                                    secondary={
                                        <TextField
                                            error={!state.start}
                                            disabled={task.readOnly === true || task.readOnlyDates === true}
                                            name="start"
                                            fullWidth
                                            required
                                            InputLabelProps={{ shrink: true }}
                                            type={"date"}
                                            value={state.start}
                                            onChange={handlePeriodChanged}
                                            variant="standard"
                                            size="small"
                                            margin="normal"
                                        />
                                    }
                                />
                            </ListItem>
                            <Divider component="li" />
                            <ListItem alignItems="flex-start">
                                <ListItemText
                                    secondaryTypographyProps={{ component: "span" }}
                                    primary={endCaption}
                                    secondary={
                                        <TextField
                                            error={!state.end}
                                            disabled={task.readOnly === true || task.readOnlyDates === true}
                                            name="end"
                                            fullWidth
                                            required
                                            InputLabelProps={{ shrink: true }}
                                            type={"date"}
                                            value={state.end}
                                            onChange={handlePeriodChanged}
                                            variant="standard"
                                            size="small"
                                            margin="normal"
                                        />
                                    }
                                />
                            </ListItem>
                            {hasValue(task.progress) || legend || dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
                            {hasValue(task.progress) ? (
                                <>
                                    <ListItem alignItems="flex-start">
                                        <ListItemText
                                            secondaryTypographyProps={{ component: "span" }}
                                            primary={`${progressCaption}${
                                                task.readOnly === true || task.readOnlyProgress === true ? ` (${task.progress}%)` : ""
                                            }`}
                                            secondary={
                                                <Slider
                                                    disabled={task.readOnly === true || task.readOnlyProgress === true}
                                                    defaultValue={task.progress}
                                                    valueLabelDisplay="auto"
                                                    onChange={handleProgressChanged}
                                                />
                                            }
                                        />
                                    </ListItem>
                                    {legend || dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
                                </>
                            ) : null}
                            {legend ? (
                                <>
                                    <ListItem alignItems="flex-start">{legend}</ListItem>
                                    {dispTaskAttributes.length > 0 ? <Divider component="li" /> : null}
                                </>
                            ) : null}
                            {dispTaskAttributes.length > 0
                                ? dispTaskAttributes.map((attr, i) => {
                                      const defaultView = task[attr.name];
                                      const customView = taskAttributeRenderer ? taskAttributeRenderer({ task, attribute: attr }) : null;
                                      return (
                                          <React.Fragment key={i}>
                                              <ListItem alignItems="flex-start">
                                                  <ListItemText
                                                      primary={attr.caption}
                                                      secondaryTypographyProps={{ component: "span" }}
                                                      secondary={customView ? customView : defaultView}
                                                  />
                                              </ListItem>
                                              {i < dispTaskAttributes.length - 1 ? <Divider component="li" /> : null}
                                          </React.Fragment>
                                      );
                                  })
                                : null}
                        </List>
                    </DialogContent>
                    <DialogActions>
                        <Button disabled={!state.start || !state.end || task.readOnly} onClick={handleOk}>
                            {okBtnCaption}
                        </Button>
                        <Button onClick={handleCancel}>{cancelBtnCaption}</Button>
                    </DialogActions>
                </>
            )}
        </Dialog>
    );
};

//Контроль свойств - Редактор задачи
P8PGanttTaskEditor.propTypes = {
    task: P8P_GANTT_TASK_SHAPE,
    taskAttributes: PropTypes.arrayOf(P8P_GANTT_TASK_ATTRIBUTE_SHAPE),
    taskColors: PropTypes.arrayOf(P8P_GANTT_TASK_COLOR_SHAPE),
    onOk: PropTypes.func,
    onCancel: PropTypes.func,
    taskAttributeRenderer: PropTypes.func,
    taskDialogRenderer: PropTypes.func,
    taskDialogProps: PropTypes.object,
    numbCaption: PropTypes.string.isRequired,
    nameCaption: PropTypes.string.isRequired,
    startCaption: PropTypes.string.isRequired,
    endCaption: PropTypes.string.isRequired,
    progressCaption: PropTypes.string.isRequired,
    legendCaption: PropTypes.string.isRequired,
    okBtnCaption: PropTypes.string.isRequired,
    cancelBtnCaption: PropTypes.string.isRequired
};

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

//Диаграмма Ганта
const P8PGantt = ({
    containerStyle,
    title,
    titleStyle,
    onTitleClick,
    zoomBar,
    readOnly,
    readOnlyDates,
    readOnlyProgress,
    zoom,
    tasks,
    taskAttributes,
    taskColors,
    onTaskDatesChange,
    onTaskProgressChange,
    taskAttributeRenderer,
    taskDialogRenderer,
    taskDialogProps,
    noDataFoundText,
    numbTaskEditorCaption,
    nameTaskEditorCaption,
    startTaskEditorCaption,
    endTaskEditorCaption,
    progressTaskEditorCaption,
    legendTaskEditorCaption,
    okTaskEditorBtnCaption,
    cancelTaskEditorBtnCaption
}) => {
    //Собственное состояние
    const [state, setState] = useState({
        noData: true,
        gantt: null,
        zoom: P8P_GANTT_ZOOM.includes(zoom) ? zoom : 3,
        editTask: null
    });

    //Ссылки на DOM
    const svgContainerRef = useRef(null);

    //Отображение диаграммы
    const showGantt = useCallback(() => {
        if (!state.gantt) {
            // eslint-disable-next-line no-undef
            const gantt = new Gantt("#__gantt__", tasks, {
                view_mode: P8P_GANTT_ZOOM_VIEW_MODES[state.zoom],
                date_format: "YYYY-MM-DD",
                language: "ru",
                readOnly,
                readOnlyDates,
                readOnlyProgress,
                on_date_change: (task, start, end, isMain) => (onTaskDatesChange ? onTaskDatesChange({ task, start, end, isMain }) : null),
                on_progress_change: (task, progress) => (onTaskProgressChange ? onTaskProgressChange({ task, progress }) : null),
                on_click: openTaskEditor
            });
            setState(pv => ({ ...pv, gantt, noData: false }));
        } else {
            state.gantt.refresh(tasks);
            setState(pv => ({ ...pv, noData: false }));
        }
    }, [state.gantt, state.zoom, readOnly, readOnlyDates, readOnlyProgress, tasks, onTaskDatesChange, onTaskProgressChange]);

    //Обновление масштаба диаграммы
    const handleZoomChange = direction =>
        setState(pv => ({
            ...pv,
            zoom: pv.zoom + direction < 0 ? 0 : pv.zoom + direction >= P8P_GANTT_ZOOM.length ? P8P_GANTT_ZOOM.length - 1 : pv.zoom + direction
        }));

    //Открытие редактора задачи
    const openTaskEditor = task => setState(pv => ({ ...pv, editTask: { ...task } }));

    //При сохранении задачи в редакторе
    const handleTaskEditorSave = ({ task, start, end, progress }) => {
        setState(pv => ({ ...pv, editTask: null }));
        if (onTaskDatesChange && (task.start != start || task.end != end)) onTaskDatesChange({ task, start, end, isMain: true });
        if (onTaskProgressChange && task.progress != progress) onTaskProgressChange({ task, progress });
    };

    //При закрытии редактора задачи без сохранения
    const handleTaskEditorCancel = () => setState(pv => ({ ...pv, editTask: null }));

    //При изменении масштаба
    useEffect(() => {
        if (state.gantt) state.gantt.change_view_mode(P8P_GANTT_ZOOM_VIEW_MODES[state.zoom]);
    }, [state.gantt, state.zoom]);

    //При изменении списка задач
    useEffect(() => {
        if (Array.isArray(tasks) && tasks.length > 0) showGantt();
        else setState(pv => ({ ...pv, noData: true }));
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [tasks]);

    //При подключении компонента к старице
    useEffect(() => {
        svgContainerRef.current.children[0].classList.add("scroll");
    }, []);

    //Генерация содержимого
    return (
        <div style={{ ...(containerStyle ? containerStyle : {}) }}>
            {state.gantt && state.noData ? <P8PAppInlineError text={noDataFoundText} /> : null}
            {state.gantt && !state.noData && title ? (
                <Typography
                    p={1}
                    sx={{ ...STYLES.GANTT_TITLE, ...(titleStyle ? titleStyle : {}) }}
                    align="center"
                    color="textSecondary"
                    variant="subtitle1"
                >
                    {onTitleClick ? (
                        <Link component="button" variant="body2" underline="hover" onClick={() => onTitleClick()}>
                            {title}
                        </Link>
                    ) : (
                        title
                    )}
                </Typography>
            ) : null}
            {state.gantt && !state.noData && zoomBar ? (
                <Box p={1} sx={STYLES.GANTT_ZOOM}>
                    <IconButton onClick={() => handleZoomChange(-1)} disabled={state.zoom == 0}>
                        <Icon>zoom_in</Icon>
                    </IconButton>
                    <IconButton onClick={() => handleZoomChange(1)} disabled={state.zoom == P8P_GANTT_ZOOM.length - 1}>
                        <Icon>zoom_out</Icon>
                    </IconButton>
                </Box>
            ) : null}
            {state.editTask ? (
                <P8PGanttTaskEditor
                    task={state.editTask}
                    taskAttributes={taskAttributes}
                    taskColors={taskColors}
                    onOk={handleTaskEditorSave}
                    onCancel={handleTaskEditorCancel}
                    taskAttributeRenderer={taskAttributeRenderer}
                    taskDialogRenderer={taskDialogRenderer}
                    taskDialogProps={taskDialogProps}
                    numbCaption={numbTaskEditorCaption}
                    nameCaption={nameTaskEditorCaption}
                    startCaption={startTaskEditorCaption}
                    endCaption={endTaskEditorCaption}
                    progressCaption={progressTaskEditorCaption}
                    legendCaption={legendTaskEditorCaption}
                    okBtnCaption={okTaskEditorBtnCaption}
                    cancelBtnCaption={cancelTaskEditorBtnCaption}
                />
            ) : null}
            <div style={STYLES.GANTT(state.noData, title, zoomBar)} ref={svgContainerRef}>
                <svg id="__gantt__" width="100%"></svg>
            </div>
        </div>
    );
};

//Контроль свойств - Диаграмма Ганта
P8PGantt.propTypes = {
    containerStyle: PropTypes.object,
    title: PropTypes.string,
    titleStyle: PropTypes.object,
    onTitleClick: PropTypes.func,
    zoomBar: PropTypes.bool,
    readOnly: PropTypes.bool,
    readOnlyDates: PropTypes.bool,
    readOnlyProgress: PropTypes.bool,
    zoom: PropTypes.number,
    tasks: PropTypes.arrayOf(P8P_GANTT_TASK_SHAPE).isRequired,
    taskAttributes: PropTypes.arrayOf(P8P_GANTT_TASK_ATTRIBUTE_SHAPE),
    taskColors: PropTypes.arrayOf(P8P_GANTT_TASK_COLOR_SHAPE),
    onTaskDatesChange: PropTypes.func,
    onTaskProgressChange: PropTypes.func,
    taskAttributeRenderer: PropTypes.func,
    taskDialogRenderer: PropTypes.func,
    taskDialogProps: PropTypes.object,
    noDataFoundText: PropTypes.string.isRequired,
    numbTaskEditorCaption: PropTypes.string.isRequired,
    nameTaskEditorCaption: PropTypes.string.isRequired,
    startTaskEditorCaption: PropTypes.string.isRequired,
    endTaskEditorCaption: PropTypes.string.isRequired,
    progressTaskEditorCaption: PropTypes.string.isRequired,
    legendTaskEditorCaption: PropTypes.string.isRequired,
    okTaskEditorBtnCaption: PropTypes.string.isRequired,
    cancelTaskEditorBtnCaption: PropTypes.string.isRequired
};

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

export { P8P_GANTT_TASK_SHAPE, P8P_GANTT_TASK_ATTRIBUTE_SHAPE, P8P_GANTT_TASK_COLOR_SHAPE, taskLegendDesc, P8PGantt, useP8PGantt };
