import { PointRange } from './point-range';
import * as Matrix from './matrix';
import { RangeSelection, EntireColumnsSelection, EntireRowsSelection, EmptySelection, } from './selection';
import { createFormulaParser, Model } from './engine';
import { convertPtToPx } from './util';
export function applyBoldToCells(currentData, selectedRange, editable) {
    if (!selectedRange) {
        return currentData;
    }
    const { start, end } = selectedRange;
    let updatedData = currentData;
    let anyBold = false;
    for (let row = start.row; row <= end.row; row++) {
        for (let col = start.column; col <= end.column; col++) {
            const currentCell = Matrix.get({ row, column: col }, updatedData);
            if (!currentCell || !editable)
                continue;
            if (currentCell.readOnly)
                continue;
            if (currentCell.style?.fontWeight === 'bold') {
                anyBold = true;
                break;
            }
        }
        if (anyBold)
            break;
    }
    for (let row = start.row; row <= end.row; row++) {
        for (let col = start.column; col <= end.column; col++) {
            const currentCell = Matrix.get({ row, column: col }, updatedData);
            if (!currentCell || !editable)
                continue;
            if (currentCell.readOnly)
                continue;
            const updatedCell = {
                ...currentCell,
                style: {
                    ...currentCell.style,
                    fontWeight: anyBold ? 'normal' : 'bold',
                },
            };
            updatedData = Matrix.set({ row, column: col }, updatedCell, updatedData);
        }
    }
    return updatedData;
}
export function applyItalicToCells(currentData, selectedRange, editable) {
    if (!selectedRange) {
        return currentData;
    }
    const { start, end } = selectedRange;
    let updatedData = currentData;
    let anyItalic = false;
    for (let row = start.row; row <= end.row; row++) {
        for (let col = start.column; col <= end.column; col++) {
            const currentCell = Matrix.get({ row, column: col }, updatedData);
            if (!currentCell || !editable)
                continue;
            if (currentCell.readOnly)
                continue;
            if (currentCell.style?.fontStyle === 'italic') {
                anyItalic = true;
                break;
            }
        }
        if (anyItalic)
            break;
    }
    for (let row = start.row; row <= end.row; row++) {
        for (let col = start.column; col <= end.column; col++) {
            const currentCell = Matrix.get({ row, column: col }, updatedData);
            if (!currentCell || !editable)
                continue;
            if (currentCell.readOnly)
                continue;
            const updatedCell = {
                ...currentCell,
                style: {
                    ...currentCell.style,
                    fontStyle: anyItalic ? 'normal' : 'italic',
                },
            };
            updatedData = Matrix.set({ row, column: col }, updatedCell, updatedData);
        }
    }
    return updatedData;
}
export function applyFontFamily(currentData, selectedRange, value, editable) {
    if (!selectedRange) {
        return currentData;
    }
    const { start, end } = selectedRange;
    let updatedData = currentData;
    for (let row = start.row; row <= end.row; row++) {
        for (let col = start.column; col <= end.column; col++) {
            const currentCell = Matrix.get({ row, column: col }, updatedData);
            if (!currentCell || !editable)
                continue;
            if (currentCell.readOnly)
                continue;
            let updatedCell = {
                ...currentCell,
                style: {
                    ...currentCell.style,
                    fontFamily: value,
                },
            };
            updatedData = Matrix.set({ row, column: col }, updatedCell, updatedData);
        }
    }
    return updatedData;
}
export function applyFontSize(currentData, selectedRange, value, editable) {
    if (!selectedRange) {
        return currentData;
    }
    const conversion = Number(value);
    if (isNaN(conversion)) {
        return currentData;
    }
    const fontSizeInPx = convertPtToPx(conversion);
    const { start, end } = selectedRange;
    let updatedData = currentData;
    for (let row = start.row; row <= end.row; row++) {
        for (let col = start.column; col <= end.column; col++) {
            const currentCell = Matrix.get({ row, column: col }, updatedData);
            if (!currentCell || !editable)
                continue;
            if (currentCell.readOnly)
                continue;
            let updatedCell = {
                ...currentCell,
                style: {
                    ...currentCell.style,
                    fontSize: fontSizeInPx,
                },
            };
            updatedData = Matrix.set({ row, column: col }, updatedCell, updatedData);
        }
    }
    return updatedData;
}
export function applyBorderToCells(currentData, selectedRange, value, color, editable) {
    if (!selectedRange) {
        return currentData;
    }
    let updatedData = currentData;
    const { start, end } = selectedRange;
    for (let row = start.row; row <= end.row; row++) {
        for (let col = start.column; col <= end.column; col++) {
            const currentCell = Matrix.get({ row, column: col }, updatedData);
            if (!currentCell || !editable)
                continue;
            if (currentCell.readOnly)
                continue;
            let updatedCell = { ...currentCell };
            switch (value) {
                case 'border-all-sides':
                    updatedCell.style = {
                        ...currentCell.style,
                        borderRight: `2px solid ${color}`,
                        borderLeft: `2px solid ${color}`,
                        borderTop: `2px solid ${color}`,
                        borderBottom: `2px solid ${color}`,
                    };
                    break;
                case 'border-none':
                    updatedCell.style = {
                        ...currentCell.style,
                        borderRight: `1px solid var(--excel-header-border)`,
                        borderLeft: `1px solid var(--excel-header-border)`,
                        borderTop: `1px solid var(--excel-header-border)`,
                        borderBottom: `1px solid var(--excel-header-border)`,
                    };
                    break;
                case 'border-right':
                    updatedCell.style = {
                        ...currentCell.style,
                        borderRight: `2px solid ${color}`,
                    };
                    break;
                case 'border-left':
                    updatedCell.style = {
                        ...currentCell.style,
                        borderLeft: `2px solid ${color}`,
                    };
                    break;
                case 'border-top':
                    updatedCell.style = {
                        ...currentCell.style,
                        borderTop: `2px solid ${color}`,
                    };
                    break;
                case 'border-bottom':
                    updatedCell.style = {
                        ...currentCell.style,
                        borderBottom: `2px solid ${color}`,
                    };
                    break;
                default:
                    break;
            }
            updatedData = Matrix.set({ row, column: col }, updatedCell, updatedData);
        }
    }
    return updatedData;
}
export function applyTextAlign(currentData, selectedRange, value, editable) {
    if (!selectedRange) {
        return currentData;
    }
    const { start, end } = selectedRange;
    let updatedData = currentData;
    for (let row = start.row; row <= end.row; row++) {
        for (let col = start.column; col <= end.column; col++) {
            const currentCell = Matrix.get({ row, column: col }, updatedData);
            if (!currentCell || !editable)
                continue;
            if (currentCell.readOnly)
                continue;
            let updatedCell = {
                ...currentCell,
                style: {
                    ...currentCell.style,
                    textAlign: value,
                },
            };
            updatedData = Matrix.set({ row, column: col }, updatedCell, updatedData);
        }
    }
    return updatedData;
}
export function applyUnderlineToCells(currentData, selectedRange, editable) {
    if (!selectedRange) {
        return currentData;
    }
    const { start, end } = selectedRange;
    let updatedData = currentData;
    let anyUnderlined = false;
    for (let row = start.row; row <= end.row; row++) {
        for (let col = start.column; col <= end.column; col++) {
            const currentCell = Matrix.get({ row, column: col }, updatedData);
            if (!currentCell || !editable)
                continue;
            if (currentCell.readOnly)
                continue;
            if (currentCell.style?.textDecoration === 'underline') {
                anyUnderlined = true;
                break;
            }
        }
        if (anyUnderlined)
            break;
    }
    for (let row = start.row; row <= end.row; row++) {
        for (let col = start.column; col <= end.column; col++) {
            const currentCell = Matrix.get({ row, column: col }, updatedData);
            if (!currentCell || !editable)
                continue;
            if (currentCell.readOnly)
                continue;
            const updatedCell = {
                ...currentCell,
                style: {
                    ...currentCell.style,
                    textDecoration: anyUnderlined ? 'none' : 'underline',
                },
            };
            updatedData = Matrix.set({ row, column: col }, updatedCell, updatedData);
        }
    }
    return updatedData;
}
export function isValidHexColor(color) {
    if (color.length !== 6) {
        return false;
    }
    const hexChars = '0123456789ABCDEFabcdef';
    for (let i = 1; i < color.length; i++) {
        if (!hexChars.includes(color[i])) {
            return false;
        }
    }
    return true;
}
export function applyColorToCells(currentData, selectedRange, color, editable) {
    if (!selectedRange) {
        return currentData;
    }
    const { start, end } = selectedRange;
    let updatedData = currentData;
    const isHex = isValidHexColor(color);
    const applyColor = isHex ? `#${color}` : color;
    for (let row = start.row; row <= end.row; row++) {
        for (let col = start.column; col <= end.column; col++) {
            const currentCell = Matrix.get({ row, column: col }, updatedData);
            if (!currentCell || !editable)
                continue;
            if (currentCell.readOnly)
                continue;
            const updatedCell = {
                ...currentCell,
                style: {
                    ...currentCell.style,
                    color: applyColor,
                },
            };
            updatedData = Matrix.set({ row, column: col }, updatedCell, updatedData);
        }
    }
    return updatedData;
}
export function applyBackgroundColorToCells(currentData, selectedRange, backgroundColor, editable) {
    if (!selectedRange) {
        return currentData;
    }
    if (selectedRange) {
        const { start, end } = selectedRange;
        let updatedData = currentData;
        const isHex = isValidHexColor(backgroundColor);
        const applyColor = isHex ? `#${backgroundColor}` : backgroundColor;
        for (let row = start.row; row <= end.row; row++) {
            for (let col = start.column; col <= end.column; col++) {
                const currentCell = Matrix.get({ row, column: col }, updatedData);
                if (!currentCell || !editable)
                    continue;
                if (currentCell.readOnly)
                    continue;
                const updatedCell = {
                    ...currentCell,
                    style: {
                        ...currentCell.style,
                        backgroundColor: applyColor,
                    },
                };
                updatedData = Matrix.set({ row, column: col }, updatedCell, updatedData);
            }
        }
        return updatedData;
    }
    return currentData;
}
export function applyFormatePainter(currentData, activePoint) {
    if (!activePoint) {
        return undefined;
    }
    const { row, column } = activePoint;
    let updatedData = { ...currentData };
    const currentCell = Matrix.get({ row, column }, updatedData);
    if (currentCell && currentCell.style) {
        return currentCell.style;
    }
    return undefined;
}
export function cellFormatePainter(currentData, formattedStyle, activePoint) {
    if (!activePoint || !formattedStyle.open) {
        return currentData;
    }
    let updatedData = currentData;
    const currentCell = Matrix.get(activePoint, updatedData);
    if (currentCell && !currentCell.readOnly) {
        const updatedCell = {
            ...currentCell,
            style: formattedStyle.style,
        };
        updatedData = Matrix.set(activePoint, updatedCell, updatedData);
    }
    return updatedData;
}
export function dragEndFormatePainter(currentData, selectedRange, formattedStyle) {
    if (!selectedRange || !formattedStyle.open) {
        return currentData;
    }
    const { start, end } = selectedRange;
    let updatedData = currentData;
    for (let row = start.row; row <= end.row; row++) {
        for (let col = start.column; col <= end.column; col++) {
            const currentCell = Matrix.get({ row, column: col }, updatedData);
            if (!currentCell)
                continue;
            if (currentCell.readOnly)
                continue;
            const updatedCell = {
                ...currentCell,
                style: formattedStyle.style,
            };
            updatedData = Matrix.set({ row, column: col }, updatedCell, updatedData);
        }
    }
    return updatedData;
}
export function dragEndAutoFill(currentData, selectedRange, cellValue, activeCell) {
    if (!selectedRange) {
        return currentData;
    }
    const { start, end } = selectedRange;
    const rowCount = start.row - end.row;
    const columnCount = start.column - end.column;
    let startPoint = { row: 0, column: 0 };
    let endPoint = { row: 0, column: 0 };
    if (rowCount > columnCount) {
        startPoint = { row: activeCell?.row ?? 0, column: start.column };
        endPoint = { row: activeCell?.row ?? 0, column: end.column };
    }
    else {
        startPoint = { row: start.row, column: activeCell?.column ?? 0 };
        endPoint = { row: end.row, column: activeCell?.column ?? 0 };
    }
    let updatedData = currentData;
    for (let row = startPoint.row; row <= endPoint.row; row++) {
        for (let col = startPoint.column; col <= endPoint.column; col++) {
            const currentCell = Matrix.get({ row, column: col }, updatedData);
            if (!currentCell ||
                currentCell.readOnly ||
                ['file'].includes(currentCell?.inputType?.type || '')) {
                continue;
            }
            const updatedCell = {
                ...currentCell,
                style: cellValue.style,
                value: cellValue.value,
                inputType: cellValue.inputType,
            };
            updatedData = Matrix.set({ row, column: col }, updatedCell, updatedData);
        }
    }
    return updatedData;
}
export function edit(state) {
    if (isActiveReadOnly(state) || !state.editable) {
        return state;
    }
    return { ...state, mode: 'edit' };
}
const canClearCell = (cell) => cell && !cell.readOnly;
const clearCell = (cell) => {
    if (!canClearCell(cell) || cell?.inputType?.type === 'file') {
        return cell;
    }
    return {
        ...cell,
        value: '',
        style: cell?.style,
    };
};
const nextLine = (cell) => {
    if (!cell)
        return undefined;
    return {
        ...cell,
        value: (cell.value || '') + '\n',
        style: cell.style,
    };
};
export function clear(state) {
    if (!state.active) {
        return state;
    }
    const selectedRange = state.selected.toRange(state.model.data);
    const changes = [];
    let newData = state.model.data;
    for (const point of selectedRange || []) {
        const cell = Matrix.get(point, state.model.data);
        const clearedCell = clearCell(cell);
        changes.push({
            prevCell: cell || null,
            nextCell: clearedCell || null,
        });
        newData = Matrix.set(point, clearedCell, newData);
    }
    return {
        ...state,
        model: new Model(createFormulaParser, newData),
        ...commit(changes),
    };
}
export function clearEditMode(state) {
    if (!state.active) {
        return state;
    }
    if (state.mode === 'view') {
        const resultState = clear(state);
        return resultState;
    }
    else {
        const changes = [];
        let newData = state.model.data;
        const cell = Matrix.get(state.active, state.model.data);
        const clearedCell = clearCell(cell);
        changes.push({
            prevCell: cell || null,
            nextCell: clearedCell || null,
        });
        newData = Matrix.set(state.active, clearedCell, newData);
        return {
            ...state,
            mode: 'edit',
            model: new Model(createFormulaParser, newData),
            ...commit(changes),
        };
    }
}
export function enterFunctionality(state) {
    if (!state.active) {
        return state;
    }
    const selectedRange = state.selected.toRange(state.model.data);
    const changes = [];
    let newData = state.model.data;
    for (const point of selectedRange || []) {
        const cell = Matrix.get(point, state.model.data);
        const nextLineCell = nextLine(cell);
        changes.push({
            prevCell: cell || null,
            nextCell: nextLineCell || null,
        });
        newData = Matrix.set(point, nextLineCell, newData);
    }
    return {
        ...state,
        mode: 'edit',
        model: new Model(createFormulaParser, newData),
        ...commit(changes),
    };
}
export function blur(state) {
    return {
        ...state,
        active: null,
        copied: null,
        formattedStyle: { open: false, style: undefined },
        selected: new EmptySelection(),
    };
}
export function view(state) {
    return { ...state, mode: 'view' };
}
export function commit(changes) {
    return { lastCommit: changes };
}
export const go = (rowDelta, columnDelta) => (state) => {
    if (!state.active) {
        return;
    }
    const size = Matrix.getSize(state.model.data);
    const newColumn = state.active.column + columnDelta;
    const shouldWrap = newColumn >= size.columns;
    const nextActive = {
        row: state.active.row + rowDelta + (shouldWrap ? 1 : 0),
        column: (state.active.column + columnDelta) % size.columns,
    };
    if (!Matrix.has(nextActive, state.model.data)) {
        return { ...state, mode: 'view' };
    }
    return {
        ...state,
        active: nextActive,
        selected: new RangeSelection(new PointRange(nextActive, nextActive)),
        mode: 'view',
    };
};
const keyDownHandlers = {
    ArrowUp: go(-1, 0),
    ArrowDown: go(+1, 0),
    ArrowLeft: go(0, -1),
    ArrowRight: go(0, +1),
    Tab: go(0, +1),
    Enter: edit,
    Backspace: clearEditMode,
    Delete: clear,
    Escape: blur,
};
const editKeyDownHandlers = {
    Escape: view,
    Tab: keyDownHandlers.Tab,
    Enter: keyDownHandlers.ArrowDown,
    Delete: clear,
};
const editShiftKeyDownHandlers = {
    Tab: go(0, -1),
};
export var Direction;
(function (Direction) {
    Direction["Left"] = "Left";
    Direction["Right"] = "Right";
    Direction["Top"] = "Top";
    Direction["Bottom"] = "Bottom";
})(Direction || (Direction = {}));
const shiftKeyDownHandlers = {
    ArrowUp: (state) => ({
        ...state,
        selected: modifyEdge(state.selected, state.active, state.model.data, Direction.Top),
    }),
    ArrowDown: (state) => ({
        ...state,
        selected: modifyEdge(state.selected, state.active, state.model.data, Direction.Bottom),
    }),
    ArrowLeft: (state) => ({
        ...state,
        selected: modifyEdge(state.selected, state.active, state.model.data, Direction.Left),
    }),
    ArrowRight: (state) => ({
        ...state,
        selected: modifyEdge(state.selected, state.active, state.model.data, Direction.Right),
    }),
    Tab: go(0, -1),
};
const shiftMetaKeyDownHandlers = {};
const metaKeyDownHandlers = {};
export function getKeyDownHandler(state, event) {
    const { key } = event;
    let handlers;
    if (state.mode === 'edit') {
        if (event.shiftKey && key === 'Enter') {
            handlers = keyDownHandlers;
        }
        else if (event.altKey && key === 'Enter') {
            return enterFunctionality;
        }
        else if (event.shiftKey) {
            handlers = editShiftKeyDownHandlers;
        }
        else {
            handlers = editKeyDownHandlers;
        }
    }
    else if (event.shiftKey && event.metaKey) {
        handlers = shiftMetaKeyDownHandlers;
    }
    else if (event.shiftKey) {
        handlers = shiftKeyDownHandlers;
    }
    else if (event.metaKey) {
        handlers = metaKeyDownHandlers;
    }
    else {
        handlers = keyDownHandlers;
    }
    return handlers[key];
}
/** Returns whether the reducer has a handler for the given keydown event */
export function hasKeyDownHandler(state, event) {
    return getKeyDownHandler(state, event) !== undefined;
}
/** Returns whether the active cell is read only */
export function isActiveReadOnly(state) {
    const activeCell = getActive(state);
    return Boolean(activeCell?.readOnly);
}
/** Gets active cell from given state */
export function getActive(state) {
    const activeCell = state.active && Matrix.get(state.active, state.model.data);
    return activeCell || null;
}
/** Modify given edge according to given active point and data */
export function modifyEdge(selection, active, data, direction) {
    if (!active) {
        return selection;
    }
    if (selection instanceof RangeSelection) {
        const nextSelection = modifyRangeSelectionEdge(selection, active, data, direction);
        // @ts-expect-error
        return nextSelection;
    }
    if (selection instanceof EntireColumnsSelection) {
        // @ts-expect-error
        return modifyEntireColumnsSelection(selection, active, data, direction);
    }
    if (selection instanceof EntireRowsSelection) {
        // @ts-expect-error
        return modifyEntireRowsSelection(selection, active, data, direction);
    }
    return selection;
}
export function modifyRangeSelectionEdge(rangeSelection, active, data, edge) {
    const field = edge === Direction.Left || edge === Direction.Right ? 'column' : 'row';
    const key = edge === Direction.Left || edge === Direction.Top ? 'start' : 'end';
    const delta = key === 'start' ? -1 : 1;
    const edgeOffsets = rangeSelection.range.has({
        ...active,
        [field]: active[field] + delta * -1,
    });
    const keyToModify = edgeOffsets ? (key === 'start' ? 'end' : 'start') : key;
    const nextRange = new PointRange(rangeSelection.range.start, rangeSelection.range.end);
    nextRange[keyToModify][field] += delta;
    const nextSelection = new RangeSelection(nextRange).normalizeTo(data);
    return nextSelection;
}
export function modifyEntireRowsSelection(selection, active, data, edge) {
    if (edge === Direction.Left || edge === Direction.Right) {
        return selection;
    }
    const delta = edge === Direction.Top ? -1 : 1;
    const property = edge === Direction.Top ? 'start' : 'end';
    const oppositeProperty = property === 'start' ? 'end' : 'start';
    const newSelectionData = { ...selection };
    if (edge === Direction.Top
        ? selection.end > active.row
        : selection.start < active.row) {
        newSelectionData[oppositeProperty] = selection[oppositeProperty] + delta;
    }
    else {
        newSelectionData[property] = selection[property] + delta;
    }
    const nextSelection = new EntireRowsSelection(Math.max(newSelectionData.start, 0), Math.max(newSelectionData.end, 0));
    return nextSelection.normalizeTo(data);
}
export function modifyEntireColumnsSelection(selection, active, data, edge) {
    if (edge === Direction.Top || edge === Direction.Bottom) {
        return selection;
    }
    const delta = edge === Direction.Left ? -1 : 1;
    const property = edge === Direction.Left ? 'start' : 'end';
    const oppositeProperty = property === 'start' ? 'end' : 'start';
    const newSelectionData = { ...selection };
    if (edge === Direction.Left
        ? selection.end > active.row
        : selection.start < active.row) {
        newSelectionData[oppositeProperty] = selection[oppositeProperty] + delta;
    }
    else {
        newSelectionData[property] = selection[property] + delta;
    }
    const nextSelection = new EntireColumnsSelection(Math.max(newSelectionData.start, 0), Math.max(newSelectionData.end, 0));
    return nextSelection.normalizeTo(data);
}
//# sourceMappingURL=reducerFunctions.js.map