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

export class ColumnsStore extends Store {
    constructor({items, storeName = 'ColumnsStore', ...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.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.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 collisions = [];
        let mergedItems = defaults.map(defaultSpec => {
            let persistentItem = _.find(items, {id: defaultSpec.id});
            if (persistentItem) {
                // Prefer some values from defaults
                let {isAvailable, ...attrs} = persistentItem;
                if (!isAvailable && defaultSpec.isAvailable()) {
                    collisions.push(persistentItem.id);
                }
                let mergedItem = {
                    ...defaultSpec,
                    ...attrs
                };
                return mergedItem;
            } else {
                return defaultSpec;
            }
        });

        if (collisions.length > 0) {
            mergedItems = this.resolveCollisions(this.getSortedByPosition(mergedItems), collisions);
        }

        return mergedItems;
    }

    /**
     * Re arrange items using this logic: the id's specified on columnIds should be placed at the end
     * of list of columns. But before the last non editable column.
     *
     * Collision is a case when column currently available by business logic but previously were saved as unavailable.
     * so, to prevents appearing it in a random place we rearrange it.
     * @param {Array} items a list of columns spec
     * @param {Array} columnIds a list of ids of columns which position detected as incorrect
     * @return {Array}
     */
    resolveCollisions(items, columnIds) {
        // remove collision items
        const itemsWithCollision = _.remove(items, item => _.includes(columnIds, item.id));

        // find new insertion position
        let insertAt = _.findLastIndex(items, item => item.isCustomizable);
        insertAt = Math.max(insertAt, 0);

        items.splice(insertAt + 1, 0, ...itemsWithCollision);
        items = this.setupItemsPosition(items);
        return items;
    }

    loadAll() {
        let defaults = this.getFromDefaults();
        let savedItems = loadState(this.stateName) || [];
        let items = this.mergeItemsWithDefaults(savedItems, defaults);

        return Promise.resolve(items);
    }

    save() {
        let items = this.getRawData().map(item => {
            const {id, position, isVisible, isAvailable} = item;
            return {
                id,
                position,
                isVisible,
                isAvailable: isAvailable()
            };
        });
        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));
};
