import {types, getParent, SnapshotIn, flow, getEnv, getRoot, IAnyModelType} from 'mobx-state-tree';
import {iRendererStore} from './iRenderer';
import {resolveVariable} from '../utils/tpl-builtin';
import isEqual = require('lodash/isEqual');
import find = require('lodash/find');
import {isBreakpoint, createObject, isObject, isVisible, guid, findTree, flattenTree, eachTree, difference} from '../utils/helper';
import {evalExpression} from '../utils/tpl';

export const Column = types
    .model('Column', {
        label: types.optional(types.frozen(), undefined),
        type: types.string,
        name: types.maybe(types.string),
        groupName: '',
        toggled: false,
        toggable: true,
        expandable: false,
        isPrimary: false,
        searchable: types.maybe(types.frozen()),
        sortable: false,
        filterable: types.optional(types.frozen(), undefined),
        fixed: '',
        index: 0,
        rawIndex: 0,
        breakpoint: types.optional(types.frozen(), undefined),
        pristine: types.optional(types.frozen(), undefined),
        remark: types.optional(types.frozen(), undefined),
        className: ''
    })
    .actions(self => ({
        toggleToggle() {
            self.toggled = !self.toggled;
            const table = getParent(self, 2) as ITableStore;

            if (!table.activeToggaleColumns.length) {
                self.toggled = true;
            }

            table.persistSaveToggledColumns();
        },
        setToggled(value: boolean) {
            self.toggled = value;
        }
    }));

export type IColumn = typeof Column.Type;
export type SColumn = SnapshotIn<typeof Column>;

export const Row = types
    .model('Row', {
        id: types.identifier,
        parentId: '',
        key: types.string,
        pristine: types.frozen({} as any),
        data: types.frozen({} as any),
        rowSpans: types.frozen({} as any),
        index: types.number,
        newIndex: types.number,
        expandable: false,
        isHover: false,
        children: types.optional(types.array(types.late((): IAnyModelType => Row)), []),
        depth: types.number // 当前children位于第几层，便于使用getParent获取最顶层TableStore
    })
    .views(self => ({
        get checked(): boolean {
            return (getParent(self, self.depth * 2) as ITableStore).isSelected(self as IRow);
        },

        get modified() {
            if (!self.data) {
                return false;
            }

            return Object.keys(self.data).some(key => !isEqual(self.data[key], self.pristine[key]));
        },

        getDataWithModifiedChilden() {
            let data = {
                ...self.data
            };

            if (data.children && self.children) {
                data.children = self.children.map(item => item.getDataWithModifiedChilden());
            }

            return data;
        },

        get expanded(): boolean {
            return (getParent(self, self.depth * 2) as ITableStore).isExpanded(self as IRow);
        },

        get moved() {
            return self.index !== self.newIndex;
        },

        get locals(): any {
            return createObject(
                createObject((getParent(self, self.depth * 2) as ITableStore).data, {
                    index: self.index
                }),
                self.data
            );
        },

        get checkable(): boolean {
            const table = getParent(self, self.depth * 2) as ITableStore;
            return table && table.itemCheckableOn ? evalExpression(table.itemCheckableOn, (self as IRow).locals) : true;
        },

        get draggable(): boolean {
            const table = getParent(self, self.depth * 2) as ITableStore;
            return table && table.itemDraggableOn ? evalExpression(table.itemDraggableOn, (self as IRow).locals) : true;
        }
    }))
    .actions(self => ({
        toggle() {
            (getParent(self, self.depth * 2) as ITableStore).toggle(self as IRow);
        },

        toggleExpanded() {
            (getParent(self, self.depth * 2) as ITableStore).toggleExpanded(self as IRow);
        },

        change(values: object, savePristine?: boolean) {
            self.data = {
                ...self.data,
                ...values
            };

            savePristine &&
                (self.pristine = {
                    ...self.data
                });
        },

        reset() {
            self.newIndex = self.index;
            self.data = self.pristine;
        },

        setIsHover(value: boolean) {
            self.isHover = value;
        }
    }));

export type IRow = typeof Row.Type;
export type SRow = SnapshotIn<typeof Row>;

export const TableStore = iRendererStore
    .named('TableStore')
    .props({
        columns: types.array(Column),
        rows: types.array(Row),
        selectedRows: types.array(types.reference(Row)),
        expandedRows: types.array(types.reference(Row)),
        primaryField: 'id',
        orderBy: '',
        orderDir: types.optional(types.union(types.literal('asc'), types.literal('desc')), 'asc'),
        draggable: false,
        dragging: false,
        selectable: false,
        multiple: true,
        footable: types.frozen(),
        expandConfig: types.frozen(),
        isNested: false,
        columnsTogglable: types.optional(types.union(types.boolean, types.literal('auto')), 'auto'),
        itemCheckableOn: '',
        itemDraggableOn: '',
        hideCheckToggler: false,
        combineNum: 0
    })
    .views(self => {
        function getFilteredColumns() {
            return self.columns.filter(
                item =>
                    isVisible(item.pristine, self.data) &&
                    (item.type === '__checkme'
                        ? self.selectable && !self.dragging && !self.hideCheckToggler && self.rows.length
                        : item.type === '__dragme'
                        ? self.dragging
                        : item.type === '__expandme'
                        ? (getFootableColumns().length || self.isNested) && !self.dragging
                        : (item.toggled || !item.toggable) &&
                          (!self.footable || !item.breakpoint || !isBreakpoint(item.breakpoint)))
            );
        }

        function getFootableColumns() {
            return self.columns.filter(item =>
                item.type === '__checkme' || item.type === '__dragme' || item.type === '__expandme'
                    ? false
                    : (item.toggled || !item.toggable) &&
                      (self.footable && item.breakpoint && isBreakpoint(item.breakpoint))
            );
        }

        function getLeftFixedColumns() {
            if (self.dragging) {
                return [];
            }

            return getFilteredColumns().filter(item => item.fixed === 'left');
        }

        function getRightFixedColumns() {
            if (self.dragging) {
                return [];
            }

            return getFilteredColumns().filter(item => item.fixed === 'right');
        }

        function isSelected(row: IRow): boolean {
            return !!~self.selectedRows.indexOf(row);
        }

        function isExpanded(row: IRow): boolean {
            return !!~self.expandedRows.indexOf(row);
        }

        function getTogglable() {
            if (self.columnsTogglable === 'auto') {
                return self.columns.filter(item => !/^__/.test(item.type)).length > 5;
            }

            return self.columnsTogglable;
        }

        function getToggableColumns() {
            return self.columns.filter(item => isVisible(item.pristine, self.data) && item.toggable !== false);
        }

        function getActiveToggableColumns() {
            return getToggableColumns().filter(item => item.toggled);
        }

        function getModifiedRows(rows:IRow[] = [], modifiedRows:IRow[] = []) {
            rows = rows && rows.length ? rows : self.rows;
            rows.forEach((item:IRow) => {
                if (item.children && item.children.length) {
                    getModifiedRows(item.children, modifiedRows);
                }
                let diff = difference(item.data, item.pristine);
                let hasDifference = Object.keys(diff).length;
                if (hasDifference) {
                    modifiedRows.push(item);
                }
            });
            return modifiedRows;
        }

        function getModified() {
            return getModifiedRows().length;
        }

        function getMovedRows() {
            return flattenTree(self.rows).filter((item: IRow) => item.moved);
        }

        function getMoved() {
            return getMovedRows().length;
        }

        function getHoverIndex(): number {
            return self.rows.findIndex(item => item.isHover);
        }

        function getUnSelectedRows() {
            return self.rows.filter(item => !item.checked);
        }

        function getData(superData: any): any {
            return createObject(superData, {
                items: self.rows.map(item => item.data),
                selectedItems: self.selectedRows.map(item => item.data),
                unSelectedItems: getUnSelectedRows().map(item => item.data)
            });
        }

        function getColumnGroup(): Array<{
            label: string;
            index: number;
            colSpan: number;
        }> {
            const columsn = getFilteredColumns();
            const len = columsn.length;

            if (!len) {
                return [];
            }

            const result: Array<{
                label: string;
                index: number;
                colSpan: number;
            }> = [
                {
                    label: columsn[0].groupName,
                    colSpan: 1,
                    index: columsn[0].index
                }
            ];

            //  如果是勾选栏，让它和下一列合并。
            if (columsn[0].type === '__checkme' && columsn[1]) {
                result[0].label = columsn[1].groupName;
            }

            for (let i = 1; i < len; i++) {
                let prev = result[result.length - 1];
                const current = columsn[i];

                if (current.groupName === prev.label) {
                    prev.colSpan++;
                } else {
                    result.push({
                        label: current.groupName,
                        colSpan: 1,
                        index: current.index
                    });
                }
            }

            if (result.length === 1 && !result[0].label) {
                result.pop();
            }

            return result;
        }

        return {
            get filteredColumns() {
                return getFilteredColumns();
            },

            get footableColumns() {
                return getFootableColumns();
            },

            get leftFixedColumns() {
                return getLeftFixedColumns();
            },

            get rightFixedColumns() {
                return getRightFixedColumns();
            },

            get toggableColumns() {
                return getToggableColumns();
            },

            get activeToggaleColumns() {
                return getActiveToggableColumns();
            },

            get someChecked() {
                return !!self.selectedRows.length;
            },

            get allChecked(): boolean {
                return !!(
                    self.selectedRows.length === (self as ITableStore).checkableRows.length &&
                    (self as ITableStore).checkableRows.length
                );
            },

            isSelected,

            get allExpanded() {
                return !!(self.expandedRows.length === self.rows.length && self.rows.length);
            },

            isExpanded,

            get toggable() {
                return getTogglable();
            },

            get modified() {
                return getModified();
            },

            get modifiedRows() {
                return getModifiedRows();
            },

            get unSelectedRows() {
                return getUnSelectedRows();
            },

            get checkableRows() {
                return self.rows.filter(item => item.checkable);
            },

            get moved() {
                return getMoved();
            },

            get movedRows() {
                return getMovedRows();
            },

            get hoverIndex() {
                return getHoverIndex();
            },

            getData,

            get columnGroup() {
                return getColumnGroup();
            },

            getRowById(id: string) {
                return findTree(self.rows, item => item.id === id);
            }
        };
    })
    .actions(self => {
        function update(config: Partial<STableStore>) {
            config.primaryField !== void 0 && (self.primaryField = config.primaryField);
            config.selectable !== void 0 && (self.selectable = config.selectable);
            config.columnsTogglable !== void 0 && (self.columnsTogglable = config.columnsTogglable);
            config.draggable !== void 0 && (self.draggable = config.draggable);

            if (typeof config.orderBy === 'string') {
                setOrderByInfo(config.orderBy, config.orderDir === 'desc' ? 'desc' : 'asc');
            }

            config.multiple !== void 0 && (self.multiple = config.multiple);
            config.footable !== void 0 && (self.footable = config.footable);
            config.expandConfig !== void 0 && (self.expandConfig = config.expandConfig);
            config.itemCheckableOn !== void 0 && (self.itemCheckableOn = config.itemCheckableOn);
            config.itemDraggableOn !== void 0 && (self.itemDraggableOn = config.itemDraggableOn);
            config.hideCheckToggler !== void 0 && (self.hideCheckToggler = !!config.hideCheckToggler);

            config.combineNum !== void 0 && (self.combineNum = parseInt(config.combineNum as any, 10) || 0);

            if (config.columns && Array.isArray(config.columns)) {
                let columns: Array<SColumn> = config.columns.concat();

                columns.unshift({
                    type: '__expandme',
                    toggable: false,
                    // fixed: 'left',
                    className: 'Table-expandCell'
                });

                columns.unshift({
                    type: '__checkme',
                    toggable: false,
                    fixed: 'left',
                    className: 'Table-checkCell'
                });

                columns.unshift({
                    type: '__dragme',
                    toggable: false,
                    className: 'Table-dragCell'
                });

                columns = columns.map((item, index) => ({
                    ...item,
                    index,
                    rawIndex: index - 3,
                    type: item.type || 'plain',
                    pristine: item,
                    toggled: item.toggled !== false,
                    breakpoint: item.breakpoint,
                    isPrimary: index === 3
                }));

                self.columns.replace(columns as any);
            }
        }

        function combineCell(arr: Array<SRow>, keys: Array<string>): Array<SRow> {
            if (!keys.length || !arr.length) {
                return arr;
            }

            const key: string = keys.shift() as string;
            let rowIndex = 0;
            let row = arr[rowIndex];
            row.rowSpans[key] = 1;
            let value = resolveVariable(key, row.data);
            for (let i = 1, len = arr.length; i < len; i++) {
                const current = arr[i];
                if (resolveVariable(key, current.data) == value) {
                    row.rowSpans[key] += 1;
                    current.rowSpans[key] = 0;
                } else {
                    if (row.rowSpans[key] > 1) {
                        combineCell(arr.slice(rowIndex, i), keys.concat());
                    }

                    rowIndex = i;
                    row = current;
                    row.rowSpans[key] = 1;
                    value = resolveVariable(key, row.data);
                }
            }

            if (row.rowSpans[key] > 1 && keys.length) {
                combineCell(arr.slice(rowIndex, arr.length), keys.concat());
            }

            return arr;
        }

        function autoCombineCell(arr: Array<SRow>, columns: Array<IColumn>, maxCount: number): Array<SRow> {
            if (!columns.length || !maxCount || !arr.length) {
                return arr;
            }

            const keys: Array<string> = [];
            for (let i = 0; i < maxCount; i++) {
                const column = columns[i];

                // maxCount 可能比实际配置的 columns 还有多。
                if (!column) {
                    break;
                }

                if ('__' === column.type.substring(0, 2)) {
                    maxCount++;
                    continue;
                }

                const key = column.name;
                if (!key) {
                    break;
                }
                keys.push(key);
            }

            return combineCell(arr, keys);
        }

        function initChildren(children: Array<any>, depth: number, pindex: number, parentId: string): any {
            depth += 1;
            return children.map((item, key) => {
                item = isObject(item)
                    ? item
                    : {
                          item
                      };
                const id = guid();

                return {
                    // id: String(item && (item as any)[self.primaryField] || `${pindex}-${depth}-${key}`),
                    id: id,
                    parentId,
                    key: String(`${pindex}-${depth}-${key}`),
                    depth: depth,
                    index: key,
                    newIndex: key,
                    pristine: item,
                    data: item,
                    rowSpans: {},
                    modified: false,
                    children: item && Array.isArray(item.children) ? initChildren(item.children, depth, key, id) : [],
                    expandable: !!(
                        (item && Array.isArray(item.children) && item.children.length) ||
                        (self.footable && self.footableColumns.length)
                    )
                };
            });
        }

        function initRows(rows: Array<any>, getEntryId?: (entry: any, index: number) => string) {
            self.selectedRows.clear();
            self.expandedRows.clear();

            let arr: Array<SRow> = rows.map((item, key) => {
                let id = getEntryId ? getEntryId(item, key) : guid();
                return {
                    // id: getEntryId ? getEntryId(item, key) : String(item && (item as any)[self.primaryField] || `${key}-1-${key}`),
                    id: id,
                    key: String(`${key}-1-${key}`),
                    depth: 1, // 最大父节点默认为第一层，逐层叠加
                    index: key,
                    newIndex: key,
                    pristine: item,
                    data: item,
                    rowSpans: {},
                    modified: false,
                    children: item && Array.isArray(item.children) ? initChildren(item.children, 1, key, id) : [],
                    expandable: !!(
                        (item && Array.isArray(item.children) && item.children.length) ||
                        (self.footable && self.footableColumns.length)
                    )
                };
            });

            if (self.combineNum) {
                arr = autoCombineCell(arr, self.columns, self.combineNum);
            }

            self.rows.replace(arr as Array<IRow>);
            self.isNested = self.rows.some(item => item.children.length);

            const expand = self.footable && self.footable.expand;
            if (expand === 'first' || (self.expandConfig && self.expandConfig.expand === 'first')) {
                self.rows.length && self.expandedRows.push(self.rows[0]);
            } else if (
                (expand === 'all' && !self.footable.accordion) ||
                (self.expandConfig && self.expandConfig.expand === 'all' && !self.expandConfig.accordion)
            ) {
                self.expandedRows.replace(self.rows);
            }

            self.dragging = false;
        }

        function updateSelected(selected: Array<any>, valueField?: string) {
            self.selectedRows.clear();
            self.rows.forEach(item => {
                if (~selected.indexOf(item.pristine)) {
                    self.selectedRows.push(item);
                } else if (find(selected, a => a[valueField || 'value'] == item.pristine[valueField || 'value'])) {
                    self.selectedRows.push(item);
                }
            });
        }

        function toggleAll() {
            if (self.allChecked) {
                self.selectedRows.clear();
            } else {
                self.selectedRows.replace(self.checkableRows);
            }
        }

        function toggle(row: IRow) {
            if (!row.checkable) {
                return;
            }

            const idx = self.selectedRows.indexOf(row);

            if (self.multiple) {
                ~idx ? self.selectedRows.splice(idx, 1) : self.selectedRows.push(row);
            } else {
                ~idx ? self.selectedRows.splice(idx, 1) : self.selectedRows.replace([row]);
            }
        }

        function clear() {
            self.selectedRows.clear();
        }

        function toggleExpandAll() {
            if (self.allExpanded) {
                self.expandedRows.clear();
            } else {
                self.expandedRows.replace(self.rows);
            }
        }

        function toggleExpanded(row: IRow) {
            const idx = self.expandedRows.indexOf(row);

            if (~idx) {
                self.expandedRows.splice(idx, 1);
            } else if (self.footable && self.footable.accordion) {
                self.expandedRows.replace([row]);
            } else if (self.expandConfig && self.expandConfig.accordion) {
                let rows = self.expandedRows.filter(item => item.depth !== row.depth);
                rows.push(row);
                self.expandedRows.replace(rows);
            } else {
                self.expandedRows.push(row);
            }
        }

        function collapseAllAtDepth(depth: number) {
            let rows = self.expandedRows.filter(item => item.depth !== depth);
            self.expandedRows.replace(rows);
        }

        function setOrderByInfo(key: string, direction: 'asc' | 'desc') {
            self.orderBy = key;
            self.orderDir = direction;
        }

        function reset() {
            self.rows.forEach(item => item.reset());
            let rows = self.rows.concat();
            eachTree(rows, item => {
                if (item.children) {
                    let rows = item.children.concat().sort((a, b) => a.index - b.index);
                    rows.forEach(item => item.reset());
                    item.children.replace(rows);
                }
            });
            rows.forEach(item => item.reset());
            rows = rows.sort((a, b) => a.index - b.index);
            self.rows.replace(rows);
            self.dragging = false;
        }

        function toggleDragging() {
            self.dragging = !self.dragging;
        }

        function stopDragging() {
            self.dragging = false;
        }

        function exchange(fromIndex: number, toIndex: number, item?: IRow) {
            item = item || self.rows[fromIndex];

            if (item.parentId) {
                const parent: IRow = self.getRowById(item.parentId) as any;
                const offset = parent.children.indexOf(item) - fromIndex;
                toIndex += offset;
                fromIndex += offset;

                const newRows = parent.children.concat();
                newRows.splice(fromIndex, 1);
                newRows.splice(toIndex, 0, item);
                newRows.forEach((item, index) => (item.newIndex = index));
                parent.children.replace(newRows);
                return;
            }

            const newRows = self.rows.concat();
            newRows.splice(fromIndex, 1);
            newRows.splice(toIndex, 0, item);

            newRows.forEach((item, index) => (item.newIndex = index));
            self.rows.replace(newRows);
        }

        function persistSaveToggledColumns() {
            const key =
                location.pathname + self.path + self.toggableColumns.map(item => item.name || item.index).join('-');
            localStorage.setItem(key, JSON.stringify(self.activeToggaleColumns.map(item => item.index)));
        }

        return {
            update,
            initRows,
            updateSelected,
            toggleAll,
            toggle,
            toggleExpandAll,
            toggleExpanded,
            collapseAllAtDepth,
            clear,
            setOrderByInfo,
            reset,
            toggleDragging,
            stopDragging,
            exchange,

            persistSaveToggledColumns,

            // events
            afterAttach() {
                setTimeout(() => {
                    const key =
                        location.pathname +
                        self.path +
                        self.toggableColumns.map(item => item.name || item.index).join('-');
                    const data = localStorage.getItem(key);

                    if (data) {
                        const selectedColumns = JSON.parse(data);
                        self.toggableColumns.forEach(item => item.setToggled(!!~selectedColumns.indexOf(item.index)));
                    }
                }, 200);
            }
        };
    });

export type ITableStore = typeof TableStore.Type;
export type STableStore = SnapshotIn<typeof TableStore>;
