/*!
 * Built by Revolist OU ❤️
 */
import { getSourceItem, getVisibleSourceItem } from "../../store/index";
import { CELL_CLASS, DISABLED_CLASS, getCellRaw, getCellData, } from "../../utils";
import { getRange } from "../../store/index";
import { isGroupingColumn } from "../../plugins/groupingRow/grouping.service";
import slice from "lodash/slice";
export function getCellEditor(column, editors = {}) {
    const editor = column === null || column === void 0 ? void 0 : column.editor;
    if (!editor) {
        return undefined;
    }
    // reference
    if (typeof editor === 'string') {
        return editors[editor];
    }
    return editor;
}
export default class ColumnService {
    get columns() {
        return getVisibleSourceItem(this.source);
    }
    constructor(dataStore, source) {
        this.dataStore = dataStore;
        this.source = source;
        this.unsubscribe = [];
        this.hasGrouping = false;
        this.unsubscribe.push(source.onChange('source', s => this.checkGrouping(s)));
        this.checkGrouping(source.get('source'));
        this.type = source.get('type');
    }
    checkGrouping(cols) {
        for (let rgCol of cols) {
            if (isGroupingColumn(rgCol)) {
                this.hasGrouping = true;
                return;
            }
            this.hasGrouping = false;
        }
    }
    isReadOnly(r, c) {
        var _a;
        const readOnly = (_a = this.columns[c]) === null || _a === void 0 ? void 0 : _a.readonly;
        if (typeof readOnly === 'function') {
            const data = this.rowDataModel(r, c);
            return readOnly(data);
        }
        return !!readOnly;
    }
    mergeProperties(r, c, defaultProps, schemaModel) {
        var _a, _b;
        const props = Object.assign({}, defaultProps);
        props.class = Object.assign(Object.assign({}, (typeof props.class === 'string'
            ? { [props.class]: true }
            : props.class)), { [CELL_CLASS]: true, [DISABLED_CLASS]: this.isReadOnly(r, c) });
        const extra = (_b = (_a = schemaModel.column) === null || _a === void 0 ? void 0 : _a.cellProperties) === null || _b === void 0 ? void 0 : _b.call(_a, schemaModel);
        if (!extra) {
            return props;
        }
        return doPropMerge(props, extra);
    }
    getRowClass(r, prop) {
        const model = getSourceItem(this.dataStore, r) || {};
        return model[prop] || '';
    }
    getSaveData(rowIndex, colIndex, val) {
        const data = this.rowDataModel(rowIndex, colIndex);
        if (typeof val === 'undefined') {
            val = getCellData(data.value);
        }
        return Object.assign(Object.assign({}, data), { val });
    }
    /**
     * Get cell data model for given rowIndex and colIndex
     * Used to pass data to editor/renderer
     */
    rowDataModel(rowIndex, colIndex) {
        const column = this.columns[colIndex];
        const prop = column === null || column === void 0 ? void 0 : column.prop;
        const model = getSourceItem(this.dataStore, rowIndex) || {};
        const type = this.dataStore.get('type');
        return {
            prop,
            model,
            data: this.dataStore.get('source'),
            column,
            rowIndex,
            colIndex,
            colType: this.type,
            type,
            value: getCellRaw(model, column),
        };
    }
    getRangeData(d, columns) {
        var _a;
        const changed = {};
        // get original length sizes
        const copyColLength = d.oldRange.x1 - d.oldRange.x + 1;
        const copyRowLength = d.oldRange.y1 - d.oldRange.y + 1;
        const mapping = {};
        // rows
        for (let rowIndex = d.newRange.y, i = 0; rowIndex < d.newRange.y1 + 1; rowIndex++, i++) {
            // copy original data link
            const oldRowIndex = d.oldRange.y + (i % copyRowLength);
            const copyRow = getSourceItem(this.dataStore, oldRowIndex) || {};
            // columns
            for (let colIndex = d.newRange.x, j = 0; colIndex < d.newRange.x1 + 1; colIndex++, j++) {
                // check if old range area
                if (rowIndex >= d.oldRange.y &&
                    rowIndex <= d.oldRange.y1 &&
                    colIndex >= d.oldRange.x &&
                    colIndex <= d.oldRange.x1) {
                    continue;
                }
                // requested column beyond range
                if (!this.columns[colIndex]) {
                    continue;
                }
                const prop = (_a = this.columns[colIndex]) === null || _a === void 0 ? void 0 : _a.prop;
                const copyColIndex = d.oldRange.x + (j % copyColLength);
                const copyColumnProp = columns[copyColIndex].prop;
                /** if can write */
                if (!this.isReadOnly(rowIndex, colIndex)) {
                    /** to show before save */
                    if (!changed[rowIndex]) {
                        changed[rowIndex] = {};
                    }
                    changed[rowIndex][prop] = copyRow[copyColumnProp];
                    /** Generate mapping object */
                    if (!mapping[rowIndex]) {
                        mapping[rowIndex] = {};
                    }
                    mapping[rowIndex][prop] = {
                        colIndex: copyColIndex,
                        colProp: copyColumnProp,
                        rowIndex: oldRowIndex,
                    };
                }
            }
        }
        return {
            changed,
            mapping,
        };
    }
    getTransformedDataToApply({ start, data, targetRange, }) {
        const changed = {};
        const copyRowLength = data.length;
        if (!copyRowLength) {
            return {
                changed,
                range: null,
            };
        }
        const colLength = this.columns.length;
        const rowLength = this.dataStore.get('items').length;
        const bounds = this.getDataApplyBounds(start, targetRange, copyRowLength, rowLength, colLength);
        if (!bounds) {
            return {
                changed,
                range: null,
            };
        }
        const { startRow, startCol, endRow } = bounds;
        let maxCol = startCol - 1;
        let lastRow = startRow - 1;
        // rows
        for (let rowIndex = startRow, i = 0; rowIndex <= endRow; rowIndex++, i++) {
            // copy original data link
            const copyRow = data[i % copyRowLength];
            const copyColLength = (copyRow === null || copyRow === void 0 ? void 0 : copyRow.length) || 0;
            if (!copyColLength) {
                continue;
            }
            maxCol = Math.max(maxCol, this.applyClipboardRow(changed, {
                bounds,
                copyColLength,
                copyRow,
                rowIndex,
                start,
                targetRange,
            }));
            lastRow = rowIndex;
        }
        return {
            changed,
            range: this.getAppliedRange(bounds, lastRow, maxCol),
        };
    }
    getDataApplyBounds(start, targetRange, copyRowLength, rowLength, colLength) {
        var _a, _b, _c;
        const startRow = (_a = targetRange === null || targetRange === void 0 ? void 0 : targetRange.y) !== null && _a !== void 0 ? _a : start.y;
        const startCol = (_b = targetRange === null || targetRange === void 0 ? void 0 : targetRange.x) !== null && _b !== void 0 ? _b : start.x;
        const endRow = Math.min(rowLength - 1, (_c = targetRange === null || targetRange === void 0 ? void 0 : targetRange.y1) !== null && _c !== void 0 ? _c : start.y + copyRowLength - 1);
        if (endRow < startRow || startCol >= colLength) {
            return null;
        }
        return { startRow, startCol, endRow, colLength };
    }
    applyClipboardRow(changed, { bounds, copyColLength, copyRow, rowIndex, start, targetRange, }) {
        var _a;
        const endCol = Math.min(bounds.colLength - 1, (_a = targetRange === null || targetRange === void 0 ? void 0 : targetRange.x1) !== null && _a !== void 0 ? _a : start.x + copyColLength - 1);
        for (let colIndex = bounds.startCol, j = 0; colIndex <= endCol; colIndex++, j++) {
            if (this.isReadOnly(rowIndex, colIndex)) {
                continue;
            }
            const prop = this.columns[colIndex].prop;
            changed[rowIndex] = changed[rowIndex] || {};
            changed[rowIndex][prop] = copyRow[j % copyColLength];
        }
        return endCol;
    }
    getAppliedRange({ startRow, startCol }, lastRow, maxCol) {
        if (lastRow < startRow || maxCol < startCol) {
            return null;
        }
        return getRange({ x: startCol, y: startRow }, {
            y: lastRow,
            x: maxCol,
        });
    }
    getRangeStaticData(d, value) {
        const changed = {};
        // rows
        for (let rowIndex = d.y, i = 0; rowIndex < d.y1 + 1; rowIndex++, i++) {
            // columns
            for (let colIndex = d.x, j = 0; colIndex < d.x1 + 1; colIndex++, j++) {
                // requested column beyond range
                if (!this.columns[colIndex]) {
                    continue;
                }
                const p = this.columns[colIndex].prop;
                /** if can write */
                if (!this.isReadOnly(rowIndex, colIndex)) {
                    /** to show before save */
                    if (!changed[rowIndex]) {
                        changed[rowIndex] = {};
                    }
                    changed[rowIndex][p] = value;
                }
            }
        }
        return changed;
    }
    getRangeTransformedToProps(d, store) {
        var _a;
        const area = [];
        const type = this.dataStore.get('type');
        // rows
        for (let rowIndex = d.y, i = 0; rowIndex < d.y1 + 1; rowIndex++, i++) {
            // columns
            for (let colIndex = d.x, j = 0; colIndex < d.x1 + 1; colIndex++, j++) {
                const prop = (_a = this.columns[colIndex]) === null || _a === void 0 ? void 0 : _a.prop;
                area.push({
                    prop,
                    rowIndex,
                    colIndex,
                    model: getSourceItem(store, rowIndex),
                    type,
                    colType: this.type,
                });
            }
        }
        return area;
    }
    copyRangeArray(range, store) {
        const cols = [...this.columns];
        const props = slice(cols, range.x, range.x1 + 1).map(v => v.prop);
        const toCopy = [];
        const mapping = {};
        // rows indexes
        for (let i = range.y; i <= range.y1; i++) {
            const rgRow = [];
            mapping[i] = {};
            // columns indexes
            for (let prop of props) {
                const item = getSourceItem(store, i);
                // if no item - skip
                if (!item) {
                    continue;
                }
                const val = item[prop];
                rgRow.push(val);
                mapping[i][prop] = val;
            }
            toCopy.push(rgRow);
        }
        return {
            data: toCopy,
            mapping,
        };
    }
    destroy() {
        this.unsubscribe.forEach(f => f());
    }
}
/**
 * Checks if the given rowDrag is a service for dragging rows.
 */
export function isRowDragService(rowDrag, model) {
    if (typeof rowDrag === 'function') {
        return rowDrag(model);
    }
    return !!rowDrag;
}
function mergeClasses(class1 = {}, class2 = {}) {
    if (typeof class1 === 'string') {
        class1 = { [class1]: true };
    }
    if (typeof class2 === 'string') {
        class2 = { [class2]: true };
    }
    return Object.assign(Object.assign({}, class1), class2);
}
export function doPropMerge(existing, extra) {
    // if className is provided - remove it from props it messing with stencil
    if (extra.className) {
        extra.class = mergeClasses(extra.class, extra.className);
        delete extra.className;
    }
    let props = Object.assign(Object.assign({}, extra), existing);
    // extend existing props
    if (extra.class) {
        props.class = mergeClasses(props.class, extra.class);
    }
    if (extra.style) {
        props.style = Object.assign(Object.assign({}, extra.style), props.style);
    }
    return props;
}
