const INITIAL_COLUMN_COUNT = 21;
const INITIAL_ROW_COUNT = 23;
const COLUMN_GROWTH_BATCH = 10;
const ROW_GROWTH_BATCH = 30;
const MAX_COLUMN_COUNT = 1000;
const MAX_ROW_COUNT = 10000;
const GROWTH_SCROLL_THRESHOLD = 100;
let columns = Array.from({ length: INITIAL_COLUMN_COUNT }, (_, index) => getColumnName(index));
let rows = Array.from({ length: INITIAL_ROW_COUNT }, (_, index) => index + 1);
const DEFAULT_COLUMN_WIDTH = 72;
const DEFAULT_ROW_HEIGHT = 25;
const MIN_ROW_HEADER_WIDTH = 28;
const MIN_COLUMN_WIDTH = 32;
const MIN_ROW_HEIGHT = 20;
const firstCell = { columnIndex: 0, rowIndex: 0 };
let nextSheetId = 2;
const defaultStyle = {
    fontFamily: "新細明體",
    fontSize: 11,
    bold: false,
    italic: false,
    underline: false,
    fillColor: "",
    fontColor: "",
    textAlign: "left",
    verticalAlign: "middle",
};

function createSheet(id, name) {
    return {
        id,
        name,
        cellValues: {},
        cellStyles: {},
        mergedCells: [],
        columnWidths: Array(INITIAL_COLUMN_COUNT).fill(DEFAULT_COLUMN_WIDTH),
        rowHeights: Array(INITIAL_ROW_COUNT).fill(DEFAULT_ROW_HEIGHT),
    };
}

function getColumnName(columnIndex) {
    let value = columnIndex + 1;
    let name = "";

    while (value > 0) {
        const remainder = (value - 1) % 26;
        name = String.fromCharCode(65 + remainder) + name;
        value = Math.floor((value - 1) / 26);
    }

    return name;
}

function getColumnIndex(columnName) {
    return columnName.split("").reduce((index, character) => (
        index * 26 + character.charCodeAt(0) - 64
    ), 0) - 1;
}

function getCellName({ columnIndex, rowIndex }) {
    return `${getColumnName(columnIndex)}${rowIndex + 1}`;
}

function getCellFromName(cellName) {
    const match = /^([A-Z]+)(\d+)$/.exec(cellName);
    return {
        columnIndex: getColumnIndex(match[1]),
        rowIndex: Number(match[2]) - 1,
    };
}

function getRangeBounds(anchorCell, activeCell) {
    return {
        startColumn: Math.min(anchorCell.columnIndex, activeCell.columnIndex),
        endColumn: Math.max(anchorCell.columnIndex, activeCell.columnIndex),
        startRow: Math.min(anchorCell.rowIndex, activeCell.rowIndex),
        endRow: Math.max(anchorCell.rowIndex, activeCell.rowIndex),
    };
}

function getCellsInRange(anchorCell, activeCell) {
    const bounds = getRangeBounds(anchorCell, activeCell);
    const cellNames = [];

    for (let rowIndex = bounds.startRow; rowIndex <= bounds.endRow; rowIndex += 1) {
        for (let columnIndex = bounds.startColumn; columnIndex <= bounds.endColumn; columnIndex += 1) {
            cellNames.push(getCellName({ columnIndex, rowIndex }));
        }
    }

    return cellNames;
}

function isCellInRange(cell, anchorCell, activeCell) {
    const bounds = getRangeBounds(anchorCell, activeCell);
    return (
        cell.columnIndex >= bounds.startColumn &&
        cell.columnIndex <= bounds.endColumn &&
        cell.rowIndex >= bounds.startRow &&
        cell.rowIndex <= bounds.endRow
    );
}

function clamp(value, min, max) {
    return Math.min(Math.max(value, min), max);
}

function mergeStyle(style = {}) {
    return { ...defaultStyle, ...style };
}

function getCellCss(style = {}) {
    const merged = mergeStyle(style);
    const verticalAlignment = {
        top: "flex-start",
        middle: "center",
        bottom: "flex-end",
    };
    return {
        display: "grid",
        alignItems: verticalAlignment[merged.verticalAlign],
        backgroundColor: merged.fillColor || "transparent",
        color: merged.fontColor || "#111827",
        fontFamily: merged.fontFamily,
        fontSize: `${merged.fontSize}px`,
        fontWeight: merged.bold ? 700 : 400,
        fontStyle: merged.italic ? "italic" : "normal",
        textDecoration: merged.underline ? "underline" : "none",
        textAlign: merged.textAlign,
        whiteSpace: "pre",
        overflowWrap: "normal",
        writingMode: "horizontal-tb",
    };
}

function isPrintableKey(event) {
    return event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey;
}

function getRowHeaderWidth(rowCount) {
    return Math.max(
        MIN_ROW_HEADER_WIDTH,
        String(rowCount).length * 10 + 14,
    );
}

function getSheetColumnsTemplate(columnWidths, rowHeaderWidth) {
    return `${rowHeaderWidth}px ${columnWidths.map((width) => `${width}px`).join(" ")}`;
}

function estimateTextWidth(value, style) {
    const merged = mergeStyle(style);
    const maxChars = String(value || "")
        .split("\n")
        .reduce((longest, line) => Math.max(longest, line.length), 0);
    return Math.ceil(maxChars * merged.fontSize * 0.62 + 14);
}

function estimateTextHeight(value, style) {
    const merged = mergeStyle(style);
    const lines = Math.max(1, String(value || "").split("\n").length);
    return Math.ceil(lines * merged.fontSize * 1.25 + 6);
}

function Fa({ icon }) {
    return <i className={`fa-solid ${icon}`} aria-hidden="true" />;
}

function IconButton({ children, className = "", active = false, label, onClick }) {
    return (
        <button
            className={`icon-btn${active ? " active" : ""}${className ? ` ${className}` : ""}`}
            type="button"
            aria-label={label}
            title={label}
            onMouseDown={(event) => event.preventDefault()}
            onClick={onClick}
        >
            {children}
        </button>
    );
}

function RibbonPanel({ children }) {
    return <section className="ribbon-panel">{children}</section>;
}

function Ribbon({ activeStyle, formatPainter, isMergedSelection, onCommand, onFontFamilyChange, onFontSizeChange }) {
    return (
        <div className="excel-ribbon">
            <div className="ribbon-row">
                <RibbonPanel>
                    <IconButton label="貼上" onClick={() => onCommand("paste")}><Fa icon="fa-paste" /></IconButton>
                    <IconButton label="剪下" onClick={() => onCommand("cut")}><Fa icon="fa-scissors" /></IconButton>
                    <IconButton label="複製" onClick={() => onCommand("copy")}><Fa icon="fa-copy" /></IconButton>
                    <IconButton active={formatPainter} label="複製格式" onClick={() => onCommand("formatPainter")}><Fa icon="fa-paintbrush" /></IconButton>
                </RibbonPanel>

                <RibbonPanel>
                    <select className="select" value={activeStyle.fontFamily} aria-label="字型" onChange={(event) => onFontFamilyChange(event.target.value)}>
                        <option>新細明體</option>
                        <option>微軟正黑體</option>
                        <option>Arial</option>
                    </select>
                    <select className="font-size" value={activeStyle.fontSize} aria-label="字級" onChange={(event) => onFontSizeChange(Number(event.target.value))}>
                        <option value="11">11</option>
                        <option value="12">12</option>
                        <option value="14">14</option>
                        <option value="16">16</option>
                        <option value="18">18</option>
                    </select>
                    <IconButton label="放大字型" onClick={() => onCommand("increaseFont")}><Fa icon="fa-font" /><Fa icon="fa-caret-up" /></IconButton>
                    <IconButton label="縮小字型" onClick={() => onCommand("decreaseFont")}><Fa icon="fa-font" /><Fa icon="fa-caret-down" /></IconButton>
                </RibbonPanel>

                <RibbonPanel>
                    <IconButton active={activeStyle.bold} label="粗體" onClick={() => onCommand("bold")}><Fa icon="fa-bold" /></IconButton>
                    <IconButton active={activeStyle.italic} label="斜體" onClick={() => onCommand("italic")}><Fa icon="fa-italic" /></IconButton>
                    <IconButton active={activeStyle.underline} label="底線" onClick={() => onCommand("underline")}><Fa icon="fa-underline" /></IconButton>
                    <IconButton label="框線" onClick={() => onCommand("border")}><Fa icon="fa-border-all" /></IconButton>
                    <IconButton className="fill-color" active={Boolean(activeStyle.fillColor)} label="填滿色" onClick={() => onCommand("fillColor")}><Fa icon="fa-fill-drip" /></IconButton>
                    <IconButton className="font-color" active={Boolean(activeStyle.fontColor)} label="字型色彩" onClick={() => onCommand("fontColor")}><Fa icon="fa-font" /></IconButton>
                </RibbonPanel>
            </div>

            <div className="ribbon-row ribbon-second-row">
              <RibbonPanel>
                <IconButton active={activeStyle.textAlign === "left"} label="靠左" onClick={() => onCommand("alignLeft")}><Fa icon="fa-align-left" /></IconButton>
                <IconButton active={activeStyle.textAlign === "center"} label="置中" onClick={() => onCommand("alignCenter")}><Fa icon="fa-align-center" /></IconButton>
                <IconButton active={activeStyle.textAlign === "right"} label="靠右" onClick={() => onCommand("alignRight")}><Fa icon="fa-align-right" /></IconButton>
                <IconButton active={activeStyle.verticalAlign === "top"} label="靠上" onClick={() => onCommand("alignTop")}><Fa icon="fa-arrow-up" /></IconButton>
                <IconButton active={activeStyle.verticalAlign === "middle"} label="靠中" onClick={() => onCommand("alignMiddle")}><Fa icon="fa-arrows-up-down" /></IconButton>
                <IconButton active={activeStyle.verticalAlign === "bottom"} label="靠下" onClick={() => onCommand("alignBottom")}><Fa icon="fa-arrow-down" /></IconButton>
                <IconButton active={isMergedSelection} label={isMergedSelection ? "取消合併" : "跨欄置中"} onClick={() => onCommand("mergeCenter")}><Fa icon="fa-table-columns" /></IconButton>
              </RibbonPanel>
            </div>
        </div>
    );
}

function FormulaBar({ activeCellName, activeCellValue, onValueChange, onEditStart }) {
    return (
        <div className="formula-bar">
            <input className="name-box" value={activeCellName} readOnly aria-label={`名稱方塊 ${activeCellName}`} />
            <div className="formula-tools">                
                <span className="fx">fx</span>
            </div>
            <input
                className="formula-input"
                value={activeCellValue}
                aria-label={`公式列 ${activeCellName}`}
                onFocus={onEditStart}
                onChange={(event) => onValueChange(activeCellName, event.target.value)}
            />
        </div>
    );
}

function ContextMenu({ menu, onCommand, canInsertDelete }) {
    if (!menu) return null;

    return (
        <div
            className="sheet-context-menu"
            role="menu"
            aria-label="儲存格操作"
            style={{ left: `${menu.x}px`, top: `${menu.y}px` }}
            onMouseDown={(event) => event.stopPropagation()}
            onContextMenu={(event) => event.preventDefault()}
        >
            <button type="button" role="menuitem" onClick={() => onCommand("cut")}>
                <Fa icon="fa-scissors" />
                <span>剪下</span>
                <kbd>Ctrl+X</kbd>
            </button>
            <button type="button" role="menuitem" onClick={() => onCommand("copy")}>
                <Fa icon="fa-copy" />
                <span>複製</span>
                <kbd>Ctrl+C</kbd>
            </button>
            <div className="context-menu-separator" />
            <button type="button" role="menuitem" disabled={!canInsertDelete} onClick={() => onCommand("insert")}>
                <Fa icon="fa-plus" />
                <span>插入</span>
            </button>
            <button type="button" role="menuitem" disabled={!canInsertDelete} onClick={() => onCommand("delete")}>
                <Fa icon="fa-trash-can" />
                <span>刪除</span>
            </button>
        </div>
    );
}

function SheetTabMenu({ menu, canDelete, onDelete }) {
    if (!menu) return null;

    return (
        <div
            className="sheet-tab-menu"
            role="menu"
            aria-label="工作表操作"
            style={{ left: `${menu.x}px`, top: `${menu.y}px` }}
            onMouseDown={(event) => event.stopPropagation()}
            onContextMenu={(event) => event.preventDefault()}
        >
            <button type="button" role="menuitem" disabled={!canDelete} onClick={onDelete}>
                <Fa icon="fa-trash-can" />
                <span>刪除工作表</span>
            </button>
        </div>
    );
}

function SheetTabs({
    sheets,
    activeSheetId,
    editingSheetId,
    editingName,
    onAdd,
    onSelect,
    onRenameStart,
    onRenameChange,
    onRenameCommit,
    onContextMenu,
    isFullscreen,
    onToggleFullscreen,
}) {
    return (
        <div className="sheet-tabs" aria-label="工作表頁籤">
            <div className="sheet-tabs-scroll">
                {sheets.map((sheet) => (
                    <div
                        className={`sheet-tab${sheet.id === activeSheetId ? " active" : ""}`}
                        key={sheet.id}
                        role="button"
                        tabIndex="0"
                        aria-label={`切換至 ${sheet.name}`}
                        aria-pressed={sheet.id === activeSheetId}
                        onClick={() => onSelect(sheet.id)}
                        onDoubleClick={() => onRenameStart(sheet)}
                        onContextMenu={(event) => onContextMenu(event, sheet.id)}
                        onKeyDown={(event) => {
                            if (event.key === "Enter" || event.key === " ") {
                                event.preventDefault();
                                onSelect(sheet.id);
                            }
                        }}
                    >
                        {editingSheetId === sheet.id ? (
                            <input
                                className="sheet-tab-name-input"
                                value={editingName}
                                aria-label="工作表名稱"
                                autoFocus
                                onClick={(event) => event.stopPropagation()}
                                onChange={(event) => onRenameChange(event.target.value)}
                                onBlur={onRenameCommit}
                                onKeyDown={(event) => {
                                    event.stopPropagation();
                                    if (event.key === "Enter") onRenameCommit();
                                    if (event.key === "Escape") onRenameCommit(true);
                                }}
                            />
                        ) : (
                            <span>{sheet.name}</span>
                        )}
                    </div>
                ))}
            </div>
            <button className="sheet-add-button" type="button" aria-label="新增工作表" title="新增工作表" onClick={onAdd}>
                <Fa icon="fa-plus" />
            </button>
            <button
                className="sheet-fullscreen-button"
                type="button"
                aria-label={isFullscreen ? "退出全螢幕" : "進入全螢幕"}
                title={isFullscreen ? "退出全螢幕" : "進入全螢幕"}
                aria-pressed={isFullscreen}
                onClick={onToggleFullscreen}
            >
                <Fa icon={isFullscreen ? "fa-compress" : "fa-expand"} />
            </button>
        </div>
    );
}

function Sheet({
    activeCell,
    anchorCell,
    selectionType,
    clipboardRange,
    editingCell,
    cellValues,
    cellStyles,
    mergedCells,
    columnWidths,
    rowHeights,
    resizeGuide,
    onCellMouseDown,
    onCellMouseEnter,
    onCellDoubleClick,
    onSelectColumn,
    onSelectRow,
    onSelectAll,
    onContextMenu,
    onCellValueChange,
    onColumnResizeStart,
    onColumnAutoSize,
    onRowResizeStart,
    onRowAutoSize,
    onCommitEditMove,
    onMoveCell,
    onStopEditing,
    onCancelClipboard,
}) {
    const inputRefs = React.useRef({});
    const activeCellName = getCellName(activeCell);
    const anchorCellName = getCellName(anchorCell);
    const bounds = getRangeBounds(anchorCell, activeCell);
    const clipboardBounds = clipboardRange
        ? getRangeBounds(clipboardRange.anchorCell, clipboardRange.activeCell)
        : null;
    const rowHeaderWidth = getRowHeaderWidth(rowHeights.length);
    const sheetWidth = rowHeaderWidth + columnWidths.reduce((sum, width) => sum + width, 0);

    React.useEffect(() => {
        if (editingCell) {
            const input = inputRefs.current[editingCell];
            input?.focus();
            input?.setSelectionRange(input.value.length, input.value.length);
        }
    }, [editingCell]);

    React.useEffect(() => {
        if (!editingCell) {
            const cell = document.querySelector(`[data-cell-name="${activeCellName}"]`);
            cell?.focus({ preventScroll: true });
            if (selectionType === "cells") {
                cell?.scrollIntoView({ block: "nearest", inline: "nearest" });
            }
        }
    }, [activeCellName, editingCell, selectionType]);

    function handleEditKeyDown(event, columnIndex, rowIndex) {
        if (event.key === "Enter") {
            event.preventDefault();
            event.stopPropagation();
            onCommitEditMove(columnIndex, rowIndex + 1);
            return;
        }
        if (event.key === "Tab") {
            event.preventDefault();
            event.stopPropagation();
            onCommitEditMove(columnIndex + (event.shiftKey ? -1 : 1), rowIndex);
            return;
        }
        if (event.key === "Escape") {
            event.preventDefault();
            event.stopPropagation();
            onStopEditing();
            onCancelClipboard();
        }
    }

    function handleCellKeyDown(event, columnIndex, rowIndex) {
        if (event.ctrlKey || event.metaKey) return;

        const moves = {
            ArrowUp: [0, -1],
            ArrowDown: [0, 1],
            ArrowLeft: [-1, 0],
            ArrowRight: [1, 0],
            Enter: [0, 1],
            Tab: [event.shiftKey ? -1 : 1, 0],
        };
        const move = moves[event.key];
        if (!move) return;

        event.preventDefault();
        event.stopPropagation();
        onMoveCell(columnIndex + move[0], rowIndex + move[1], event.shiftKey);
    }

    return (
        <div className="sheet-wrap">
            <div className="sheet" role="grid" aria-label="空白試算表" style={{ width: `${sheetWidth}px` }}>
                {resizeGuide && <div className={`resize-guide ${resizeGuide.type}`} style={resizeGuide.style} />}
                <div className="sheet-row sheet-header-row" style={{ gridTemplateColumns: getSheetColumnsTemplate(columnWidths, rowHeaderWidth), height: `${DEFAULT_ROW_HEIGHT}px` }}>
                <div
                    className={`corner${selectionType === "sheet" ? " header-selected" : ""}`}
                    role="button"
                    aria-label="選取整張工作表"
                    title="選取整張工作表"
                    onMouseDown={(event) => {
                        if (event.button !== 0) return;
                        event.preventDefault();
                        onSelectAll();
                    }}
                    onContextMenu={(event) => onContextMenu(event, "sheet")}
                />
                {columns.map((column, columnIndex) => (
                    <div
                        className={`column-head${selectionType === "column" && columnIndex >= bounds.startColumn && columnIndex <= bounds.endColumn ? " header-selected" : ""}`}
                        key={column}
                        role="columnheader"
                        aria-label={`選取 ${column} 欄`}
                        aria-selected={selectionType === "column" && columnIndex >= bounds.startColumn && columnIndex <= bounds.endColumn}
                        onMouseDown={(event) => {
                            if (event.button !== 0 || event.target.closest(".column-resize-handle")) return;
                            event.preventDefault();
                            onSelectColumn(columnIndex, event.shiftKey);
                        }}
                        onContextMenu={(event) => onContextMenu(event, "column", { columnIndex })}
                    >
                        {column}
                        <span
                            className="column-resize-handle"
                            aria-label={`調整 ${column} 欄寬`}
                            onMouseDown={(event) => onColumnResizeStart(columnIndex, event)}
                            onDoubleClick={(event) => {
                                event.preventDefault();
                                onColumnAutoSize(columnIndex);
                            }}
                        />
                    </div>
                ))}
                </div>
                {rows.map((row) => (
                    <div className="sheet-row" key={row} style={{ gridTemplateColumns: getSheetColumnsTemplate(columnWidths, rowHeaderWidth), height: `${rowHeights[row - 1]}px` }}>
                        <div
                            className={`row-head${selectionType === "row" && row - 1 >= bounds.startRow && row - 1 <= bounds.endRow ? " header-selected" : ""}`}
                            role="rowheader"
                            aria-label={`選取第 ${row} 列`}
                            aria-selected={selectionType === "row" && row - 1 >= bounds.startRow && row - 1 <= bounds.endRow}
                            onMouseDown={(event) => {
                                const rect = event.currentTarget.getBoundingClientRect();
                                if (rect.bottom - event.clientY <= 8 || event.target.closest(".row-resize-handle")) {
                                    onRowResizeStart(row - 1, event);
                                    return;
                                }
                                if (event.button !== 0) return;
                                event.preventDefault();
                                onSelectRow(row - 1, event.shiftKey);
                            }}
                            onContextMenu={(event) => onContextMenu(event, "row", { rowIndex: row - 1 })}
                            onDoubleClick={(event) => {
                                const rect = event.currentTarget.getBoundingClientRect();
                                if (rect.bottom - event.clientY <= 8) onRowAutoSize(row - 1);
                            }}
                        >
                            {row}
                            <span
                                className="row-resize-handle"
                                aria-label={`調整第 ${row} 列高度`}
                                onMouseDown={(event) => onRowResizeStart(row - 1, event)}
                                onDoubleClick={(event) => {
                                    event.preventDefault();
                                    onRowAutoSize(row - 1);
                                }}
                            />
                        </div>
                        {columns.map((column, columnIndex) => {
                            const rowIndex = row - 1;
                            const cell = { columnIndex, rowIndex };
                            const cellName = `${column}${row}`;
                            const mergedRange = mergedCells.find((range) => (
                                range.rowIndex === rowIndex
                                && columnIndex >= range.startColumn
                                && columnIndex <= range.endColumn
                            ));
                            if (mergedRange && columnIndex !== mergedRange.startColumn) return null;

                            const isActive = activeCellName === cellName;
                            const isAnchor = anchorCellName === cellName;
                            const isSelected = isCellInRange(cell, anchorCell, activeCell);
                            const isClipboardCell = clipboardBounds && (
                                columnIndex >= clipboardBounds.startColumn &&
                                columnIndex <= clipboardBounds.endColumn &&
                                rowIndex >= clipboardBounds.startRow &&
                                rowIndex <= clipboardBounds.endRow
                            );
                            const isEditing = editingCell === cellName;
                            const style = mergeStyle(cellStyles[cellName]);
                            const visualEndColumn = mergedRange?.endColumn ?? columnIndex;
                            const classes = [
                                "cell",
                                isActive ? "active-cell" : "",
                                isSelected ? "range-selected" : "",
                                isAnchor ? "range-anchor" : "",
                                isSelected && rowIndex === bounds.startRow ? "range-top" : "",
                                isSelected && visualEndColumn === bounds.endColumn ? "range-right" : "",
                                isSelected && rowIndex === bounds.endRow ? "range-bottom" : "",
                                isSelected && columnIndex === bounds.startColumn ? "range-left" : "",
                                isClipboardCell ? "clipboard-range" : "",
                                isClipboardCell && rowIndex === clipboardBounds.startRow ? "clipboard-top" : "",
                                isClipboardCell && visualEndColumn === clipboardBounds.endColumn ? "clipboard-right" : "",
                                isClipboardCell && rowIndex === clipboardBounds.endRow ? "clipboard-bottom" : "",
                                isClipboardCell && columnIndex === clipboardBounds.startColumn ? "clipboard-left" : "",
                            ].filter(Boolean).join(" ");

                            return (
                                <div
                                    className={classes}
                                    key={cellName}
                                    role="gridcell"
                                    aria-label={cellName}
                                    data-cell-name={cellName}
                                    data-column-index={columnIndex}
                                    data-row-index={rowIndex}
                                    tabIndex="-1"
                                    style={{
                                        ...(mergedRange ? {
                                            gridColumn: `${columnIndex + 2} / span ${mergedRange.endColumn - mergedRange.startColumn + 1}`,
                                        } : {}),
                                    }}
                                    onMouseDown={(event) => {
                                        event.currentTarget.focus({ preventScroll: true });
                                        onCellMouseDown(
                                            cell,
                                            event.shiftKey,
                                            event,
                                            mergedRange,
                                        );
                                    }}
                                    onMouseEnter={() => onCellMouseEnter(cell)}
                                    onDoubleClick={() => onCellDoubleClick(cell)}
                                    onContextMenu={(event) => onContextMenu(event, "cell", { cell })}
                                    onKeyDown={(event) => handleCellKeyDown(event, columnIndex, rowIndex)}
                                >
                                    {!isEditing && (
                                        <div
                                            className="cell-display"
                                            style={getCellCss(style)}
                                        >
                                            {cellValues[cellName] || ""}
                                        </div>
                                    )}
                                    {isEditing && (
                                        <textarea
                                            ref={(element) => {
                                                inputRefs.current[cellName] = element;
                                            }}
                                            className="cell-input"
                                            value={cellValues[cellName] || ""}
                                            aria-label={`儲存格 ${cellName}`}
                                            rows="1"
                                            spellCheck="false"
                                            style={{ ...getCellCss(style), backgroundColor: style.fillColor || "#fff" }}
                                            onChange={(event) => onCellValueChange(cellName, event.target.value)}
                                            onKeyDown={(event) => handleEditKeyDown(event, columnIndex, rowIndex)}
                                            onBlur={onStopEditing}
                                        />
                                    )}
                                </div>
                            );
                        })}
                    </div>
                ))}
            </div>
        </div>
    );
}

function ReportHelperApp() {
    const [sheets, setSheets] = React.useState(() => [createSheet(1, "工作表1")]);
    const [activeSheetId, setActiveSheetId] = React.useState(1);
    const [editingSheetId, setEditingSheetId] = React.useState(null);
    const [editingSheetName, setEditingSheetName] = React.useState("");
    const [sheetTabMenu, setSheetTabMenu] = React.useState(null);
    const [isFullscreen, setIsFullscreen] = React.useState(false);
    const [activeCell, setActiveCell] = React.useState(firstCell);
    const [anchorCell, setAnchorCell] = React.useState(firstCell);
    const [selectionType, setSelectionType] = React.useState("cells");
    const [editingCell, setEditingCell] = React.useState(null);
    const [cellValues, setCellValues] = React.useState({});
    const [cellStyles, setCellStyles] = React.useState({});
    const [mergedCells, setMergedCells] = React.useState([]);
    const [columnWidths, setColumnWidths] = React.useState(() => columns.map(() => DEFAULT_COLUMN_WIDTH));
    const [rowHeights, setRowHeights] = React.useState(() => rows.map(() => DEFAULT_ROW_HEIGHT));
    const [resizeGuide, setResizeGuide] = React.useState(null);
    const [clipboard, setClipboard] = React.useState(null);
    const [clipboardRange, setClipboardRange] = React.useState(null);
    const [formatPainter, setFormatPainter] = React.useState(null);
    const [contextMenu, setContextMenu] = React.useState(null);
    const dragRef = React.useRef(null);
    const resizeRef = React.useRef(null);
    const sheetStateRef = React.useRef(null);
    const scrollRangeRef = React.useRef({
        beyondDefaultColumns: false,
        beyondDefaultRows: false,
    });
    const historyRef = React.useRef({
        current: null,
        undo: [],
        redo: [],
        applying: false,
    });
    columns = Array.from({ length: columnWidths.length }, (_, index) => getColumnName(index));
    rows = Array.from({ length: rowHeights.length }, (_, index) => index + 1);
    const activeCellName = getCellName(activeCell);
    const activeCellValue = cellValues[activeCellName] || "";
    const activeStyle = mergeStyle(cellStyles[activeCellName]);
    const selectedCells = getCellsInRange(anchorCell, activeCell);
    const selectedMergedRange = mergedCells.find((range) => (
        activeCell.rowIndex === range.rowIndex
        && activeCell.columnIndex >= range.startColumn
        && activeCell.columnIndex <= range.endColumn
    ));
    sheetStateRef.current = {
        cellValues,
        cellStyles,
        mergedCells,
        columnWidths,
        rowHeights,
        clipboardRange,
    };

    React.useEffect(() => {
        document.body.classList.toggle("report-fullscreen-open", isFullscreen);
        return () => document.body.classList.remove("report-fullscreen-open");
    }, [isFullscreen]);

    React.useEffect(() => {
        const history = historyRef.current;
        const snapshot = {
            cellValues,
            cellStyles,
            mergedCells,
            columnWidths,
            rowHeights,
        };

        if (!history.current) {
            history.current = snapshot;
            return;
        }

        if (history.applying) {
            history.current = snapshot;
            history.applying = false;
            return;
        }

        history.undo.push(history.current);
        if (history.undo.length > 100) history.undo.shift();
        history.current = snapshot;
        history.redo = [];
    }, [cellValues, cellStyles, mergedCells, columnWidths, rowHeights]);

    React.useEffect(() => {
        function stopDrag() {
            dragRef.current = null;
        }

        window.addEventListener("mouseup", stopDrag);
        return () => window.removeEventListener("mouseup", stopDrag);
    }, []);

    React.useEffect(() => {
        const app = document.querySelector(".excel-app");
        const sheet = document.querySelector(".sheet-wrap");
        if (!app || !sheet) return undefined;

        function handleSheetScroll() {
            const remainingRight = sheet.scrollWidth - sheet.clientWidth - sheet.scrollLeft;
            const remainingBottom = sheet.scrollHeight - sheet.clientHeight - sheet.scrollTop;
            const current = sheetStateRef.current;
            const defaultWidth = getRowHeaderWidth(INITIAL_ROW_COUNT)
                + current.columnWidths
                    .slice(0, INITIAL_COLUMN_COUNT)
                    .reduce((sum, width) => sum + width, 0);
            const defaultHeight = DEFAULT_ROW_HEIGHT
                + current.rowHeights
                    .slice(0, INITIAL_ROW_COUNT)
                    .reduce((sum, height) => sum + height, 0);
            const defaultScrollLeft = Math.max(0, defaultWidth - sheet.clientWidth);
            const defaultScrollTop = Math.max(0, defaultHeight - sheet.clientHeight);
            const scrollRange = scrollRangeRef.current;

            if (remainingRight <= GROWTH_SCROLL_THRESHOLD) expandColumns();
            if (remainingBottom <= GROWTH_SCROLL_THRESHOLD) expandRows();

            if (sheet.scrollLeft > defaultScrollLeft + 1) {
                scrollRange.beyondDefaultColumns = true;
            } else if (scrollRange.beyondDefaultColumns) {
                scrollRange.beyondDefaultColumns = false;
                trimUnusedGrid({ trimColumns: true });
            }

            if (sheet.scrollTop > defaultScrollTop + 1) {
                scrollRange.beyondDefaultRows = true;
            } else if (scrollRange.beyondDefaultRows) {
                scrollRange.beyondDefaultRows = false;
                trimUnusedGrid({ trimRows: true });
            }

            setContextMenu(null);
            setSheetTabMenu(null);
        }

        function handleWheel(event) {
            event.preventDefault();
            if (event.shiftKey && !event.deltaX) {
                sheet.scrollLeft += event.deltaY;
            } else {
                sheet.scrollTop += event.deltaY;
                sheet.scrollLeft += event.deltaX;
            }
            setContextMenu(null);
            setSheetTabMenu(null);
        }

        app.addEventListener("wheel", handleWheel, { passive: false });
        sheet.addEventListener("scroll", handleSheetScroll, { passive: true });
        return () => {
            app.removeEventListener("wheel", handleWheel);
            sheet.removeEventListener("scroll", handleSheetScroll);
        };
    }, []);

    React.useEffect(() => {
        if (!contextMenu && !sheetTabMenu) return undefined;

        function closeContextMenu(event) {
            if (!event.target.closest(".sheet-context-menu, .sheet-tab-menu")) {
                setContextMenu(null);
                setSheetTabMenu(null);
            }
        }

        function closeOnEscape(event) {
            if (event.key === "Escape") {
                setContextMenu(null);
                setSheetTabMenu(null);
            }
        }

        function closeOnResize() {
            setContextMenu(null);
            setSheetTabMenu(null);
        }

        window.addEventListener("mousedown", closeContextMenu);
        window.addEventListener("resize", closeOnResize);
        window.addEventListener("keydown", closeOnEscape);
        return () => {
            window.removeEventListener("mousedown", closeContextMenu);
            window.removeEventListener("resize", closeOnResize);
            window.removeEventListener("keydown", closeOnEscape);
        };
    }, [contextMenu, sheetTabMenu]);

    React.useEffect(() => {
        function handleResizeMove(event) {
            const resize = resizeRef.current;
            if (!resize) return;

            if (resize.type === "column") {
                const width = clamp(resize.startWidth + event.clientX - resize.startX, MIN_COLUMN_WIDTH, 600);
                setResizeGuide({
                    type: "column",
                    style: { left: `${resize.startLeft + width}px` },
                });
                resize.nextSize = width;
            } else {
                const height = clamp(resize.startHeight + event.clientY - resize.startY, MIN_ROW_HEIGHT, 300);
                setResizeGuide({
                    type: "row",
                    style: { top: `${resize.startTop + height}px` },
                });
                resize.nextSize = height;
            }
        }

        function handleResizeEnd() {
            const resize = resizeRef.current;
            if (!resize) return;

            if (resize.type === "column") {
                setColumnWidths((current) => current.map((width, index) => (
                    index === resize.index ? Math.round(resize.nextSize || resize.startWidth) : width
                )));
            } else {
                setRowHeights((current) => current.map((height, index) => (
                    index === resize.index ? Math.round(resize.nextSize || resize.startHeight) : height
                )));
            }

            resizeRef.current = null;
            setResizeGuide(null);
        }

        window.addEventListener("mousemove", handleResizeMove);
        window.addEventListener("mouseup", handleResizeEnd);
        return () => {
            window.removeEventListener("mousemove", handleResizeMove);
            window.removeEventListener("mouseup", handleResizeEnd);
        };
    }, []);

    React.useEffect(() => {
        function handleKeyDown(event) {
            const target = event.target;
            const tagName = target?.tagName;
            const isFormControl = ["INPUT", "SELECT", "TEXTAREA", "BUTTON"].includes(tagName);

            if ((event.ctrlKey || event.metaKey) && !editingCell) {
                const key = event.key.toLowerCase();
                if (key === "home") {
                    event.preventDefault();
                    moveCell(0, 0, false);
                    return;
                }
                if (key === "end") {
                    event.preventDefault();
                    moveCellToLastValuedCell();
                    return;
                }
                if (["arrowup", "arrowdown", "arrowleft", "arrowright"].includes(key)) {
                    event.preventDefault();
                    moveToNextValuedCell(key);
                    return;
                }
                if (key === "z") {
                    event.preventDefault();
                    restoreHistory(event.shiftKey ? "redo" : "undo");
                    return;
                }
                if (key === "c") {
                    event.preventDefault();
                    copySelection(false);
                }
                if (key === "x") {
                    event.preventDefault();
                    copySelection(true);
                }
                if (key === "v") {
                    event.preventDefault();
                    pasteSelection();
                }
                return;
            }

            if (event.key === "Escape" && (clipboardRange || contextMenu)) {
                event.preventDefault();
                cancelClipboardMode();
                setContextMenu(null);
                return;
            }

            if (editingCell || isFormControl) return;

            const moves = {
                ArrowUp: [0, -1],
                ArrowDown: [0, 1],
                ArrowLeft: [-1, 0],
                ArrowRight: [1, 0],
                Enter: [0, 1],
                Tab: [event.shiftKey ? -1 : 1, 0],
            };
            const move = moves[event.key];

            if (move) {
                event.preventDefault();
                moveCell(activeCell.columnIndex + move[0], activeCell.rowIndex + move[1], event.shiftKey);
                return;
            }

            if (event.key === "Delete" || event.key === "Backspace") {
                event.preventDefault();
                clearSelection();
                return;
            }

            if (isPrintableKey(event)) {
                event.preventDefault();
                const cellName = activeCellName;
                cancelClipboardMode();
                updateCellValue(cellName, event.key);
                setEditingCell(cellName);
            }
        }

        window.addEventListener("keydown", handleKeyDown);
        return () => {
            window.removeEventListener("keydown", handleKeyDown);
        };
    }, [activeCell, anchorCell, activeCellName, editingCell, cellValues, cellStyles, clipboard, clipboardRange, contextMenu]);

    function restoreHistory(direction) {
        const history = historyRef.current;
        const source = direction === "undo" ? history.undo : history.redo;
        const destination = direction === "undo" ? history.redo : history.undo;
        if (!source.length || !history.current) return;

        const snapshot = source.pop();
        destination.push(history.current);
        history.current = snapshot;
        history.applying = true;

        setEditingCell(null);
        setContextMenu(null);
        setCellValues(snapshot.cellValues);
        setCellStyles(snapshot.cellStyles);
        setMergedCells(snapshot.mergedCells || []);
        setColumnWidths(snapshot.columnWidths);
        setRowHeights(snapshot.rowHeights);
    }

    function cancelClipboardMode() {
        setClipboard(null);
        setClipboardRange(null);
    }

    function getCurrentSheetData(sheet) {
        return {
            ...sheet,
            cellValues,
            cellStyles,
            mergedCells,
            columnWidths,
            rowHeights,
        };
    }

    function resetSheetInteraction(sheet) {
        setActiveCell(firstCell);
        setAnchorCell(firstCell);
        setSelectionType("cells");
        setEditingCell(null);
        setResizeGuide(null);
        setFormatPainter(null);
        setContextMenu(null);
        setSheetTabMenu(null);
        cancelClipboardMode();
        setCellValues(sheet.cellValues);
        setCellStyles(sheet.cellStyles);
        setMergedCells(sheet.mergedCells || []);
        setColumnWidths(sheet.columnWidths);
        setRowHeights(sheet.rowHeights);
        historyRef.current = {
            current: {
                cellValues: sheet.cellValues,
                cellStyles: sheet.cellStyles,
                mergedCells: sheet.mergedCells || [],
                columnWidths: sheet.columnWidths,
                rowHeights: sheet.rowHeights,
            },
            undo: [],
            redo: [],
            applying: true,
        };
        document.querySelector(".sheet-wrap")?.scrollTo({ top: 0, left: 0 });
    }

    function selectSheet(sheetId) {
        if (sheetId === activeSheetId || editingSheetId) return;
        const targetSheet = sheets.find((sheet) => sheet.id === sheetId);
        if (!targetSheet) return;

        setSheets((current) => current.map((sheet) => (
            sheet.id === activeSheetId ? getCurrentSheetData(sheet) : sheet
        )));
        setActiveSheetId(sheetId);
        resetSheetInteraction(targetSheet);
    }

    function addSheet() {
        const usedNames = new Set(sheets.map((sheet) => sheet.name));
        let nameIndex = 1;
        while (usedNames.has(`工作表${nameIndex}`)) nameIndex += 1;

        const newSheet = createSheet(nextSheetId, `工作表${nameIndex}`);
        nextSheetId += 1;
        setSheets((current) => [
            ...current.map((sheet) => (
                sheet.id === activeSheetId ? getCurrentSheetData(sheet) : sheet
            )),
            newSheet,
        ]);
        setActiveSheetId(newSheet.id);
        resetSheetInteraction(newSheet);
    }

    function startSheetRename(sheet) {
        setSheetTabMenu(null);
        setEditingSheetId(sheet.id);
        setEditingSheetName(sheet.name);
    }

    function commitSheetRename(cancel = false) {
        if (!editingSheetId) return;
        if (!cancel) {
            const nextName = editingSheetName.trim();
            if (nextName) {
                setSheets((current) => current.map((sheet) => (
                    sheet.id === editingSheetId ? { ...sheet, name: nextName } : sheet
                )));
            }
        }
        setEditingSheetId(null);
        setEditingSheetName("");
    }

    function openSheetTabMenu(event, sheetId) {
        event.preventDefault();
        event.stopPropagation();
        setContextMenu(null);
        setSheetTabMenu({
            sheetId,
            x: Math.max(8, Math.min(event.clientX, window.innerWidth - 180)),
            y: Math.max(8, Math.min(event.clientY, window.innerHeight - 60)),
        });
    }

    function deleteSheet(sheetId) {
        if (sheets.length <= 1) return;
        const deleteIndex = sheets.findIndex((sheet) => sheet.id === sheetId);
        if (deleteIndex < 0) return;

        if (sheetId === activeSheetId) {
            const targetSheet = sheets[deleteIndex > 0 ? deleteIndex - 1 : 1];
            setSheets((current) => current.filter((sheet) => sheet.id !== sheetId));
            setActiveSheetId(targetSheet.id);
            resetSheetInteraction(targetSheet);
        } else {
            setSheets((current) => current
                .map((sheet) => (
                    sheet.id === activeSheetId ? getCurrentSheetData(sheet) : sheet
                ))
                .filter((sheet) => sheet.id !== sheetId));
        }
        setSheetTabMenu(null);
    }

    function updateCellValue(cellName, value) {
        setCellValues((current) => ({ ...current, [cellName]: value }));
    }

    function clearSelection() {
        setCellValues((current) => {
            const next = { ...current };
            selectedCells.forEach((cellName) => {
                delete next[cellName];
            });
            return next;
        });
    }

    function updateSelectedStyles(updater) {
        setCellStyles((current) => {
            const next = { ...current };
            selectedCells.forEach((cellName) => {
                next[cellName] = updater(mergeStyle(next[cellName]), cellName);
            });
            return next;
        });
    }

    function setSelectedCellsStyle(partialStyle) {
        updateSelectedStyles((style) => ({ ...style, ...partialStyle }));
    }

    function expandColumns(minimumCount = 0) {
        setColumnWidths((current) => {
            if (current.length >= MAX_COLUMN_COUNT) return current;
            const nextCount = Math.min(
                MAX_COLUMN_COUNT,
                Math.max(current.length + COLUMN_GROWTH_BATCH, minimumCount),
            );
            if (nextCount <= current.length) return current;
            return [
                ...current,
                ...Array(nextCount - current.length).fill(DEFAULT_COLUMN_WIDTH),
            ];
        });
    }

    function expandRows(minimumCount = 0) {
        setRowHeights((current) => {
            if (current.length >= MAX_ROW_COUNT) return current;
            const nextCount = Math.min(
                MAX_ROW_COUNT,
                Math.max(current.length + ROW_GROWTH_BATCH, minimumCount),
            );
            if (nextCount <= current.length) return current;
            return [
                ...current,
                ...Array(nextCount - current.length).fill(DEFAULT_ROW_HEIGHT),
            ];
        });
    }

    function trimUnusedGrid({ trimColumns = false, trimRows = false }) {
        const current = sheetStateRef.current;
        if (!current) return;

        let lastUsedColumn = INITIAL_COLUMN_COUNT - 1;
        let lastUsedRow = INITIAL_ROW_COUNT - 1;
        const cellNames = new Set([
            ...Object.keys(current.cellValues),
            ...Object.keys(current.cellStyles),
        ]);

        cellNames.forEach((cellName) => {
            const hasValue = String(current.cellValues[cellName] ?? "").length > 0;
            const hasStyle = Boolean(current.cellStyles[cellName]);
            if (!hasValue && !hasStyle) return;
            const cell = getCellFromName(cellName);
            lastUsedColumn = Math.max(lastUsedColumn, cell.columnIndex);
            lastUsedRow = Math.max(lastUsedRow, cell.rowIndex);
        });

        current.columnWidths.forEach((width, index) => {
            if (width !== DEFAULT_COLUMN_WIDTH) lastUsedColumn = Math.max(lastUsedColumn, index);
        });
        current.rowHeights.forEach((height, index) => {
            if (height !== DEFAULT_ROW_HEIGHT) lastUsedRow = Math.max(lastUsedRow, index);
        });
        current.mergedCells.forEach((range) => {
            lastUsedColumn = Math.max(lastUsedColumn, range.endColumn);
            lastUsedRow = Math.max(lastUsedRow, range.rowIndex);
        });

        if (current.clipboardRange) {
            const clipboardBounds = getRangeBounds(
                current.clipboardRange.anchorCell,
                current.clipboardRange.activeCell,
            );
            lastUsedColumn = Math.max(lastUsedColumn, clipboardBounds.endColumn);
            lastUsedRow = Math.max(lastUsedRow, clipboardBounds.endRow);
        }

        const nextColumnWidths = trimColumns
            ? current.columnWidths.slice(0, lastUsedColumn + 1)
            : current.columnWidths;
        const nextRowHeights = trimRows
            ? current.rowHeights.slice(0, lastUsedRow + 1)
            : current.rowHeights;
        const columnsChanged = nextColumnWidths.length !== current.columnWidths.length;
        const rowsChanged = nextRowHeights.length !== current.rowHeights.length;
        if (!columnsChanged && !rowsChanged) return;

        const nextSnapshot = {
            cellValues: current.cellValues,
            cellStyles: current.cellStyles,
            mergedCells: current.mergedCells,
            columnWidths: nextColumnWidths,
            rowHeights: nextRowHeights,
        };
        historyRef.current = {
            current: nextSnapshot,
            undo: [],
            redo: [],
            applying: true,
        };

        if (columnsChanged) setColumnWidths(nextColumnWidths);
        if (rowsChanged) setRowHeights(nextRowHeights);
        setActiveCell((cell) => ({
            columnIndex: Math.min(cell.columnIndex, nextColumnWidths.length - 1),
            rowIndex: Math.min(cell.rowIndex, nextRowHeights.length - 1),
        }));
        setAnchorCell((cell) => ({
            columnIndex: Math.min(cell.columnIndex, nextColumnWidths.length - 1),
            rowIndex: Math.min(cell.rowIndex, nextRowHeights.length - 1),
        }));
    }

    function moveCell(columnIndex, rowIndex, extendSelection = false) {
        const nextColumnIndex = clamp(columnIndex, 0, MAX_COLUMN_COUNT - 1);
        const nextRowIndex = clamp(rowIndex, 0, MAX_ROW_COUNT - 1);

        if (nextColumnIndex >= columnWidths.length) expandColumns(nextColumnIndex + 1);
        if (nextRowIndex >= rowHeights.length) expandRows(nextRowIndex + 1);

        let nextCell = {
            columnIndex: nextColumnIndex,
            rowIndex: nextRowIndex,
        };
        const mergedRange = mergedCells.find((range) => (
            range.rowIndex === nextCell.rowIndex
            && nextCell.columnIndex >= range.startColumn
            && nextCell.columnIndex <= range.endColumn
        ));
        if (mergedRange) {
            nextCell = {
                columnIndex: mergedRange.startColumn,
                rowIndex: mergedRange.rowIndex,
            };
        }

        setSelectionType("cells");
        if (!extendSelection) {
            setAnchorCell(mergedRange
                ? { columnIndex: mergedRange.endColumn, rowIndex: mergedRange.rowIndex }
                : nextCell);
            setActiveCell(nextCell);
            return;
        }
        setActiveCell(nextCell);
    }

    function getValuedCells() {
        return Object.entries(cellValues)
            .filter(([, value]) => value !== "" && value !== null && value !== undefined)
            .map(([cellName]) => getCellFromName(cellName));
    }

    function moveCellToLastValuedCell() {
        const valuedCells = getValuedCells();
        if (!valuedCells.length) {
            moveCell(0, 0, false);
            return;
        }

        const lastCell = valuedCells.reduce((last, cell) => {
            if (cell.rowIndex > last.rowIndex) return cell;
            if (cell.rowIndex === last.rowIndex && cell.columnIndex > last.columnIndex) return cell;
            return last;
        });
        moveCell(lastCell.columnIndex, lastCell.rowIndex, false);
    }

    function moveToNextValuedCell(direction) {
        const directionMap = {
            arrowup: { columnDelta: 0, rowDelta: -1 },
            arrowdown: { columnDelta: 0, rowDelta: 1 },
            arrowleft: { columnDelta: -1, rowDelta: 0 },
            arrowright: { columnDelta: 1, rowDelta: 0 },
        };
        const { columnDelta, rowDelta } = directionMap[direction];
        const candidates = getValuedCells().filter((cell) => {
            if (columnDelta) {
                return cell.rowIndex === activeCell.rowIndex
                    && Math.sign(cell.columnIndex - activeCell.columnIndex) === columnDelta;
            }
            return cell.columnIndex === activeCell.columnIndex
                && Math.sign(cell.rowIndex - activeCell.rowIndex) === rowDelta;
        });

        const nextCell = candidates.reduce((nearest, cell) => {
            const distance = Math.abs(cell.columnIndex - activeCell.columnIndex)
                + Math.abs(cell.rowIndex - activeCell.rowIndex);
            if (!nearest || distance < nearest.distance) return { cell, distance };
            return nearest;
        }, null)?.cell;

        const fallbackColumnIndex = columnDelta < 0
            ? 0
            : activeCell.columnIndex + columnDelta;
        const fallbackRowIndex = rowDelta < 0
            ? 0
            : activeCell.rowIndex + rowDelta;

        moveCell(
            nextCell?.columnIndex ?? fallbackColumnIndex,
            nextCell?.rowIndex ?? fallbackRowIndex,
            false,
        );
    }

    function selectCell(cell, extendSelection = false) {
        setEditingCell(null);
        setSelectionType("cells");
        if (!extendSelection) setAnchorCell(cell);
        setActiveCell(cell);

        if (formatPainter) {
            const targetCells = getCellsInRange(extendSelection ? anchorCell : cell, cell);
            setCellStyles((current) => {
                const next = { ...current };
                targetCells.forEach((cellName) => {
                    next[cellName] = { ...formatPainter };
                });
                return next;
            });
            setFormatPainter(null);
        }
    }

    function handleCellMouseDown(cell, extendSelection, event, mergedRange = null) {
        event.preventDefault();
        if (mergedRange && !extendSelection) {
            setEditingCell(null);
            setSelectionType("cells");
            setAnchorCell({
                columnIndex: mergedRange.endColumn,
                rowIndex: mergedRange.rowIndex,
            });
            setActiveCell({
                columnIndex: mergedRange.startColumn,
                rowIndex: mergedRange.rowIndex,
            });
            return;
        }
        selectCell(cell, extendSelection);
        dragRef.current = {
            mode: "select",
            anchor: extendSelection ? anchorCell : cell,
        };
    }

    function handleCellMouseEnter(cell) {
        const drag = dragRef.current;
        if (!drag) return;
        setAnchorCell(drag.anchor);
        setActiveCell(cell);
    }

    function selectColumn(columnIndex, extendSelection = false) {
        const anchorColumn = extendSelection && selectionType === "column"
            ? anchorCell.columnIndex
            : columnIndex;
        setEditingCell(null);
        setSelectionType("column");
        setAnchorCell({ columnIndex: anchorColumn, rowIndex: 0 });
        setActiveCell({ columnIndex, rowIndex: rows.length - 1 });
    }

    function selectRow(rowIndex, extendSelection = false) {
        const anchorRow = extendSelection && selectionType === "row"
            ? anchorCell.rowIndex
            : rowIndex;
        setEditingCell(null);
        setSelectionType("row");
        setAnchorCell({ columnIndex: 0, rowIndex: anchorRow });
        setActiveCell({ columnIndex: columns.length - 1, rowIndex });
    }

    function selectAll() {
        setEditingCell(null);
        setSelectionType("sheet");
        setAnchorCell(firstCell);
        setActiveCell({ columnIndex: columns.length - 1, rowIndex: rows.length - 1 });
    }

    function openContextMenu(event, targetType, target = {}) {
        event.preventDefault();
        event.stopPropagation();
        const bounds = getRangeBounds(anchorCell, activeCell);

        if (targetType === "cell") {
            if (selectionType !== "cells" || !isCellInRange(target.cell, anchorCell, activeCell)) {
                selectCell(target.cell, false);
            }
        } else if (targetType === "column") {
            const isSelectedColumn = selectionType === "column"
                && target.columnIndex >= bounds.startColumn
                && target.columnIndex <= bounds.endColumn;
            if (!isSelectedColumn) selectColumn(target.columnIndex, false);
        } else if (targetType === "row") {
            const isSelectedRow = selectionType === "row"
                && target.rowIndex >= bounds.startRow
                && target.rowIndex <= bounds.endRow;
            if (!isSelectedRow) selectRow(target.rowIndex, false);
        } else if (targetType === "sheet" && selectionType !== "sheet") {
            selectAll();
        }

        setContextMenu({
            x: Math.max(8, Math.min(event.clientX, window.innerWidth - 230)),
            y: Math.max(8, Math.min(event.clientY, window.innerHeight - 190)),
        });
    }

    function getColumnLeft(columnIndex) {
        return getRowHeaderWidth(rowHeights.length)
            + columnWidths.slice(0, columnIndex).reduce((sum, width) => sum + width, 0);
    }

    function getRowTop(rowIndex) {
        return DEFAULT_ROW_HEIGHT + rowHeights.slice(0, rowIndex).reduce((sum, height) => sum + height, 0);
    }

    function handleColumnResizeStart(columnIndex, event) {
        event.preventDefault();
        event.stopPropagation();
        resizeRef.current = {
            type: "column",
            index: columnIndex,
            startX: event.clientX,
            startWidth: columnWidths[columnIndex],
            startLeft: getColumnLeft(columnIndex),
            nextSize: columnWidths[columnIndex],
        };
        setResizeGuide({
            type: "column",
            style: { left: `${getColumnLeft(columnIndex) + columnWidths[columnIndex]}px` },
        });
    }

    function handleRowResizeStart(rowIndex, event) {
        event.preventDefault();
        event.stopPropagation();
        resizeRef.current = {
            type: "row",
            index: rowIndex,
            startY: event.clientY,
            startHeight: rowHeights[rowIndex],
            startTop: getRowTop(rowIndex),
            nextSize: rowHeights[rowIndex],
        };
        setResizeGuide({
            type: "row",
            style: { top: `${getRowTop(rowIndex) + rowHeights[rowIndex]}px` },
        });
    }

    function autoSizeColumn(columnIndex) {
        const columnName = columns[columnIndex];
        const headerWidth = columnName.length * 14 + 24;
        const contentWidth = rows.reduce((max, row, rowIndex) => {
            const cellName = getCellName({ columnIndex, rowIndex });
            return Math.max(max, estimateTextWidth(cellValues[cellName], cellStyles[cellName]));
        }, headerWidth);
        setColumnWidths((current) => current.map((width, index) => (
            index === columnIndex ? clamp(contentWidth, MIN_COLUMN_WIDTH, 600) : width
        )));
    }

    function autoSizeRow(rowIndex) {
        const contentHeight = columns.reduce((max, column, columnIndex) => {
            const cellName = getCellName({ columnIndex, rowIndex });
            return Math.max(max, estimateTextHeight(cellValues[cellName], cellStyles[cellName]));
        }, DEFAULT_ROW_HEIGHT);
        setRowHeights((current) => current.map((height, index) => (
            index === rowIndex ? clamp(contentHeight, MIN_ROW_HEIGHT, 300) : height
        )));
    }

    function startEditing(cell) {
        const cellName = getCellName(cell);
        cancelClipboardMode();
        setAnchorCell(cell);
        setActiveCell(cell);
        setEditingCell(cellName);
    }

    function copySelection(cut = false) {
        const bounds = getRangeBounds(anchorCell, activeCell);
        const data = [];

        for (let rowIndex = bounds.startRow; rowIndex <= bounds.endRow; rowIndex += 1) {
            const rowData = [];
            for (let columnIndex = bounds.startColumn; columnIndex <= bounds.endColumn; columnIndex += 1) {
                const cellName = getCellName({ columnIndex, rowIndex });
                rowData.push({
                    value: cellValues[cellName] || "",
                    style: mergeStyle(cellStyles[cellName]),
                });
            }
            data.push(rowData);
        }

        setClipboard({ data });
        setClipboardRange({
            anchorCell: { ...anchorCell },
            activeCell: { ...activeCell },
        });

        if (cut) clearSelection();
    }

    function pasteSelection() {
        if (!clipboard) return;

        setCellValues((current) => {
            const next = { ...current };
            clipboard.data.forEach((rowData, rowOffset) => {
                rowData.forEach((cellData, columnOffset) => {
                    const columnIndex = activeCell.columnIndex + columnOffset;
                    const rowIndex = activeCell.rowIndex + rowOffset;
                    if (columnIndex >= columns.length || rowIndex >= rows.length) return;
                    next[getCellName({ columnIndex, rowIndex })] = cellData.value;
                });
            });
            return next;
        });

        setCellStyles((current) => {
            const next = { ...current };
            clipboard.data.forEach((rowData, rowOffset) => {
                rowData.forEach((cellData, columnOffset) => {
                    const columnIndex = activeCell.columnIndex + columnOffset;
                    const rowIndex = activeCell.rowIndex + rowOffset;
                    if (columnIndex >= columns.length || rowIndex >= rows.length) return;
                    next[getCellName({ columnIndex, rowIndex })] = { ...cellData.style };
                });
            });
            return next;
        });

    }

    function transformCellMap(current, transform) {
        const next = {};
        Object.entries(current).forEach(([cellName, value]) => {
            const cell = getCellFromName(cellName);
            const nextCell = transform(cell);
            if (!nextCell) return;
            if (
                nextCell.columnIndex < 0 ||
                nextCell.columnIndex >= columns.length ||
                nextCell.rowIndex < 0 ||
                nextCell.rowIndex >= rows.length
            ) return;
            next[getCellName(nextCell)] = value;
        });
        return next;
    }

    function insertSelection() {
        const bounds = getRangeBounds(anchorCell, activeCell);
        let transform;

        if (selectionType === "row") {
            const count = bounds.endRow - bounds.startRow + 1;
            transform = (cell) => (
                cell.rowIndex >= bounds.startRow
                    ? { ...cell, rowIndex: cell.rowIndex + count }
                    : cell
            );
            setMergedCells((current) => current.map((range) => (
                range.rowIndex >= bounds.startRow
                    ? { ...range, rowIndex: range.rowIndex + count }
                    : range
            )));
            setRowHeights((current) => {
                const inserted = Array(count).fill(DEFAULT_ROW_HEIGHT);
                return [
                    ...current.slice(0, bounds.startRow),
                    ...inserted,
                    ...current.slice(bounds.startRow, rows.length - count),
                ];
            });
        } else if (selectionType === "column") {
            const count = bounds.endColumn - bounds.startColumn + 1;
            transform = (cell) => {
                const expandingRange = mergedCells.find((range) => (
                    range.rowIndex === cell.rowIndex
                    && cell.columnIndex === range.startColumn
                    && bounds.startColumn >= range.startColumn
                    && bounds.startColumn <= range.endColumn
                ));
                if (expandingRange) return cell;
                return cell.columnIndex >= bounds.startColumn
                    ? { ...cell, columnIndex: cell.columnIndex + count }
                    : cell;
            };
            setMergedCells((current) => current.map((range) => {
                if (bounds.startColumn > range.endColumn) return range;
                if (bounds.startColumn < range.startColumn) {
                    return {
                        ...range,
                        startColumn: range.startColumn + count,
                        endColumn: range.endColumn + count,
                    };
                }
                return {
                    ...range,
                    endColumn: range.endColumn + count,
                };
            }));
            setColumnWidths((current) => {
                const inserted = Array(count).fill(DEFAULT_COLUMN_WIDTH);
                return [
                    ...current.slice(0, bounds.startColumn),
                    ...inserted,
                    ...current.slice(bounds.startColumn, columns.length - count),
                ];
            });
        } else {
            const count = bounds.endRow - bounds.startRow + 1;
            transform = (cell) => (
                cell.columnIndex >= bounds.startColumn &&
                cell.columnIndex <= bounds.endColumn &&
                cell.rowIndex >= bounds.startRow
                    ? { ...cell, rowIndex: cell.rowIndex + count }
                    : cell
            );
        }

        setCellValues((current) => transformCellMap(current, transform));
        setCellStyles((current) => transformCellMap(current, transform));
    }

    function deleteSelection() {
        const bounds = getRangeBounds(anchorCell, activeCell);
        let transform;

        if (selectionType === "row") {
            const count = bounds.endRow - bounds.startRow + 1;
            transform = (cell) => {
                if (cell.rowIndex >= bounds.startRow && cell.rowIndex <= bounds.endRow) return null;
                return cell.rowIndex > bounds.endRow
                    ? { ...cell, rowIndex: cell.rowIndex - count }
                    : cell;
            };
            setRowHeights((current) => [
                ...current.slice(0, bounds.startRow),
                ...current.slice(bounds.endRow + 1),
                ...Array(count).fill(DEFAULT_ROW_HEIGHT),
            ]);
        } else if (selectionType === "column") {
            const count = bounds.endColumn - bounds.startColumn + 1;
            transform = (cell) => {
                if (cell.columnIndex >= bounds.startColumn && cell.columnIndex <= bounds.endColumn) return null;
                return cell.columnIndex > bounds.endColumn
                    ? { ...cell, columnIndex: cell.columnIndex - count }
                    : cell;
            };
            setColumnWidths((current) => [
                ...current.slice(0, bounds.startColumn),
                ...current.slice(bounds.endColumn + 1),
                ...Array(count).fill(DEFAULT_COLUMN_WIDTH),
            ]);
        } else {
            const count = bounds.endRow - bounds.startRow + 1;
            transform = (cell) => {
                const inSelectedColumns = cell.columnIndex >= bounds.startColumn && cell.columnIndex <= bounds.endColumn;
                if (!inSelectedColumns) return cell;
                if (cell.rowIndex >= bounds.startRow && cell.rowIndex <= bounds.endRow) return null;
                return cell.rowIndex > bounds.endRow
                    ? { ...cell, rowIndex: cell.rowIndex - count }
                    : cell;
            };
        }

        setCellValues((current) => transformCellMap(current, transform));
        setCellStyles((current) => transformCellMap(current, transform));
    }

    function toggleMergeCenter() {
        if (selectionType !== "cells") return;

        const bounds = getRangeBounds(anchorCell, activeCell);
        const overlappingRanges = mergedCells.filter((range) => (
            range.rowIndex >= bounds.startRow
            && range.rowIndex <= bounds.endRow
            && range.endColumn >= bounds.startColumn
            && range.startColumn <= bounds.endColumn
        ));

        if (overlappingRanges.length) {
            const overlappingSet = new Set(overlappingRanges);
            setMergedCells((current) => current.filter((range) => !overlappingSet.has(range)));
            return;
        }

        if (bounds.startColumn === bounds.endColumn) return;

        const nextRanges = [];
        const anchorCellNames = [];
        for (let rowIndex = bounds.startRow; rowIndex <= bounds.endRow; rowIndex += 1) {
            nextRanges.push({
                rowIndex,
                startColumn: bounds.startColumn,
                endColumn: bounds.endColumn,
            });
            anchorCellNames.push(getCellName({
                columnIndex: bounds.startColumn,
                rowIndex,
            }));
        }

        setMergedCells((current) => [...current, ...nextRanges]);
        setCellValues((current) => {
            const next = { ...current };
            for (let rowIndex = bounds.startRow; rowIndex <= bounds.endRow; rowIndex += 1) {
                for (let columnIndex = bounds.startColumn + 1; columnIndex <= bounds.endColumn; columnIndex += 1) {
                    delete next[getCellName({ columnIndex, rowIndex })];
                }
            }
            return next;
        });
        setCellStyles((current) => {
            const next = { ...current };
            anchorCellNames.forEach((cellName) => {
                next[cellName] = {
                    ...mergeStyle(next[cellName]),
                    textAlign: "center",
                };
            });
            return next;
        });
        setAnchorCell({
            columnIndex: bounds.endColumn,
            rowIndex: bounds.endRow,
        });
        setActiveCell({
            columnIndex: bounds.startColumn,
            rowIndex: bounds.startRow,
        });
    }

    function handleContextCommand(command) {
        const commandMap = {
            cut: () => copySelection(true),
            copy: () => copySelection(false),
            insert: insertSelection,
            delete: deleteSelection,
        };
        commandMap[command]?.();
        setContextMenu(null);
    }

    function handleCommand(command) {
        const commandMap = {
            paste: pasteSelection,
            cut: () => copySelection(true),
            copy: () => copySelection(false),
            formatPainter: () => setFormatPainter(mergeStyle(cellStyles[activeCellName])),
            bold: () => updateSelectedStyles((style) => ({ ...style, bold: !activeStyle.bold })),
            italic: () => updateSelectedStyles((style) => ({ ...style, italic: !activeStyle.italic })),
            underline: () => updateSelectedStyles((style) => ({ ...style, underline: !activeStyle.underline })),
            fillColor: () => updateSelectedStyles((style) => ({ ...style, fillColor: activeStyle.fillColor ? "" : "#fff2cc" })),
            fontColor: () => updateSelectedStyles((style) => ({ ...style, fontColor: activeStyle.fontColor ? "" : "#c00000" })),
            increaseFont: () => setSelectedCellsStyle({ fontSize: clamp(activeStyle.fontSize + 1, 8, 36) }),
            decreaseFont: () => setSelectedCellsStyle({ fontSize: clamp(activeStyle.fontSize - 1, 8, 36) }),
            alignLeft: () => setSelectedCellsStyle({ textAlign: "left" }),
            alignCenter: () => setSelectedCellsStyle({ textAlign: "center" }),
            alignRight: () => setSelectedCellsStyle({ textAlign: "right" }),
            alignTop: () => setSelectedCellsStyle({ verticalAlign: "top" }),
            alignMiddle: () => setSelectedCellsStyle({ verticalAlign: "middle" }),
            alignBottom: () => setSelectedCellsStyle({ verticalAlign: "bottom" }),
            mergeCenter: toggleMergeCenter,
        };

        commandMap[command]?.();
    }

    return (
        <div className={`excel-app${isFullscreen ? " fullscreen" : ""}`}>
            <Ribbon
                activeStyle={activeStyle}
                formatPainter={Boolean(formatPainter)}
                isMergedSelection={Boolean(selectedMergedRange)}
                onCommand={handleCommand}
                onFontFamilyChange={(fontFamily) => setSelectedCellsStyle({ fontFamily })}
                onFontSizeChange={(fontSize) => setSelectedCellsStyle({ fontSize })}
            />
            <FormulaBar
                activeCellName={activeCellName}
                activeCellValue={activeCellValue}
                onValueChange={updateCellValue}
                onEditStart={cancelClipboardMode}
            />
            <Sheet
                activeCell={activeCell}
                anchorCell={anchorCell}
                selectionType={selectionType}
                clipboardRange={clipboardRange}
                editingCell={editingCell}
                cellValues={cellValues}
                cellStyles={cellStyles}
                mergedCells={mergedCells}
                columnWidths={columnWidths}
                rowHeights={rowHeights}
                resizeGuide={resizeGuide}
                onCellMouseDown={handleCellMouseDown}
                onCellMouseEnter={handleCellMouseEnter}
                onCellDoubleClick={startEditing}
                onSelectColumn={selectColumn}
                onSelectRow={selectRow}
                onSelectAll={selectAll}
                onContextMenu={openContextMenu}
                onCellValueChange={updateCellValue}
                onColumnResizeStart={handleColumnResizeStart}
                onColumnAutoSize={autoSizeColumn}
                onRowResizeStart={handleRowResizeStart}
                onRowAutoSize={autoSizeRow}
                onCommitEditMove={(columnIndex, rowIndex) => {
                    setEditingCell(null);
                    moveCell(columnIndex, rowIndex, false);
                }}
                onMoveCell={moveCell}
                onStopEditing={() => setEditingCell(null)}
                onCancelClipboard={cancelClipboardMode}
            />
            <SheetTabs
                sheets={sheets}
                activeSheetId={activeSheetId}
                editingSheetId={editingSheetId}
                editingName={editingSheetName}
                onAdd={addSheet}
                onSelect={selectSheet}
                onRenameStart={startSheetRename}
                onRenameChange={setEditingSheetName}
                onRenameCommit={commitSheetRename}
                onContextMenu={openSheetTabMenu}
                isFullscreen={isFullscreen}
                onToggleFullscreen={() => setIsFullscreen((current) => !current)}
            />
            <ContextMenu
                menu={contextMenu}
                canInsertDelete={selectionType !== "sheet"}
                onCommand={handleContextCommand}
            />
            <SheetTabMenu
                menu={sheetTabMenu}
                canDelete={sheets.length > 1}
                onDelete={() => deleteSheet(sheetTabMenu?.sheetId)}
            />
        </div>
    );
}

ReactDOM.createRoot(document.getElementById("report-helper-root")).render(<ReportHelperApp />);
