import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';

import {withListProps, ASC, passedWithListPropsTypes} from './HOCs/withListProps';

export const passedListManagerPropsTypes = {
    ...passedWithListPropsTypes,
    filteredRows: PropTypes.array.isRequired,
    visibleRows: PropTypes.array.isRequired
};

// TODO add catch errors (fieldsRules do not have real ids)
class ListManagerInner extends React.PureComponent {
    static propTypes = {
        ...passedWithListPropsTypes,
        rows: PropTypes.array.isRequired,
        children: PropTypes.element.isRequired,
        fieldsRules: PropTypes.shape({
            isFiltering: PropTypes.bool,
            filterByText: PropTypes.func,
            filterByParams: PropTypes.func,
            sort: PropTypes.func
        })
    };

    static defaultProps = {
        fieldsRules: {}
    };

    defaultFilterByText(value, filterText) {
        value = String(value).toLowerCase();

        return value.includes(filterText);
    }

    getFilterByText(field) {
        const customFilterByText = field.filterByText;

        if (_.isFunction(customFilterByText)) {
            return customFilterByText;
        }

        return this.defaultFilterByText;
    }

    defaultSort(prevValue, nextValue) {
        if (_.isString(prevValue)) {
            prevValue = prevValue.toLowerCase();
        }

        if (_.isString(nextValue)) {
            nextValue = nextValue.toLowerCase();
        }

        if (prevValue === nextValue) {
            return 0;
        }

        return prevValue >= nextValue ? 1 : -1;
    }

    getSort(field) {
        const customSort = field.sort;

        if (_.isFunction(customSort)) {
            return customSort;
        }

        return this.defaultSort;
    }

    getRowsByFilterText(rows, filterText, fieldsRules) {
        const fieldsRulesKeys = Object.keys(fieldsRules);

        if (fieldsRulesKeys.length === 0) {
            return rows;
        }

        filterText = filterText.toLowerCase();

        return rows.filter(row => {
            return fieldsRulesKeys
                .filter(fieldName => fieldsRules[fieldName].isFiltering === true)
                .some(fieldName => {
                    const filterByText = this.getFilterByText(fieldsRules[fieldName]);

                    return filterByText(row[fieldName], filterText);
                });
        });
    }

    getRowsByFieldParams(rows, fieldsParams, fieldsRules) {
        return rows.filter(row => {
            return Object.keys(fieldsParams).every(fieldName => {
                const filteredValue = row[fieldName];
                const fieldParams = fieldsParams[fieldName];
                const {filterByParams} = fieldsRules[fieldName];

                return filterByParams(filteredValue, fieldParams);
            });
        });
    }

    getRowsByPagination(rows, activePage, pageSize) {
        const beginIndex = activePage * pageSize;

        return rows.slice(beginIndex, beginIndex + pageSize);
    }

    getRowsBySorting(rows, sorting, fieldsRules) {
        const {field, direction} = sorting;

        if (!_.isString(field)) {
            return rows;
        }

        const sort = this.getSort(fieldsRules[field]);

        return rows.sort((preRow, nextRow) => {
            return sort(preRow[field], nextRow[field]) * (direction === ASC ? 1 : -1);
        });
    }

    getProps() {
        const {
            rows,
            activePage,
            pageSize,
            filterText,
            fieldsRules,
            fieldsParams,
            sorting
        } = this.props;

        let filteredRows = this.getRowsByFieldParams(rows, fieldsParams, fieldsRules);
        filteredRows = this.getRowsByFilterText(filteredRows, filterText, fieldsRules);
        const sortedFilteredRows = this.getRowsBySorting(filteredRows, sorting, fieldsRules);
        const visibleRows = this.getRowsByPagination(sortedFilteredRows, activePage, pageSize);

        const {children, fieldsRules: _fieldsRules, rows: _rows, ...props} = this.props;

        return {
            filteredRows,
            visibleRows,
            ...props
        };
    }

    render() {
        return React.cloneElement(this.props.children, {...this.getProps()});
    }
}

export const ListManager = withListProps(ListManagerInner);
