"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
function _export(target, all) {
    for(var name in all)Object.defineProperty(target, name, {
        enumerable: true,
        get: Object.getOwnPropertyDescriptor(all, name).get
    });
}
_export(exports, {
    get buildChangesAndRowData () {
        return buildChangesAndRowData;
    },
    get buildManualUpdateRowPayloads () {
        return buildManualUpdateRowPayloads;
    },
    get commitRowsUpdate () {
        return commitRowsUpdate;
    },
    get enqueueMutation () {
        return enqueueMutation;
    },
    get filterChangesForBatchedServerUpdate () {
        return filterChangesForBatchedServerUpdate;
    },
    get findVisualRowById () {
        return findVisualRowById;
    },
    get getRowIdByVisualRow () {
        return getRowIdByVisualRow;
    },
    get getRowIdFromRowData () {
        return getRowIdFromRowData;
    },
    get handleBeforeAlterForCrud () {
        return handleBeforeAlterForCrud;
    },
    get isMissingRowId () {
        return isMissingRowId;
    },
    get queueCrud () {
        return queueCrud;
    },
    get revertChangeTuples () {
        return revertChangeTuples;
    },
    get rowIdsFromAlterRemove () {
        return rowIdsFromAlterRemove;
    },
    get runAfterRowsMutation () {
        return runAfterRowsMutation;
    },
    get runAfterRowsMutationError () {
        return runAfterRowsMutationError;
    },
    get runBeforeRowsMutation () {
        return runBeforeRowsMutation;
    },
    get runManualUpdateRowsMutation () {
        return runManualUpdateRowsMutation;
    },
    get runUpdateFromChanges () {
        return runUpdateFromChanges;
    },
    get shouldIgnoreAfterChangeForServerUpdate () {
        return shouldIgnoreAfterChangeForServerUpdate;
    },
    get validateRowChanges () {
        return validateRowChanges;
    }
});
const _function = require("../../../helpers/function");
const _object = require("../../../helpers/object");
const _console = require("../../../helpers/console");
const _errors = require("../../../helpers/errors");
const _constants = require("../constants");
function runBeforeRowsMutation(hot, operation, payload) {
    if (hot.runHooks('beforeRowsMutation', operation, payload) === false) {
        return false;
    }
    return undefined;
}
function runAfterRowsMutation(hot, operation, payload) {
    hot.runHooks('afterRowsMutation', operation, payload);
}
function runAfterRowsMutationError(hot, operation, err, payload) {
    hot.runHooks('afterRowsMutationError', operation, err, payload);
}
function enqueueMutation(state, fn) {
    const mutationPromise = state.tail.then(fn);
    state.tail = mutationPromise.catch(()=>{});
    return mutationPromise;
}
function getRowIdFromRowData(rowData, rowIdOption) {
    if (rowIdOption === undefined || rowIdOption === null) {
        return undefined;
    }
    if ((0, _function.isFunction)(rowIdOption)) {
        return rowIdOption(rowData);
    }
    if (typeof rowIdOption === 'string') {
        return (0, _object.getProperty)(rowData, rowIdOption);
    }
    return undefined;
}
function getRowIdByVisualRow(hot, rowIdOption, visualRow) {
    return getRowIdFromRowData(hot.getSourceDataAtRow(hot.toPhysicalRow(visualRow)), rowIdOption);
}
function isMissingRowId(id) {
    return id === undefined || id === null;
}
function findVisualRowById(hot, rowIdOption, rowId) {
    for(let row = 0; row < hot.countRows(); row += 1){
        if (getRowIdByVisualRow(hot, rowIdOption, row) === rowId) {
            return row;
        }
    }
    return -1;
}
function rowIdsFromAlterRemove(hot, rowIdOption, index, amount) {
    const ids = [];
    const n = ()=>hot.countRows();
    const pushRange = (start, amt)=>{
        for(let r = 0; r < amt; r += 1){
            const v = start + r;
            if (v >= 0 && v < n()) {
                const id = getRowIdByVisualRow(hot, rowIdOption, v);
                if (isMissingRowId(id)) {
                    (0, _errors.throwWithCause)((0, _constants.dataProviderErrorRemoveMissingRowId)(v));
                }
                ids.push(id);
            }
        }
    };
    if (Array.isArray(index)) {
        index.forEach(([gi, ga])=>{
            const start = gi === undefined || gi === null ? Math.max(0, n() - (ga ?? 1)) : Math.max(0, gi);
            pushRange(start, ga ?? 1);
        });
    } else {
        const amt = typeof amount === 'number' && amount >= 1 ? amount : 1;
        const start = index === undefined || index === null ? Math.max(0, n() - amt) : Math.max(0, index);
        pushRange(start, amt);
    }
    return ids;
}
function buildChangesAndRowData(hot, rowChanges) {
    const visualRow = rowChanges[0][0];
    const rowData = hot.getSourceDataAtRow(hot.toPhysicalRow(visualRow));
    const isObj = rowData && typeof rowData === 'object' && !Array.isArray(rowData);
    const changesObj = {};
    rowChanges.forEach(([, prop, , nv])=>{
        const col = typeof prop === 'number' ? prop : hot.propToCol(prop);
        const key = isObj ? hot.colToProp(col) : col;
        changesObj[key] = nv;
    });
    let rowDataWithChanges;
    if (Array.isArray(rowData)) {
        rowDataWithChanges = [
            ...rowData
        ];
        rowChanges.forEach(([, prop, , nv])=>{
            const col = typeof prop === 'number' ? prop : hot.propToCol(prop);
            rowDataWithChanges[col] = nv;
        });
    } else if (isObj) {
        rowDataWithChanges = {
            ...rowData,
            ...changesObj
        };
    } else {
        rowDataWithChanges = {
            ...changesObj
        };
    }
    return {
        changesObj,
        rowData: rowDataWithChanges
    };
}
function revertChangeTuples(hot, changeTuples) {
    if (!changeTuples?.length) {
        return;
    }
    hot.batch(()=>{
        changeTuples.forEach(([row, prop, oldVal])=>{
            hot.setDataAtRowProp(row, prop, oldVal, 'DataProvider.revert');
        });
    });
}
function validateRowChanges(hot, visualRow, changes) {
    const entries = Object.entries(changes);
    if (entries.length === 0) {
        return Promise.resolve(true);
    }
    return new Promise((resolve)=>{
        let pending = entries.length;
        let valid = true;
        const done = ()=>{
            pending -= 1;
            if (pending === 0) {
                resolve(valid);
            }
        };
        entries.forEach(([prop, value])=>{
            const col = hot.propToCol(prop);
            if (col === undefined || col < 0) {
                done();
                return;
            }
            const cellMeta = hot.getCellMeta(visualRow, col);
            if (!hot.getCellValidator(cellMeta)) {
                done();
                return;
            }
            hot.validateCell(value, cellMeta, (result)=>{
                if (result === false && cellMeta.allowInvalid === false) {
                    valid = false;
                }
                done();
            }, 'DataProvider.updateRows');
        });
    });
}
function shouldIgnoreAfterChangeForServerUpdate(hasOnRowsUpdate, changes, source) {
    if (!hasOnRowsUpdate || !changes?.length) {
        return true;
    }
    if (source === 'DataProvider.revert') {
        return true;
    }
    if (!_constants.DATA_PROVIDER_BATCH_UPDATE_SOURCES.has(source)) {
        return true;
    }
    return false;
}
function filterChangesForBatchedServerUpdate(hot, changes) {
    const real = changes.filter((c)=>c[2] !== c[3]);
    if (real.length === 0) {
        return [];
    }
    return real.filter((c)=>{
        const col = hot.propToCol(c[1]);
        if (col === undefined || col < 0) {
            return false;
        }
        return hot.getCellMeta(c[0], col).valid !== false;
    });
}
function buildManualUpdateRowPayloads(hot, rowIdOption, rows) {
    return rows.map((p)=>{
        const visualRow = findVisualRowById(hot, rowIdOption, p.id);
        let rowData;
        if (visualRow >= 0) {
            rowData = hot.getSourceDataAtRow(hot.toPhysicalRow(visualRow));
        } else if (p.rowData !== undefined) {
            rowData = p.rowData;
        } else {
            rowData = {};
        }
        return {
            id: p.id,
            changes: p.changes,
            rowData: rowData && typeof rowData === 'object' ? rowData : {}
        };
    });
}
async function commitRowsUpdate(hot, callbacks, rowPayloads, options = {}) {
    const onRowsUpdate = callbacks.getOnRowsUpdate();
    if (!(0, _function.isFunction)(onRowsUpdate)) {
        return;
    }
    const payload = {
        rows: rowPayloads
    };
    const { revertOptimistic } = options;
    const { onRequestFailed } = callbacks;
    try {
        await onRowsUpdate(rowPayloads);
    } catch (err) {
        runAfterRowsMutationError(hot, 'update', err, payload);
        callbacks.logError('Row update failed:', err);
        if ((0, _function.isFunction)(revertOptimistic)) {
            revertOptimistic();
        }
        hot.render();
        onRequestFailed?.('update', err);
        return;
    }
    try {
        runAfterRowsMutation(hot, 'update', payload);
        await callbacks.fetchData();
    } catch (err) {
        runAfterRowsMutationError(hot, 'update', err, payload);
        callbacks.logError('Data reload failed:', err);
        if ((0, _function.isFunction)(revertOptimistic)) {
            revertOptimistic();
        }
        hot.render();
    // Do not call `onRequestFailed('fetch', err)` here: `fetchData` already surfaces fetch errors (and rethrows).
    }
}
async function runManualUpdateRowsMutation(hot, ctx, rowPayloads) {
    const { getRowIdOption, commitRowsUpdate: commitUpdate } = ctx;
    const payload = {
        rows: rowPayloads
    };
    if (runBeforeRowsMutation(hot, 'update', payload) === false) {
        return;
    }
    const rowIdOption = getRowIdOption();
    const validationResults = await Promise.all(rowPayloads.map(async (p)=>{
        const visualRow = findVisualRowById(hot, rowIdOption, p.id);
        if (visualRow < 0) {
            return true;
        }
        return validateRowChanges(hot, visualRow, p.changes);
    }));
    if (validationResults.some((ok)=>!ok)) {
        runAfterRowsMutationError(hot, 'update', new Error('Row update validation failed'), payload);
        (0, _console.error)('Row update failed: validation failed for one or more cells');
        return;
    }
    await commitUpdate(rowPayloads);
}
async function runUpdateFromChanges(hot, ctx, changes) {
    const { getRowIdOption, commitRowsUpdate: commitFn } = ctx;
    const byRow = new Map();
    changes.forEach((ch)=>{
        const vr = ch[0];
        if (!byRow.has(vr)) {
            byRow.set(vr, []);
        }
        byRow.get(vr).push(ch);
    });
    const sortedRows = [
        ...byRow.keys()
    ].sort((a, b)=>a - b);
    const rowIdOption = getRowIdOption();
    const rowPayloads = sortedRows.map((vr)=>{
        const { changesObj, rowData } = buildChangesAndRowData(hot, byRow.get(vr));
        return {
            id: getRowIdByVisualRow(hot, rowIdOption, vr),
            changes: changesObj,
            rowData
        };
    });
    const payload = {
        rows: rowPayloads
    };
    const revert = ()=>revertChangeTuples(hot, changes);
    if (rowPayloads.some((p)=>isMissingRowId(p.id))) {
        revert();
        runAfterRowsMutationError(hot, 'update', new Error(_constants.DATA_PROVIDER_ERROR_UPDATE_MISSING_ROW_ID), payload);
        (0, _console.error)('Row update failed:', _constants.DATA_PROVIDER_ERROR_UPDATE_MISSING_ROW_ID);
        hot.render();
        return;
    }
    if (runBeforeRowsMutation(hot, 'update', payload) === false) {
        revert();
        return;
    }
    const ok = await Promise.all(sortedRows.map((vr, i)=>validateRowChanges(hot, vr, rowPayloads[i].changes)));
    if (ok.some((v)=>!v)) {
        revert();
        runAfterRowsMutationError(hot, 'update', new Error('Row update validation failed'), payload);
        (0, _console.error)('Row update failed: validation failed for one or more cells');
        hot.render();
        return;
    }
    await commitFn(rowPayloads, {
        revertOptimistic: revert
    });
}
function queueCrud(ctx, operation, payload, userPromiseFn, onSuccess) {
    const { enqueueMutation: enqueue, runBeforeRowsMutation: beforeMut, runAfterRowsMutation: afterMut, runAfterRowsMutationError: afterMutErr, logError: logErr, onRequestFailed } = ctx;
    return enqueue(async ()=>{
        if (beforeMut(operation, payload) === false) {
            return;
        }
        const mutationLabel = operation === 'create' ? 'Row create' : 'Row remove';
        try {
            await userPromiseFn();
        } catch (err) {
            afterMutErr(operation, err, payload);
            logErr(`${mutationLabel} failed:`, err);
            onRequestFailed?.(operation, err);
            return;
        }
        try {
            afterMut(operation, payload);
            await onSuccess();
        } catch (err) {
            afterMutErr(operation, err, payload);
            logErr('Data reload failed:', err);
        // Do not call `onRequestFailed('fetch', err)` here: `onSuccess` is typically `fetchData`, which already surfaces fetch errors (and rethrows).
        }
    });
}
/**
 * @param {object} ctx Same shape as [[handleBeforeAlterForCrud]].
 * @param {'insert_row_above'|'insert_row_below'} action Insert direction.
 * @param {number|Array|undefined|null} index Row index or alter grouping.
 * @param {number} amount Row count when `index` is a number.
 * @returns {boolean|undefined} `false` when the alter is handled.
 */ function handleInsertRowAlterForCrud(ctx, action, index, amount) {
    const { hot, getOnRowsCreate, getRowId, createRows } = ctx;
    if (!(0, _function.isFunction)(getOnRowsCreate())) {
        return;
    }
    const n = hot.countSourceRows();
    if (hot.getSettings().maxRows === n) {
        return;
    }
    const position = action === 'insert_row_below' ? 'below' : 'above';
    const visualIndex = index ?? (position === 'below' ? n : 0);
    createRows({
        position,
        referenceRowId: visualIndex >= 0 ? getRowId(visualIndex) : undefined,
        rowsAmount: typeof amount === 'number' && amount >= 1 ? amount : 1
    });
    return false;
}
/**
 * @param {object} ctx Same shape as [[handleBeforeAlterForCrud]].
 * @param {number|Array|undefined|null} index Row index or alter grouping.
 * @param {number} amount Row count when `index` is a number.
 * @returns {boolean|undefined} `false` when the alter is handled.
 */ function handleRemoveRowAlterForCrud(ctx, index, amount) {
    const { hot, getOnRowsRemove, getRowIdOption, removeRows } = ctx;
    if (!(0, _function.isFunction)(getOnRowsRemove())) {
        return;
    }
    const rowIds = rowIdsFromAlterRemove(hot, getRowIdOption(), index, amount);
    if (rowIds.length === 0) {
        return;
    }
    removeRows(rowIds);
    return false;
}
function handleBeforeAlterForCrud(ctx, action, index, amount) {
    if (action === 'insert_row_above' || action === 'insert_row_below') {
        return handleInsertRowAlterForCrud(ctx, action, index, amount);
    }
    if (action === 'remove_row') {
        return handleRemoveRowAlterForCrud(ctx, index, amount);
    }
}
