import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import * as React from 'react';
import classNames from 'classnames';
import * as Actions from './actions';
import * as Matrix from './matrix';
import reducer, { INITIAL_STATE } from './reducer';
import context from './context';
import { Model, createFormulaParser } from './engine';
import { range, readTextFromClipboard, writeTextToClipboard, calculateSpreadsheetSize, getCSV, shouldHandleClipboardEvent, isFocusedWithin, } from './util';
import DefaultTable from './Table';
import DefaultRow from './Row';
import DefaultHeaderRow from './HeaderRow';
import DefaultCornerIndicator, { enhance as enhanceCornerIndicator, } from './CornerIndicator';
import DefaultColumnIndicator, { enhance as enhanceColumnIndicator, } from './ColumnIndicator';
import DefaultRowIndicator, { enhance as enhanceRowIndicator, } from './RowIndicator';
import { Cell as DefaultCell, enhance as enhanceCell } from './Cell';
import DefaultDataViewer from './DataViewer';
import DefaultDataEditor from './DataEditor';
import ActiveCell from './ActiveCell';
import Selected from './Selected';
import Copied from './Copied';
import './Spreadsheet.scss';
import ExcelToolBar from '../../ExcelToolBar/ExcelToolBar';
import { hasKeyDownHandler } from './reducerFunctions';
import { throttle } from '../../../../utils/throttle/throttle';
/**
 * The Spreadsheet component
 */
const Spreadsheet = (props) => {
    const { className, columnLabels, rowLabels, toolbar, editable, onKeyDown, Table = DefaultTable, Row = DefaultRow, sheetHeight, HeaderRow = DefaultHeaderRow, DataEditor = DefaultDataEditor, DataViewer = DefaultDataViewer, onSelect = () => { }, onActivate = () => { }, onEvaluatedDataChange = () => { }, contextOption, attachmentAction, workRef, minimumColumnWidth, setContextMenu, showHider, maxRowLimit, maxColLimit, } = props;
    const [sheetChange, setSheetChange] = React.useState(false);
    const [maxWidth, setMaxWidth] = React.useState(0);
    const [maxHeight, setMaxHeight] = React.useState(0);
    const [renderReady, setRenderReady] = React.useState(true);
    const resizeObserver = new ResizeObserver((entries) => {
        const entry = entries[0];
        if (entry && rootRef.current) {
            const target = entry.target;
            if (target) {
                const { clientWidth, clientHeight } = target;
                setMaxHeight(Math.min(rootRef.current.clientHeight, clientHeight));
                if (showHider) {
                    setMaxWidth(Math.min(rootRef.current.clientWidth, clientWidth));
                }
            }
            removeSelect();
        }
    });
    const scrollOption = props.scroller;
    const initialState = React.useMemo(() => {
        const createParser = (props.createFormulaParser ||
            createFormulaParser);
        const model = new Model(createParser, props.data);
        setSheetChange((prev) => !prev);
        return {
            ...INITIAL_STATE,
            model,
            selected: props.selected || INITIAL_STATE.selected,
        };
    }, [props.createFormulaParser, props.data, props.selected]);
    const reducerElements = React.useReducer(reducer, initialState);
    const [state, dispatch] = reducerElements;
    const [visibleRange, setVisibleRange] = React.useState({
        start: 0,
        end: 100,
    });
    const [scrollCount, setScrollCount] = React.useState(0);
    const size = React.useMemo(() => {
        return calculateSpreadsheetSize(state.model.data, rowLabels, columnLabels);
    }, [state.model.data, rowLabels, columnLabels]);
    const mode = state.mode;
    const rootRef = React.useRef(null);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const useAction = (action) => {
        return React.useCallback((...args) => dispatch(action(...args)), [action]);
    };
    const setEditable = useAction(Actions.editable);
    const cut = useAction(Actions.cut);
    const copy = useAction(Actions.copy);
    const removeSelect = useAction(Actions.blur);
    const paste = useAction(Actions.paste);
    const onKeyDownAction = useAction(Actions.keyDown);
    const onKeyPress = useAction(Actions.keyPress);
    const onDragStart = useAction(Actions.dragStart);
    const onDragEnd = useAction(Actions.dragEnd);
    const setData = useAction(Actions.setData);
    const setCreateFormulaParser = useAction(Actions.setCreateFormulaParser);
    const setSelection = useAction(Actions.setSelection);
    const onBold = useAction(Actions.bold);
    const onItalic = useAction(Actions.italic);
    const setUnderlineType = useAction(Actions.underlineType);
    const setTextAlign = useAction(Actions.textAlign);
    const setFontSize = useAction(Actions.fontSize);
    const setFontFamily = useAction(Actions.fontFamily);
    const setBorderType = useAction(Actions.borderType);
    const setColor = useAction(Actions.color);
    const setBackgroundColor = useAction(Actions.backgroundStyle);
    const setFormatePainter = useAction(Actions.formatePainter);
    const formateClick = useAction(Actions.onMouseUp);
    const addRowTop = useAction(Actions.addRowTop);
    const addRowBottom = useAction(Actions.addRowBottom);
    const addColumnLeft = useAction(Actions.addColumnLeft);
    const addColumnRight = useAction(Actions.addColumnRight);
    const deleteRow = useAction(Actions.deleteRow);
    const deleteColumn = useAction(Actions.deleteColumn);
    // Track active
    const prevActiveRef = React.useRef(state.active);
    React.useEffect(() => {
        if (state.active !== prevActiveRef.current) {
            if (state.active) {
                onActivate(state.active);
            }
            else {
                const root = workRef.current;
                if (root && isFocusedWithin(root) && document.activeElement) {
                    document.activeElement.blur();
                }
            }
        }
        prevActiveRef.current = state.active;
    }, [onActivate, state.active]);
    const prevEvaluatedDataRef = React.useRef(state.model.evaluatedData);
    React.useEffect(() => {
        if (state?.model?.evaluatedData !== prevEvaluatedDataRef?.current) {
            onEvaluatedDataChange(state?.model?.evaluatedData);
        }
        prevEvaluatedDataRef.current = state.model.evaluatedData;
    }, [state?.model?.evaluatedData, onEvaluatedDataChange]);
    React.useEffect(() => {
        if (size.rows < 100) {
            setVisibleRange({
                start: 0,
                end: size.rows,
            });
        }
        else {
            setVisibleRange({
                start: 0,
                end: 100,
            });
        }
    }, [sheetChange, size.rows]);
    const prevSelectedRef = React.useRef(state.selected);
    React.useEffect(() => {
        if (!state.selected.equals(prevSelectedRef.current)) {
            if (!props.selected || !state.selected.equals(props.selected)) {
                onSelect(state.selected);
            }
        }
        prevSelectedRef.current = state.selected;
    }, [state.selected, onSelect, props.selected]);
    // Update selection when props.selected changes
    const prevSelectedPropRef = React.useRef(props.selected);
    React.useEffect(() => {
        if (props.selected &&
            prevSelectedPropRef.current &&
            !props.selected.equals(prevSelectedPropRef.current)) {
            setSelection(props.selected);
        }
        prevSelectedPropRef.current = props.selected;
    }, [props.selected, setSelection]);
    // Update data when props.data changes
    let prevDataPropRef = React.useRef(props.data);
    React.useEffect(() => {
        setEditable(editable);
        if (props.data !== prevDataPropRef.current) {
            setData(props.data);
        }
        prevDataPropRef.current = props.data;
    }, [props.data, setData]);
    const prevCreateFormulaParserPropRef = React.useRef(props.createFormulaParser);
    React.useEffect(() => {
        if (props.createFormulaParser !== prevCreateFormulaParserPropRef.current &&
            props.createFormulaParser)
            setCreateFormulaParser(props.createFormulaParser);
        prevCreateFormulaParserPropRef.current = props.createFormulaParser;
    }, [props.createFormulaParser, setCreateFormulaParser]);
    const writeDataToClipboard = React.useCallback((event) => {
        const { model, selected } = state;
        const { data } = model;
        const range = selected.toRange(data);
        if (range) {
            const selectedData = Matrix.slice(range.start, range.end, data);
            const csv = getCSV(selectedData);
            writeTextToClipboard(event, csv);
        }
    }, [state]);
    const handleCut = React.useCallback((event) => {
        if (shouldHandleClipboardEvent(rootRef.current, mode)) {
            event.preventDefault();
            event.stopPropagation();
            writeDataToClipboard(event);
            cut();
        }
    }, [mode, writeDataToClipboard, cut]);
    const handleCopy = React.useCallback((event) => {
        if (shouldHandleClipboardEvent(rootRef.current, mode)) {
            event.preventDefault();
            event.stopPropagation();
            writeDataToClipboard(event);
            copy();
        }
    }, [mode, writeDataToClipboard, copy]);
    const handlePaste = React.useCallback((event) => {
        if (shouldHandleClipboardEvent(rootRef.current, mode)) {
            event.preventDefault();
            event.stopPropagation();
            if (event.clipboardData) {
                const text = readTextFromClipboard(event);
                paste(text);
            }
        }
    }, [mode, paste]);
    const handleKeyDown = React.useCallback((event) => {
        event.persist();
        if (onKeyDown) {
            onKeyDown(event);
        }
        if (!event.defaultPrevented) {
            if (hasKeyDownHandler(state, event)) {
                event.nativeEvent.preventDefault();
            }
            onKeyDownAction(event);
        }
    }, [state, onKeyDown, onKeyDownAction]);
    React.useEffect(() => {
        const handleDocumentClick = (event) => {
            if (workRef.current && !workRef.current.contains(event.target)) {
                removeSelect();
            }
        };
        document.addEventListener('click', handleDocumentClick);
        return () => {
            document.removeEventListener('click', handleDocumentClick);
        };
    }, [workRef]);
    const handleClick = React.useCallback(() => {
        if (state.formattedStyle.open && state.active) {
            formateClick(state.active);
        }
    }, [state]);
    const handleMouseUp = React.useCallback(() => {
        onDragEnd();
        document.removeEventListener('mouseup', handleMouseUp);
    }, [onDragEnd]);
    const handleMouseMove = React.useCallback((event) => {
        if (!state.dragging && event.buttons === 1) {
            onDragStart();
            document.addEventListener('mouseup', handleMouseUp);
        }
    }, [state, onDragStart, handleMouseUp]);
    const Cell = React.useMemo(() => {
        // @ts-ignore
        return enhanceCell(props.Cell || DefaultCell);
    }, [props.Cell, sheetChange]);
    const CornerIndicator = React.useMemo(() => enhanceCornerIndicator(props.CornerIndicator || DefaultCornerIndicator), [props.CornerIndicator, sheetChange]);
    const RowIndicator = React.useMemo(() => enhanceRowIndicator(props.RowIndicator || DefaultRowIndicator), [props.RowIndicator, sheetChange]);
    const ColumnIndicator = React.useMemo(() => enhanceColumnIndicator(props.ColumnIndicator || DefaultColumnIndicator), [props.ColumnIndicator, sheetChange]);
    React.useEffect(() => {
        document.addEventListener('cut', handleCut);
        document.addEventListener('copy', handleCopy);
        document.addEventListener('paste', handlePaste);
        return () => {
            document.removeEventListener('cut', handleCut);
            document.removeEventListener('copy', handleCopy);
            document.removeEventListener('paste', handlePaste);
        };
    }, [handleCut, handleCopy, handlePaste]);
    const useTableRef = (ref) => {
        const table = ref.current;
        if (!table)
            return;
        resizeObserver.observe(table);
    };
    const removeSelection = () => {
        if ((state.selectedColumn === null || state.selectedRow === null) &&
            !state.autoFill.open) {
            removeSelect();
        }
    };
    const updateVisibleRange = (newStart, newEnd, newScrollCount) => {
        setScrollCount(newScrollCount);
        setVisibleRange({ start: newStart, end: newEnd });
        // Wait until next frame to scroll
        requestAnimationFrame(() => {
            rootRef.current?.scrollTo({
                top: 1300,
                behavior: 'instant',
            });
        });
        removeSelection();
    };
    const throttledHandleScroll = React.useMemo(() => throttle((e) => {
        setRenderReady(false);
        requestAnimationFrame(() => {
            const { scrollTop, scrollHeight, offsetHeight } = e.target;
            const visibleRows = 100;
            const rowHeight = 50;
            const totalScroll = Math.ceil((size.rows - visibleRows) / rowHeight);
            const dynamicScroll = scrollHeight - offsetHeight;
            if (size.rows < 100) {
                updateVisibleRange(0, size.rows, 0);
                setScrollCount(0);
            }
            else if (scrollTop === 0 && scrollCount !== 0) {
                const newScroll = scrollCount - 1;
                updateVisibleRange(newScroll * rowHeight, newScroll * rowHeight + visibleRows, newScroll);
                setScrollCount(newScroll);
            }
            else if (scrollTop > dynamicScroll - 30 &&
                scrollCount < totalScroll - 1) {
                const newScroll = scrollCount + 1;
                updateVisibleRange(newScroll * rowHeight, newScroll * rowHeight + visibleRows, newScroll);
                setScrollCount(newScroll);
            }
            else if (scrollTop > dynamicScroll - 30 &&
                scrollCount < totalScroll) {
                updateVisibleRange((scrollCount + 1) * rowHeight, size.rows, scrollCount + 1);
                setScrollCount(scrollCount + 1);
            }
            setRenderReady(true);
        });
        setContextMenu({
            open: false,
            options: [
                {
                    label: '',
                    value: '',
                    iconName: '',
                    action: () => { },
                    disableTooltip: '',
                    visible: false,
                    disable: false,
                },
            ],
        });
    }, 100), [size.rows, scrollCount]);
    const scrollerFunction = () => {
        if (scrollOption) {
            return range(visibleRange.end, visibleRange.start);
        }
        else {
            return range(size.rows);
        }
    };
    const tableNode = React.useMemo(() => (_jsxs(Table, { columns: size.columns, hideColumnIndicators: false, useTableRef: useTableRef, children: [_jsxs(HeaderRow, { children: [_jsx(CornerIndicator, {}), range(size.columns).map((columnNumber) => columnLabels ? (_jsx(ColumnIndicator, { column: columnNumber, minimumColumnWidth: minimumColumnWidth, setContextMenu: props.setContextMenu, label: columnNumber in columnLabels
                            ? columnLabels[columnNumber]
                            : null, deleteColumn: deleteColumn, addColumnLeft: addColumnLeft, addColumnRight: addColumnRight, columnContextEnable: props.columnContextEnable, maxColLimit: maxColLimit }, columnNumber)) : (_jsx(ColumnIndicator, { column: columnNumber, minimumColumnWidth: minimumColumnWidth, setContextMenu: props.setContextMenu, deleteColumn: deleteColumn, addColumnLeft: addColumnLeft, addColumnRight: addColumnRight, columnContextEnable: props.columnContextEnable, maxColLimit: maxColLimit }, columnNumber)))] }), renderReady &&
                scrollerFunction().map((rowNumber) => (_jsxs(Row, { row: rowNumber, children: [rowLabels ? (_jsx(RowIndicator, { label: rowNumber in rowLabels ? rowLabels[rowNumber] : null, row: rowNumber, addRowTop: addRowTop, addRowBottom: addRowBottom, deleteRow: deleteRow, setContextMenu: props.setContextMenu, rowContextEnable: props.rowContextEnable, maxRowLimit: maxRowLimit }, rowNumber)) : (_jsx(RowIndicator, { row: rowNumber, addRowTop: addRowTop, addRowBottom: addRowBottom, deleteRow: deleteRow, setContextMenu: props.setContextMenu, rowContextEnable: props.rowContextEnable, maxRowLimit: maxRowLimit }, rowNumber)), range(size.columns).map((columnNumber) => (_jsx(Cell, { row: rowNumber, column: columnNumber, 
                            // @ts-ignore
                            DataViewer: DataViewer }, columnNumber)))] }, rowNumber)))] })), [
        Table,
        size.rows,
        size.columns,
        Row,
        HeaderRow,
        CornerIndicator,
        columnLabels,
        ColumnIndicator,
        rowLabels,
        RowIndicator,
        Cell,
        DataViewer,
        visibleRange.end,
        visibleRange.start,
    ]);
    const activeCellNode = React.useMemo(() => (_jsx(ActiveCell, { attachmentAction: attachmentAction, contextOption: contextOption, setContextMenu: props.setContextMenu, 
        // @ts-ignore
        DataEditor: DataEditor })), [DataEditor]);
    const rootNode = React.useMemo(() => (_jsxs("div", { className: "ff-excel-spreadsheet-container", children: [_jsx("div", { style: { width: `${maxWidth}px` }, className: classNames('ff-excel-header-column-hider', {
                    'ff-excel-header-extend': ['show', 'disable'].includes(toolbar || ''),
                    'ff-excel-header-hider': toolbar === 'hide',
                }) }), _jsx("div", { style: { height: `${maxHeight}px` }, className: classNames('ff-excel-header-column-hider', {
                    'ff-excel-header-extend': ['show', 'disable'].includes(toolbar || ''),
                    'ff-excel-header-hider': toolbar === 'hide',
                }) }), toolbar !== 'hide' && (_jsx("div", { className: "ff-excel-toolbar-container", children: _jsx(ExcelToolBar, { editable: editable, toolbar: toolbar, onBold: onBold, onItalic: onItalic, setUnderlineType: setUnderlineType, setColor: setColor, setBorderType: setBorderType, setFontSize: setFontSize, setFontFamily: setFontFamily, setTextAlign: setTextAlign, setBackgroundColor: setBackgroundColor, setFormatePainter: setFormatePainter }) })), _jsxs("div", { ref: rootRef, style: { height: sheetHeight }, className: classNames('ff-excel-spreadsheet', className), onKeyPress: onKeyPress, onKeyDown: handleKeyDown, onClick: handleClick, onMouseMove: handleMouseMove, onScroll: (e) => {
                    scrollOption && throttledHandleScroll(e);
                    setContextMenu({
                        open: false,
                        options: [
                            {
                                label: '',
                                value: '',
                                iconName: '',
                                action: () => { },
                                disableTooltip: '',
                                visible: false,
                                disable: false,
                            },
                        ],
                    });
                }, children: [tableNode, activeCellNode, _jsx(Selected, {}), _jsx(Copied, {})] })] })), [
        className,
        onKeyPress,
        handleKeyDown,
        handleMouseMove,
        tableNode,
        activeCellNode,
        maxWidth,
    ]);
    return (_jsx(context.Provider, { value: reducerElements, children: rootNode }));
};
export default Spreadsheet;
//# sourceMappingURL=Spreadsheet.js.map