/* eslint-disable import/no-cycle */
import { functionalUpdate, getMemoOptions, memo } from '../utils';
//
import { createColumn } from './column';
import { Headers } from './headers';
//
import { ColumnFaceting } from '../features/ColumnFaceting';
import { ColumnFiltering } from '../features/ColumnFiltering';
import { ColumnGrouping } from '../features/ColumnGrouping';
import { ColumnOrdering } from '../features/ColumnOrdering';
import { ColumnPinning } from '../features/ColumnPinning';
import { ColumnSizing } from '../features/ColumnSizing';
import { ColumnVisibility } from '../features/ColumnVisibility';
import { GlobalFaceting } from '../features/GlobalFaceting';
import { GlobalFiltering } from '../features/GlobalFiltering';
import { RowExpanding } from '../features/RowExpanding';
import { RowPagination } from '../features/RowPagination';
import { RowPinning } from '../features/RowPinning';
import { RowSelection } from '../features/RowSelection';
import { RowSorting } from '../features/RowSorting';
const builtInFeatures = [
    Headers,
    ColumnVisibility,
    ColumnOrdering,
    ColumnPinning,
    ColumnFaceting,
    ColumnFiltering,
    GlobalFaceting, // depends on ColumnFaceting
    GlobalFiltering, // depends on ColumnFiltering
    RowSorting,
    ColumnGrouping, // depends on RowSorting
    RowExpanding,
    RowPagination,
    RowPinning,
    RowSelection,
    ColumnSizing,
];
export function createTable(options) {
    var _a, _b, _c;
    if (process.env.NODE_ENV !== 'production' && (options.debugAll || options.debugTable)) {
        console.info('Creating Table Instance...');
    }
    const _features = [...builtInFeatures, ...((_a = options._features) !== null && _a !== void 0 ? _a : [])];
    const table = { _features };
    const defaultOptions = table._features.reduce((obj, feature) => {
        var _a;
        return Object.assign(obj, (_a = feature.getDefaultOptions) === null || _a === void 0 ? void 0 : _a.call(feature, table));
    }, {});
    const mergeOptions = (resolvedOptions) => {
        if (table.options.mergeOptions) {
            return table.options.mergeOptions(defaultOptions, resolvedOptions);
        }
        const merged = {
            ...defaultOptions,
            ...resolvedOptions,
        };
        return merged;
    };
    const coreInitialState = {};
    let initialState = {
        ...coreInitialState,
        ...((_b = options.initialState) !== null && _b !== void 0 ? _b : {}),
    };
    table._features.forEach((feature) => {
        var _a, _b;
        initialState = ((_b = (_a = feature.getInitialState) === null || _a === void 0 ? void 0 : _a.call(feature, initialState)) !== null && _b !== void 0 ? _b : initialState);
    });
    const queued = [];
    let queuedTimeout = false;
    const coreInstance = {
        _features,
        options: {
            ...defaultOptions,
            ...options,
        },
        initialState,
        _queue: (cb) => {
            queued.push(cb);
            if (!queuedTimeout) {
                queuedTimeout = true;
                // Schedule a microtask to run the queued callbacks after
                // the current call stack (render, etc) has finished.
                Promise.resolve()
                    .then(() => {
                    while (queued.length) {
                        queued.shift()();
                    }
                    queuedTimeout = false;
                })
                    .catch((error) => setTimeout(() => {
                    throw error;
                }));
            }
        },
        reset: () => {
            table.setState(table.initialState);
        },
        setOptions: (updater) => {
            const newOptions = functionalUpdate(updater, table.options);
            table.options = mergeOptions(newOptions);
        },
        getState: () => {
            return table.options.state;
        },
        setState: (updater) => {
            var _a, _b;
            (_b = (_a = table.options).onStateChange) === null || _b === void 0 ? void 0 : _b.call(_a, updater);
        },
        _getRowId: (row, index, parent) => {
            var _a, _b, _c;
            return (_c = (_b = (_a = table.options).getRowId) === null || _b === void 0 ? void 0 : _b.call(_a, row, index, parent)) !== null && _c !== void 0 ? _c : `${parent ? [parent.id, index].join('.') : index}`;
        },
        getCoreRowModel: () => {
            if (!table._getCoreRowModel) {
                table._getCoreRowModel = table.options.getCoreRowModel(table);
            }
            return table._getCoreRowModel();
        },
        // The final calls start at the bottom of the model,
        // expanded rows, which then work their way up
        getRowModel: () => {
            return table.getPaginationRowModel();
        },
        // in next version, we should just pass in the row model as the optional 2nd arg
        getRow: (id, searchAll) => {
            let row = (searchAll ? table.getPrePaginationRowModel() : table.getRowModel()).rowsById[id];
            if (!row) {
                row = table.getCoreRowModel().rowsById[id];
                if (!row) {
                    if (process.env.NODE_ENV !== 'production') {
                        throw new Error(`getRow could not find row with ID: ${id}`);
                    }
                    throw new Error();
                }
            }
            return row;
        },
        _getDefaultColumnDef: memo(() => [table.options.defaultColumn], (defaultColumn) => {
            const baseDef = defaultColumn !== null && defaultColumn !== void 0 ? defaultColumn : {};
            const result = {
                header: (props) => {
                    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- columnDef 与 ColumnDefResolved 运行时一致
                    const resolvedColumnDef = props.header.column.columnDef;
                    if (resolvedColumnDef.accessorKey) {
                        return resolvedColumnDef.accessorKey;
                    }
                    if (resolvedColumnDef.accessorFn) {
                        return resolvedColumnDef.id;
                    }
                    return null;
                },
                // footer: props => props.header.column.id,
                cell: (props) => { var _a, _b, _c; return (_c = (_b = (_a = props.renderValue()) === null || _a === void 0 ? void 0 : _a.toString) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : null; },
                ...table._features.reduce((obj, feature) => {
                    var _a;
                    return Object.assign(obj, (_a = feature.getDefaultColumnDef) === null || _a === void 0 ? void 0 : _a.call(feature));
                }, {}),
                ...baseDef,
            };
            return result;
        }, getMemoOptions(options, 'debugColumns', '_getDefaultColumnDef')),
        _getColumnDefs: () => table.options.columns,
        getAllColumns: memo(() => [table._getColumnDefs()], (columnDefs) => {
            const recurseColumns = (defs, parent, depth = 0) => {
                return defs.map((columnDef) => {
                    const column = createColumn(table, columnDef, depth, parent);
                    const groupingColumnDef = columnDef;
                    column.columns = groupingColumnDef.columns
                        ? recurseColumns(groupingColumnDef.columns, column, depth + 1)
                        : [];
                    return column;
                });
            };
            return recurseColumns(columnDefs);
        }, getMemoOptions(options, 'debugColumns', 'getAllColumns')),
        getAllFlatColumns: memo(() => [table.getAllColumns()], (allColumns) => {
            return allColumns.flatMap((column) => {
                return column.getFlatColumns();
            });
        }, getMemoOptions(options, 'debugColumns', 'getAllFlatColumns')),
        _getAllFlatColumnsById: memo(() => [table.getAllFlatColumns()], (flatColumns) => {
            const acc = {};
            flatColumns.forEach((column) => {
                acc[column.id] = column;
            });
            return acc;
        }, getMemoOptions(options, 'debugColumns', 'getAllFlatColumnsById')),
        getAllLeafColumns: memo(() => [table.getAllColumns(), table._getOrderColumnsFn()], (allColumns, orderColumns) => {
            const leafColumns = allColumns.flatMap((column) => column.getLeafColumns());
            return orderColumns(leafColumns);
        }, getMemoOptions(options, 'debugColumns', 'getAllLeafColumns')),
        getColumn: (columnId) => {
            const column = table._getAllFlatColumnsById()[columnId];
            if (process.env.NODE_ENV !== 'production' && !column) {
                console.error(`[Table] Column with id '${columnId}' does not exist.`);
            }
            return column;
        },
    };
    Object.assign(table, coreInstance);
    for (let index = 0; index < table._features.length; index++) {
        const feature = table._features[index];
        (_c = feature === null || feature === void 0 ? void 0 : feature.createTable) === null || _c === void 0 ? void 0 : _c.call(feature, table);
    }
    return table;
}
