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, INITIAL_ROW, INITIAL_COLUMN, ROW_BUFFER, COL_BUFFER, } 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';
import useSelector from './use-selector';
/**
 * The Spreadsheet component
 */
const Spreadsheet = React.forwardRef(function Spreadsheet(props, ref) {
    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, maxRowLimit, maxColLimit, disableDeleteOption, contextMenu, getActiveCell, onAddColumn, onDeleteColumn, } = props;
    const DEFAULT_ROW_HEIGHT = 32;
    const DEFAULT_COLUMN_WIDTH = minimumColumnWidth || 100;
    const initialized = React.useRef(false);
    const data = useSelector((state) => state.model.data);
    const [sheetChange, setSheetChange] = React.useState(false);
    const [maxWidth, setMaxWidth] = React.useState(0);
    const [maxHeight, setMaxHeight] = React.useState(0);
    const [scrollPos, setScrollPos] = React.useState({ top: 0, left: 0 });
    const [isAtBottom, setIsAtBottom] = React.useState(false);
    const [isAtRight, setIsAtRight] = React.useState(false);
    const initialState = React.useMemo(() => {
        const createParser = (props.createFormulaParser ||
            createFormulaParser);
        const model = new Model(createParser, props.data);
        let newRowDimensions = {};
        let newColumnDimensions = {};
        props.data.forEach((rowData, rowIndex) => {
            let maxHeight = DEFAULT_ROW_HEIGHT;
            rowData.forEach((cell) => {
                if (cell) {
                    const cellHeight = parseFloat(String(cell.style?.height).replace('px', '')) ||
                        DEFAULT_ROW_HEIGHT;
                    maxHeight = Math.max(maxHeight, cellHeight);
                }
            });
            newRowDimensions[rowIndex] = { top: 0, height: maxHeight };
        });
        const columnCount = props.data[0] ? props.data[0].length : 0;
        for (let col = 0; col < columnCount; col++) {
            let maxWidth = DEFAULT_COLUMN_WIDTH;
            props.data.forEach((rowData) => {
                const cell = rowData[col];
                if (cell) {
                    const cellWidth = parseFloat(String(cell.style?.width).replace('px', '')) ||
                        DEFAULT_COLUMN_WIDTH;
                    maxWidth = Math.max(maxWidth, cellWidth);
                }
            });
            newColumnDimensions[col] = { left: 0, width: maxWidth };
        }
        setSheetChange((prev) => !prev);
        return {
            ...INITIAL_STATE,
            model,
            selected: props.selected || INITIAL_STATE.selected,
            rowDimensions: newRowDimensions,
            columnDimensions: newColumnDimensions,
        };
    }, [props.createFormulaParser, props.data, props.selected]);
    const reducerElements = React.useReducer(reducer, initialState);
    const [state, dispatch] = reducerElements;
    const size = React.useMemo(() => {
        return calculateSpreadsheetSize(state.model.data, rowLabels, columnLabels);
    }, [state.model.data, rowLabels, columnLabels]);
    const [visibleRowRange, setVisibleRowRange] = React.useState({
        start: 0,
        end: Math.min(INITIAL_ROW, size.rows),
    });
    const [visibleColRange, setVisibleColRange] = React.useState({
        start: 0,
        end: Math.min(INITIAL_COLUMN, size.columns),
    });
    const getCumulativeHeight = React.useCallback((row) => {
        let height = 0;
        for (let r = 0; r < row; r++) {
            height += state.rowDimensions[r]?.height || DEFAULT_ROW_HEIGHT;
        }
        return height;
    }, [state.rowDimensions, DEFAULT_ROW_HEIGHT]);
    const getCumulativeWidth = React.useCallback((col) => {
        let width = 0;
        for (let c = 0; c < col; c++) {
            width += state.columnDimensions[c]?.width || DEFAULT_COLUMN_WIDTH;
        }
        return width;
    }, [state.columnDimensions, DEFAULT_COLUMN_WIDTH]);
    // Update column dimensions when minimumColumnWidth changes
    const isFirstRender = React.useRef(true);
    React.useEffect(() => {
        if (isFirstRender.current) {
            isFirstRender.current = false;
            return;
        }
        dispatch(Actions.updateColumnDimensions(minimumColumnWidth));
    }, [minimumColumnWidth]);
    const totalHeight = getCumulativeHeight(size.rows);
    const totalWidth = getCumulativeWidth(size.columns);
    const beforeRowHeight = getCumulativeHeight(visibleRowRange.start);
    const afterRowHeight = totalHeight - getCumulativeHeight(visibleRowRange.end);
    const beforeColWidth = getCumulativeWidth(visibleColRange.start);
    const afterColWidth = totalWidth - getCumulativeWidth(visibleColRange.end);
    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);
    // Expose removeSelect
    React.useImperativeHandle(ref, () => ({
        removeSelect,
    }));
    // 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]);
    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();
    React.useEffect(() => {
        setEditable(editable);
        if (prevDataPropRef.current !== undefined &&
            props.data !== prevDataPropRef.current) {
            setData(props.data);
        }
        prevDataPropRef.current = props.data;
    }, [props.data, setData, editable]);
    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();
        const isCtrlOrCmd = event.ctrlKey || event.metaKey;
        const key = event.key.toLowerCase();
        if (isCtrlOrCmd && key === 'b') {
            event.preventDefault();
            onBold(data);
        }
        else if (isCtrlOrCmd && key === 'i') {
            event.preventDefault();
            onItalic(data);
        }
        else if (isCtrlOrCmd && key === 'u') {
            event.preventDefault();
            setUnderlineType(data);
        }
        if (onKeyDown) {
            onKeyDown(event);
        }
        if (!event.defaultPrevented) {
            if (hasKeyDownHandler(state, event)) {
                event.nativeEvent.preventDefault();
            }
            onKeyDownAction(event);
        }
    }, [state, onKeyDown, onKeyDownAction]);
    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 && mode === 'view') {
            onDragStart();
            document.addEventListener('mouseup', handleMouseUp);
        }
    }, [state, onDragStart, handleMouseUp, mode]);
    const Cell = React.useMemo(() => {
        // @ts-ignore
        return enhanceCell(props.Cell || DefaultCell);
    }, [props.Cell, sheetChange, editable]);
    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 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));
                    setMaxWidth(Math.min(rootRef.current.clientWidth, clientWidth));
                }
            }
        });
        const table = ref.current;
        if (!table)
            return;
        resizeObserver.observe(table);
    };
    const handleScroll = React.useCallback((e) => {
        const target = e.target;
        const { scrollTop, clientHeight, scrollLeft, clientWidth } = target;
        setScrollPos({ top: scrollTop, left: scrollLeft });
        // ----- For rows (NEW)-----
        let newRowStart = 0;
        while (newRowStart < size.rows &&
            getCumulativeHeight(newRowStart) < scrollTop) {
            newRowStart++;
        }
        let newRowEnd = newRowStart;
        while (newRowEnd < size.rows &&
            getCumulativeHeight(newRowEnd) < scrollTop + clientHeight) {
            newRowEnd++;
        }
        newRowStart = Math.max(0, newRowStart - ROW_BUFFER);
        newRowEnd = Math.min(size.rows, newRowEnd + ROW_BUFFER);
        setVisibleRowRange({ start: newRowStart, end: newRowEnd });
        const atBottom = newRowEnd === size.rows;
        setIsAtBottom(atBottom);
        // ----- For columns (NEW) -----
        let newColStart = 0;
        while (newColStart < size.columns &&
            getCumulativeWidth(newColStart) < scrollLeft) {
            newColStart++;
        }
        let newColEnd = newColStart;
        while (newColEnd < size.columns &&
            getCumulativeWidth(newColEnd) < scrollLeft + clientWidth) {
            newColEnd++;
        }
        newColStart = Math.max(0, newColStart - COL_BUFFER);
        newColEnd = Math.min(size.columns, newColEnd + COL_BUFFER);
        setVisibleColRange({ start: newColStart, end: newColEnd });
        const atRight = newColEnd === size.columns;
        setIsAtRight(atRight);
        // Close context menu on scroll
        if (contextMenu.open) {
            setContextMenu({
                open: false,
                contextType: null,
                options: [
                    {
                        label: '',
                        value: '',
                        iconName: '',
                        action: () => { },
                        disableTooltip: '',
                        visible: false,
                        disable: false,
                    },
                ],
            });
        }
    }, [size.rows, size.columns, getCumulativeHeight, getCumulativeWidth]);
    const throttledHandleScroll = React.useMemo(() => throttle(handleScroll, 16), [handleScroll]);
    const scrollToRow = (row) => {
        initialized.current = true;
        requestAnimationFrame(() => {
            let scrollTop = 0;
            for (let r = 0; r <= row; r++) {
                scrollTop += state.rowDimensions?.[r]?.height || DEFAULT_ROW_HEIGHT;
            }
            requestAnimationFrame(() => {
                if (!rootRef.current)
                    return;
                if (row >= size.rows - 1) {
                    rootRef?.current?.scrollTo({
                        top: scrollTop + DEFAULT_ROW_HEIGHT,
                        behavior: 'smooth',
                    });
                }
            });
        });
    };
    const scrollToColumn = (column) => {
        initialized.current = true;
        requestAnimationFrame(() => {
            let scrollLeft = 0;
            for (let c = 0; c <= column; c++) {
                scrollLeft +=
                    state.columnDimensions?.[c]?.width || DEFAULT_COLUMN_WIDTH;
            }
            requestAnimationFrame(() => {
                if (!rootRef.current)
                    return;
                if (column >= size.columns - 1) {
                    rootRef.current.scrollTo({
                        left: scrollLeft + 60,
                        behavior: 'smooth',
                    });
                }
            });
        });
    };
    React.useEffect(() => {
        if (!initialized.current) {
            setVisibleRowRange({
                start: 0,
                end: Math.min(INITIAL_ROW, size.rows),
            });
            setVisibleColRange({
                start: 0,
                end: Math.min(INITIAL_COLUMN, size.columns),
            });
        }
    }, [size.rows, size.columns, initialized.current]);
    const visibleRows = range(visibleRowRange.end, visibleRowRange.start);
    const visibleCols = range(visibleColRange.end, visibleColRange.start);
    const colgroup = (_jsxs("colgroup", { children: [_jsx("col", { style: { width: '60px' } }, "row-indicator"), ' ', _jsx("col", { style: { width: `${beforeColWidth}px` } }, "left-spacer"), ' ', visibleCols.map((colNumber) => (_jsx("col", { style: {
                    width: `${state.columnDimensions[colNumber]?.width || DEFAULT_COLUMN_WIDTH}px`,
                } }, colNumber))), _jsx("col", { style: { width: `${afterColWidth}px` } }, "right-spacer"), ' '] }));
    const tableNode = React.useMemo(() => (_jsxs(Table, { useTableRef: useTableRef, totalWidth: totalWidth, children: [colgroup, _jsxs("tbody", { children: [_jsxs(HeaderRow, { children: [_jsx(CornerIndicator, {}), _jsx("div", { className: "spacer left" }), visibleCols.map((colNumber) => (_jsx(ColumnIndicator, { column: colNumber, minimumColumnWidth: minimumColumnWidth, setContextMenu: props.setContextMenu, label: colNumber in columnLabels
                                    ? columnLabels[colNumber]
                                    : undefined, deleteColumn: deleteColumn, addColumnLeft: addColumnLeft, addColumnRight: addColumnRight, columnContextEnable: props.columnContextEnable, maxColLimit: maxColLimit, disableDeleteOption: disableDeleteOption, contextMenu: contextMenu, onAddColumn: onAddColumn, onDeleteColumn: onDeleteColumn, setVisibleRange: setVisibleColRange, scrollToColumn: scrollToColumn }, colNumber))), _jsx("div", { className: "spacer right" })] }), _jsx("tr", { style: { height: `${beforeRowHeight}px` } }), visibleRows.map((rowNumber) => (_jsxs(Row, { row: rowNumber, children: [_jsx(RowIndicator, { label: rowNumber in rowLabels ? rowLabels?.[rowNumber] : undefined, row: rowNumber, addRowTop: addRowTop, addRowBottom: addRowBottom, deleteRow: deleteRow, setContextMenu: props.setContextMenu, rowContextEnable: props.rowContextEnable, maxRowLimit: maxRowLimit, disableDeleteOption: disableDeleteOption, contextMenu: contextMenu, setVisibleRange: setVisibleRowRange, scrollToRow: scrollToRow }, rowNumber), _jsx("div", { className: "spacer left" }), visibleCols.map((columnNumber) => (_jsx(Cell, { row: rowNumber, column: columnNumber, 
                                // @ts-ignore
                                DataViewer: DataViewer }, `${rowNumber}-${columnNumber}`))), _jsx("div", { className: "spacer right" })] }, rowNumber))), _jsx("tr", { style: { height: `${afterRowHeight}px` } })] })] })), [
        Table,
        size.rows,
        size.columns,
        Row,
        HeaderRow,
        CornerIndicator,
        columnLabels,
        ColumnIndicator,
        rowLabels,
        RowIndicator,
        Cell,
        DataViewer,
        visibleRowRange,
        visibleColRange,
        beforeRowHeight,
        afterRowHeight,
        beforeColWidth,
        afterColWidth,
        state.rowDimensions,
        state.columnDimensions,
    ]);
    const activeCellNode = React.useMemo(() => (_jsx(ActiveCell, { attachmentAction: attachmentAction, contextOption: contextOption, getActiveCell: getActiveCell, setContextMenu: props.setContextMenu, 
        // @ts-ignore
        DataEditor: DataEditor, visibleRowRange: visibleRowRange, visibleColRange: visibleColRange, maxHeight: maxHeight - 64, maxWidth: maxWidth - minimumColumnWidth, totalHeight: totalHeight, totalWidth: totalWidth, isAtBottom: isAtBottom, isAtRight: isAtRight, scrollPos: scrollPos, editable: editable }, state.active
        ? `${state.active.row}-${state.active.column}`
        : 'no-active')), [
        DataEditor,
        visibleRowRange,
        visibleColRange,
        state.active,
        maxHeight,
        maxWidth,
        totalHeight,
        totalWidth,
        isAtBottom,
        isAtRight,
        editable,
    ]);
    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) => throttledHandleScroll(e), children: [tableNode, activeCellNode, _jsx(Selected, { scrollPos: scrollPos }), _jsx(Copied, { scrollPos: scrollPos })] })] })), [
        className,
        onKeyPress,
        handleKeyDown,
        handleMouseMove,
        tableNode,
        activeCellNode,
        maxWidth,
    ]);
    return (_jsx(context.Provider, { value: reducerElements, children: rootNode }));
});
export default Spreadsheet;
//# sourceMappingURL=Spreadsheet.js.map