/**
 * Состояние приложения
 */

import _ from 'lodash';
import Immutable from 'immutable';
import { ReduceStore } from 'flux/utils';
import { ActionTypes } from 'constants/Common';
import dispatcher from 'dispatcher';
import Url from 'lib/Url';

import UserStore from 'stores/Users';
import DepartmentStore from 'stores/Departments';
import GroupStore from 'stores/Groups';

const OBJECT_TYPES = {
    users: 'user',
    departments: 'department',
    groups: 'group',
    domains: 'domain',
    dkim: 'dkim',
    admins: 'user',
};

const SUBSECTION_IDENTIFIERS = [
    'member', 'admin',
];

const STORE_MAP = {
    user: UserStore,
    department: DepartmentStore,
    group: GroupStore,
};

const END_OF_PROCESS = {
    _inProgress: false, _progressCaption: null,
};

const PROCESSABLE_ERRORS = ['NOT_FOUND', 'UNSAFE'];

const util = {

    getDepartmentParents(id, data) {
        const index = (data ? data.departments : null) || {};
        let department = util.getDepartment(id, index);
        let parents = [];

        if (department) {
            if (department.parents) {
                parents = department.parents;
            } else {
                let parent = department.parent;

                while (parent) {
                    parents.unshift(parent);
                    department = util.getDepartment(parent, index);
                    parent = department ? department.parent : null;
                }
            }
        }

        return parents.map(item => ({ id: item, type: 'department' }));
    },

    getDepartment(id, data) {
        let department = data[id || '1']; // fallback на корневой отдел

        if (department) {
            return department;
        }

        department = DepartmentStore.get(id);

        return department ? department.toJS() : null;
    },

    getUserParents(id, data) {
        const index = (data ? data.users : null) || {};
        const user = index[id];

        if (!user) {
            return [];
        }

        return util.getDepartmentParents(user.department, data).concat([
            { id: user.department, type: 'department' },
        ]);
    },

    pickParents(objectChain, data) {
        const object = objectChain[0];
        let parents = [];

        // берем родителей, только если в цепочке 1 объект
        if (object && objectChain.length === 1) {
            switch (object.type) {
                case 'user':
                    parents = util.getUserParents(object.id, data);
                    break;
                case 'department':
                    parents = util.getDepartmentParents(object.id, data);
                    break;
            }
        }

        return parents.concat(objectChain);
    },

    getStateId() {
        let stateId = Url.getCurrentPath() || '';
        const root = Url.getRoot();

        if (stateId.indexOf(root) === 0) {
            stateId = stateId.substring(root.length);
        }

        return decodeURIComponent(stateId).replace(/^\//, '');
    },

    getSections(stateId) {
        if (!stateId) {
            stateId = util.getStateId();
        }

        if (stateId === '' || stateId === 'structure') {
            return [{
                value: 'root',
                object: { id: '1', type: 'department' },
            }];
        }

        let path = [];
        const sections = stateId.split('/')
            .map((item, index) => {
                let id = (item.match(/^(\d+)$/) || [])[0];

                if (OBJECT_TYPES[path[index - 1]] && SUBSECTION_IDENTIFIERS.indexOf(item) === -1) {
                    id = item;
                }

                if (id && path.length) {
                    let objectType;

                    // ищем тип объекта в массиве path
                    for (let i = path.length - 1; i >= 0 && !objectType; i--) {
                        objectType = OBJECT_TYPES[path[i]];
                    }

                    const section = {
                        value: item,
                        object: { id, type: objectType, path },
                    };

                    path = [];

                    return section;
                }
                path.push(item);

                return { value: item };
            });

        return sections;
    },

    toStatusCode(statusData) {
        if (statusData instanceof Error) {
            const textCode = _.get(statusData, 'response.statusText', '')
                .replace(/\s+/g, '_').toUpperCase();
            const numericCode = statusData.status || _.get(statusData, 'response.status');

            return textCode || numericCode;
        }

        return statusData;
    },

    hasUnprocessableError(data) {
        const status = util.toStatusCode(data.status);

        return (data.status instanceof Error) && PROCESSABLE_ERRORS.indexOf(status) === -1;
    },

};

class ApplicationStore extends ReduceStore {
    getInitialState() {
        return new Immutable.Map();
    }

    reduce(state, action) {
        const { data, type } = action;

        switch (type) {
            case ActionTypes.START_PROCESS:
                return state.merge(this._getProcessData(data));

            case ActionTypes.END_PROCESS:
                return state.merge(END_OF_PROCESS);

            case ActionTypes.RECEIVE_REQUEST_DATA:
                return state
                    .merge(this._getPreviousStateData())
                    .merge(this._pickStorableData(data))
                    .merge(END_OF_PROCESS);

            case ActionTypes.RECEIVE_APPLICATION_DATA:
                return state
                    .merge(this._getBasicStorableData(data))
                    .merge(data);
        }

        return state;
    }

    _getPreviousStateData() {
        const primaryObject = this.getPrimaryObject();

        if (primaryObject) {
            return { _previousState: { primaryObject } };
        }
    }

    _getProcessData(data) {
        return {
            _inProgress: true,
            _progressCaption: data ? data.caption : null,
        };
    }

    _pickStorableData(data) {
        if (!data || util.hasUnprocessableError(data)) {
            return;
        }

        const storableData = this._getBasicStorableData(data);

        const objectChain = storableData._sections
            .map(item => item ? item.object : null)
            .filter(Boolean);

        storableData._objectChain = util.pickParents(objectChain, data.response);

        return storableData;
    }

    /**
     * Возвращает объект с обновленными stateId/sections/requestStatus
     * @param {Object} data
     * @returns {{_stateId: *, _sections: *, _requestStatus: *}}
     * @private
     */
    _getBasicStorableData(data) {
        const status = _.get(data, 'status');
        const stateId = util.getStateId();

        return {
            _stateId: stateId,
            _sections: util.getSections(stateId),
            _requestStatus: util.toStatusCode(status),
        };
    }

    getPrimaryType() {
        const firstSection = this.getSections()[0];

        return firstSection ? firstSection.value : null;
    }

    getSections() {
        const sections = this.get('_sections');

        return sections ? sections.toJS() : [];
    }

    getSectionValues() {
        return this.getSections().map(section => section.value);
    }

    getSection(index) {
        return this.getSections()[index];
    }

    getObjectChain() {
        const chain = this.get('_objectChain');

        return chain ? chain.toJS() : [];
    }

    getStateId() {
        return this.get('_stateId');
    }

    getRequestStatus() {
        return this.get('_requestStatus');
    }

    getStateType() {
        return this.getSections()
            .map(item => item && !item.object ? item.value : null)
            .filter(Boolean)
            .join('/');
    }

    getPrimaryObject() {
        const chain = this.getObjectChain();

        if (this.getPrimaryType() === 'groups') {
            return chain[0];
        }

        return chain[chain.length - 1];
    }

    getSecondaryObject() {
        if (this.getPrimaryType() === 'groups') {
            return this.getObjectChain()[1] || null;
        }
    }

    getSectionName(index = 0) {
        const sections = this.getSections();
        const section = sections[index];

        if (section) {
            return section.value ? decodeURIComponent(section.value) : '';
        }
    }

    getSubsectionName() {
        if (this.getPrimaryType() === 'groups') {
            const sections = this.getSections();

            if (!sections[1] || sections[1].object) {
                return 'all';
            }

            return sections[1].value;
        }

        if (this.getPrimaryType() === 'customization') {
            const sections = this.getSections();

            return sections[1] ? sections[1].value : null;
        }

        if (this.getPrimaryType() === 'domains') {
            const sections = this.getSections();
            let subsectionName = sections[1] && sections[1].value;

            try {
                subsectionName = subsectionName && decodeURIComponent(subsectionName);
            } catch (e) {
                // empty
            }

            return subsectionName || null;
        }
    }

    findStored(object) {
        if (!object || !object.type || !object.id) {
            return;
        }

        const store = STORE_MAP[object.type];

        if (store) {
            return store.get(String(object.id));
        }
    }

    findCurrent() {
        return this.findStored(this.getPrimaryObject());
    }

    getPreviousState() {
        return this.get('_previousState') || new Immutable.Map();
    }

    hasPreviouslyVisited(object) {
        const previousState = this.getPreviousState().toJS();

        return _.isMatch(previousState.primaryObject, object);
    }

    hasOpen(object) {
        const chain = this.getObjectChain();

        for (let i = 0; i < chain.length; i++) {
            if (_.isMatch(chain[i], object)) {
                return true;
            }
        }

        return false;
    }

    getProcessState() {
        return {
            running: this.get('_inProgress'),
            caption: this.get('_progressCaption'),
        };
    }

    isBusy() {
        return this.get('_inProgress');
    }

    /**
     * Возвращает значение по ключу из состояния хранилища
     * @method  get
     * @param   {String}  key
     * @returns {String}
     */
    get(key) {
        return this._state.getIn(key.split('.'));
    }

    pick(...keys) {
        const props = {};

        keys.forEach(key => props[key] = this.get(key));

        return props;
    }
}

export default window.ya.connect.ApplicationStore = new ApplicationStore(dispatcher);
