import {Store} from './Store';
import _ from 'lodash';

export class ColumnsStore<V> extends Store<V> {
    defaults: any;
    position: any;
    visibleLimit: number;

    constructor({items, storeName = 'ColumnsStore', visibleLimit = 0, ...params}) {
        super({
            stateName: params.stateName,
            fields: {
                id: {
                    type: String,
                    primaryKey: true
                },

                // custom test automation id, default is id property
                testAutomationId: {
                    type: String,
                    getter: item => {
                        return item.testAutomationId || item.id;
                    }
                },

                // zero based index of column. Columns ordered on grid according to this position
                position: Number,

                // label of column for rendering on grid header
                headerLabel: String,

                // description for grid columns editor
                description: String,

                // required columns can't be hidden on grid columns editor
                isRequired: Boolean,

                // user customization of column visibility
                isVisible: Boolean,

                // business logic checker to be called on particular column
                isAvailable: {
                    type: 'custom',
                    fieldFilterHandler: (fieldValue, filter) => {
                        return filter(fieldValue());
                    }
                },

                // customizable columns can be edited by user
                isCustomizable: Boolean,

                // additional filter to be able to getting columns for short view only
                // (when grid represented with short/full view)
                isForShortView: Boolean
            }
        });
        this.defaults = items;
        this.visibleLimit = visibleLimit;
        this.register(storeName);
    }

    setVisibleColumns(listIds) {
        let items = this.getRawData().map(item => {
            return {
                ...item,
                isVisible: item.isCustomizable ? _.includes(listIds, item.id) : true
            };
        });

        this.setItems(items, false);
    }

    getColumnOffset(id) {
        let columns = this.getRawData();
        let currentPosition = _.find(columns, {id}).position;
        let columnsBefore = _.filter(columns, column => {
            const {isCustomizable, position} = column;
            return position < currentPosition && !isCustomizable;
        });

        return columnsBefore.length;
    }

    /**
     * Setup new visual position for item
     * @param {Object} record
     * @param {Number} newPosition zero based index
     */
    setVisualPosition(record, newPosition) {
        if (record.position === newPosition) {
            return;
        }

        // get items ordered by position
        let items = this.getSortedByPosition(this.getRawData());

        // remove item
        _.remove(items, item => {
            return (item as any).position === record.position;
        });

        let head = _.take(items, newPosition);
        let tail = _.takeRight(items, items.length - newPosition);

        let reorderedItems = [...head, record, ...tail];

        // reset position
        reorderedItems = this.setupItemsPosition(reorderedItems);

        this.setItems(reorderedItems, true);
    }

    setupItemsPosition(items) {
        for (let i = 0; i < items.length; i++) {
            items[i].position = i;
        }
        return items;
    }

    getSortedByPosition(items) {
        return items.sort((a, b) => a.position - b.position);
    }

    isColumnCustomizable(id) {
        let columnSpec = _.find(this.getRawData(), {id});
        return columnSpec && columnSpec.isCustomizable;
    }

    getFromDefaults() {
        const defaults = {
            isVisible: true,
            isAvailable: _.stubTrue,
            isCustomizable: true,
            isForShortView: false,
            isRequired: false
        };
        const normalizeColumnSpec = (columnSpec, index) => {
            return {
                ...defaults,
                ...columnSpec,
                position: index
            };
        };

        return this.defaults.map(normalizeColumnSpec);
    }

    mergeItemsWithDefaults(items, defaults) {
        let columns = [];
        let extraColumns = [];

        if (items && items.length) {
            defaults.forEach(defaultSpec => {
                let item = _.find(items, {id: defaultSpec.id}) as any;
                if (item && defaultSpec.isAvailable()) {
                    columns.push({...defaultSpec, ...item});
                } else {
                    extraColumns.push(defaultSpec);
                }
            });
            columns = this.getSortedByPosition(columns);
        } else {
            extraColumns = defaults;
        }
        if (extraColumns.length) {
            if (this.visibleLimit > 0) {
                let visibleLimit =
                    this.visibleLimit - columns.filter(this.isCustomizableVisibleColumn).length;
                extraColumns = this.adjustExtraColumnsVisibility(extraColumns, visibleLimit);
            }
            columns = this.addExtraColumns(columns, extraColumns);
        }

        return columns;
    }

    adjustExtraColumnsVisibility(columns, visibleLimit) {
        let visibleCount = columns.filter(this.canBeHidden).length;
        if (visibleCount <= visibleLimit) {
            return columns;
        }
        for (let index = columns.length - 1; index > -1; index--) {
            let column = columns[index];
            if (this.canBeHidden(column)) {
                column.isVisible = false;
                visibleCount--;
            }
            if (visibleCount <= visibleLimit) {
                break;
            }
        }
        return columns;
    }

    isCustomizableVisibleColumn({isVisible, isCustomizable}) {
        return isVisible && isCustomizable;
    }

    canBeHidden({isVisible, isCustomizable, isRequired, isAvailable}) {
        return isVisible && isCustomizable && !isRequired && isAvailable();
    }

    /**
     * Add additional columns using this logic:
     * it should be placed at the end column's list, but before the last non editable column.
     *
     * @param {Object[]} columns
     * @param {Object[]} additionalColumns
     * @return {Object[]}
     */
    addExtraColumns(columns, additionalColumns) {
        // Find insertion position
        let insertAt = _.findLastIndex(columns, item => (item as any).isCustomizable);
        insertAt = Math.max(insertAt, 0);

        columns.splice(insertAt + 1, 0, ...additionalColumns);
        columns = this.setupItemsPosition(columns);
        return columns;
    }

    loadAll() {
        let defaults = this.getFromDefaults();
        let savedItems = (loadState(this.stateName) || []).map(({id, position, isVisible}) => ({
            id,
            position,
            isVisible
        }));

        let items = this.mergeItemsWithDefaults(savedItems, defaults);

        return Promise.resolve(items);
    }

    save() {
        let items = this.getRawData()
            .filter(({isAvailable}) => isAvailable())
            .map(({id, position, isVisible}) => ({id, position, isVisible}));

        saveState(this.stateName, items);
    }

    getGridColumns(filterByShortView = false) {
        return this.getSorted(this.getRawData()).filter(
            ({isVisible, isAvailable, isForShortView}) => {
                let result = isVisible && isAvailable();
                if (filterByShortView) {
                    result = result && isForShortView;
                }
                return result;
            }
        );
    }
}

const loadState = key => {
    const str = localStorage.getItem(key);
    let result = undefined;
    if (!_.isNil(str)) {
        try {
            result = JSON.parse(str);
        } catch (e) {
            result = undefined;
        }
    }

    return result;
};

const saveState = (storageKey, items) => {
    localStorage.setItem(storageKey, JSON.stringify(items));
};
