import { jsxs, jsx } from 'react/jsx-runtime';
import * as React from 'react';
import classNames from '../../../../_virtual/index.js';
import { updateColumnDimensions, blur, setSelection, editable, setData, setCreateFormulaParser, cut, copy, paste, bold, italic, underlineType, keyDown, onMouseUp, dragEnd, dragStart, addColumnRight, addColumnLeft, deleteColumn, deleteRow, addRowBottom, addRowTop, formatePainter, backgroundStyle, textAlign, fontFamily, fontSize, borderType, color, keyPress } from './actions.js';
import { slice } from './matrix.js';
import reducer, { INITIAL_STATE } from './reducer.js';
import context from './context.js';
import { Model } from './engine/engine.js';
import { createFormulaParser } from './engine/formula.js';
import { calculateSpreadsheetSize, INITIAL_ROW, INITIAL_COLUMN, isFocusedWithin, getCSV, writeTextToClipboard, shouldHandleClipboardEvent, readTextFromClipboard, ROW_BUFFER, COL_BUFFER, range } from './util.js';
import Table from './Table.js';
import Row from './Row.js';
import HeaderRow from './HeaderRow.js';
import CornerIndicator, { enhance as enhance$1 } from './CornerIndicator.js';
import EnhancedColumnIndicator, { enhance as enhance$3 } from './ColumnIndicator.js';
import RowIndicator, { enhance as enhance$2 } from './RowIndicator.js';
import { enhance, Cell } from './Cell.js';
import DataViewer from './DataViewer.js';
import DataEditor from './DataEditor.js';
import ActiveCell from './ActiveCell.js';
import Selected from './Selected.js';
import Copied from './Copied.js';
import ExcelToolBar from '../../ExcelToolBar/ExcelToolBar.js';
import { hasKeyDownHandler } from './reducerFunctions.js';
import { throttle } from '../../../../utils/throttle/throttle.js';
import useSelector from './use-selector.js';

/**
 * The Spreadsheet component
 */
const Spreadsheet = React.forwardRef(function Spreadsheet(props, ref) {
    const { className, columnLabels = [], rowLabels = [], toolbar, editable: editable$1, onKeyDown, Table: Table$1 = Table, Row: Row$1 = Row, sheetHeight, HeaderRow: HeaderRow$1 = HeaderRow, DataEditor: DataEditor$1 = DataEditor, DataViewer: DataViewer$1 = DataViewer, 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(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(editable);
    const cut$1 = useAction(cut);
    const copy$1 = useAction(copy);
    const removeSelect = useAction(blur);
    const paste$1 = useAction(paste);
    const onKeyDownAction = useAction(keyDown);
    const onKeyPress = useAction(keyPress);
    const onDragStart = useAction(dragStart);
    const onDragEnd = useAction(dragEnd);
    const setData$1 = useAction(setData);
    const setCreateFormulaParser$1 = useAction(setCreateFormulaParser);
    const setSelection$1 = useAction(setSelection);
    const onBold = useAction(bold);
    const onItalic = useAction(italic);
    const setUnderlineType = useAction(underlineType);
    const setTextAlign = useAction(textAlign);
    const setFontSize = useAction(fontSize);
    const setFontFamily = useAction(fontFamily);
    const setBorderType = useAction(borderType);
    const setColor = useAction(color);
    const setBackgroundColor = useAction(backgroundStyle);
    const setFormatePainter = useAction(formatePainter);
    const formateClick = useAction(onMouseUp);
    const addRowTop$1 = useAction(addRowTop);
    const addRowBottom$1 = useAction(addRowBottom);
    const addColumnLeft$1 = useAction(addColumnLeft);
    const addColumnRight$1 = useAction(addColumnRight);
    const deleteRow$1 = useAction(deleteRow);
    const deleteColumn$1 = useAction(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$1(props.selected);
        }
        prevSelectedPropRef.current = props.selected;
    }, [props.selected, setSelection$1]);
    // Update data when props.data changes
    let prevDataPropRef = React.useRef();
    React.useEffect(() => {
        setEditable(editable$1);
        if (prevDataPropRef.current !== undefined &&
            props.data !== prevDataPropRef.current) {
            setData$1(props.data);
        }
        prevDataPropRef.current = props.data;
    }, [props.data, setData$1, editable$1]);
    const prevCreateFormulaParserPropRef = React.useRef(props.createFormulaParser);
    React.useEffect(() => {
        if (props.createFormulaParser !== prevCreateFormulaParserPropRef.current &&
            props.createFormulaParser)
            setCreateFormulaParser$1(props.createFormulaParser);
        prevCreateFormulaParserPropRef.current = props.createFormulaParser;
    }, [props.createFormulaParser, setCreateFormulaParser$1]);
    const writeDataToClipboard = React.useCallback((event) => {
        const { model, selected } = state;
        const { data } = model;
        const range = selected.toRange(data);
        if (range) {
            const selectedData = 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$1();
        }
    }, [mode, writeDataToClipboard, cut$1]);
    const handleCopy = React.useCallback((event) => {
        if (shouldHandleClipboardEvent(rootRef.current, mode)) {
            event.preventDefault();
            event.stopPropagation();
            writeDataToClipboard(event);
            copy$1();
        }
    }, [mode, writeDataToClipboard, copy$1]);
    const handlePaste = React.useCallback((event) => {
        if (shouldHandleClipboardEvent(rootRef.current, mode)) {
            event.preventDefault();
            event.stopPropagation();
            if (event.clipboardData) {
                const text = readTextFromClipboard(event);
                paste$1(text);
            }
        }
    }, [mode, paste$1]);
    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$1 = React.useMemo(() => {
        // @ts-ignore
        return enhance(props.Cell || Cell);
    }, [props.Cell, sheetChange, editable$1]);
    const CornerIndicator$1 = React.useMemo(() => enhance$1(props.CornerIndicator || CornerIndicator), [props.CornerIndicator, sheetChange]);
    const RowIndicator$1 = React.useMemo(() => enhance$2(props.RowIndicator || RowIndicator), [props.RowIndicator, sheetChange]);
    const ColumnIndicator = React.useMemo(() => enhance$3(props.ColumnIndicator || EnhancedColumnIndicator), [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$1, { useTableRef: useTableRef, totalWidth: totalWidth, children: [colgroup, jsxs("tbody", { children: [jsxs(HeaderRow$1, { children: [jsx(CornerIndicator$1, {}), 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$1, addColumnLeft: addColumnLeft$1, addColumnRight: addColumnRight$1, 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$1, { row: rowNumber, children: [jsx(RowIndicator$1, { label: rowNumber in rowLabels ? rowLabels?.[rowNumber] : undefined, row: rowNumber, addRowTop: addRowTop$1, addRowBottom: addRowBottom$1, deleteRow: deleteRow$1, 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$1, { row: rowNumber, column: columnNumber, 
                                // @ts-ignore
                                DataViewer: DataViewer$1 }, `${rowNumber}-${columnNumber}`))), jsx("div", { className: "spacer right" })] }, rowNumber))), jsx("tr", { style: { height: `${afterRowHeight}px` } })] })] })), [
        Table$1,
        size.rows,
        size.columns,
        Row$1,
        HeaderRow$1,
        CornerIndicator$1,
        columnLabels,
        ColumnIndicator,
        rowLabels,
        RowIndicator$1,
        Cell$1,
        DataViewer$1,
        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$1, visibleRowRange: visibleRowRange, visibleColRange: visibleColRange, maxHeight: maxHeight - 64, maxWidth: maxWidth - minimumColumnWidth, totalHeight: totalHeight, totalWidth: totalWidth, isAtBottom: isAtBottom, isAtRight: isAtRight, scrollPos: scrollPos, editable: editable$1 }, state.active
        ? `${state.active.row}-${state.active.column}`
        : 'no-active')), [
        DataEditor$1,
        visibleRowRange,
        visibleColRange,
        state.active,
        maxHeight,
        maxWidth,
        totalHeight,
        totalWidth,
        isAtBottom,
        isAtRight,
        editable$1,
    ]);
    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$1, 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 { Spreadsheet as default };
//# sourceMappingURL=Spreadsheet.js.map
