import { useReducer, useMemo, useEffect } from 'react';
import { getUrlParams } from '../../../tools/urlTools';
import { RATING_QUERY_KEY, SCAT_QUERY_KEY, CAT_QUERY_KEY } from '../../../constants/urlParams';
import { productListDataStore } from '../../../store/productListData';

// reducer actions
const TOGGLE_ACTION = 'TOGGLE';
const UPDATE_RATINGS_ACTION = 'UPDATE_RATINGS';
const CLEAR_ACTION = 'CLEAR';

/**
 creates an array of selected ids for each of the queryKeys.
*/
export const initValues = ({
    items: rootItems,
    flatItems
}) => {
    const queryParams = getUrlParams();
    // gets single item selected status
    const getSelectedStatus = (queryKey, selected, id) => {
        // if query params is set, all item's status come exclusively from there, and data values are not used
        if (queryParams) {
            const queryValue = queryParams[queryKey];
            return queryValue === id || (Array.isArray(queryValue) && queryValue.indexOf(id) !== -1);
        }
        // defaults to passed item's selected status
        return selected;
    };

    // recursively reduce the item's items, to get a flat object of queryKey => [ values ]
    const reduceGroup = (carry, item) => {
        const { queryKey, selected, id } = item;

        const isSelected = getSelectedStatus(queryKey, selected, id);

        const newResult = { ...carry };
        if (isSelected) {
            // default list of selected options for this query key
            newResult[queryKey] = newResult[queryKey] || [];
            newResult[queryKey].push(id);
            // if scat, add parent cat to valuestate
            if (queryKey === SCAT_QUERY_KEY) {
                const scatDetails = flatItems[SCAT_QUERY_KEY][id] || {};
                const { parentId } = scatDetails;
                newResult[CAT_QUERY_KEY] = newResult[CAT_QUERY_KEY] || [];
                newResult[CAT_QUERY_KEY].push(parentId);
            }
        }

        // recursive reduce if item has children
        if (!item.items) {
            return newResult;
        }

        return item.items.reduce(
            reduceGroup,
            newResult,
        );
    };

    // first level items are not selectable and have no querykey, they are the placeholders for groups of checkboxes
    const allKeys = rootItems.reduce(
        (carry, rootItem) => rootItem.items.reduce(reduceGroup, carry),
        {
            [RATING_QUERY_KEY]: queryParams[RATING_QUERY_KEY] || []
        }
    );
    return allKeys;
};

const toggleStateReducer = (state, item, isChecked) => {
    const { id, queryKey } = item;
    const queryKeyState = [...(state[queryKey] || [])];
    const existingIndex = queryKeyState.indexOf(id);
    if (isChecked && existingIndex === -1) {
        queryKeyState.push(id);
    } else if (!isChecked && existingIndex !== -1) {
        queryKeyState.splice(existingIndex, 1);
    }

    let newState = {
        ...state,
        [queryKey]: queryKeyState
    };

    // if item is being unchecked, uncheck all of its children as well
    if (!isChecked && item.items && item.items.length) {
        newState = item.items.reduce(
            (carry, subItem) => toggleStateReducer(carry, subItem, false),
            newState
        );
    }
    return newState;
};

const toggleRatingsReducer = (state, nStars, isChecked) => {
    const queryKeyState = [...(state[RATING_QUERY_KEY] || [])];
    const currentMinRating = Math.min(...queryKeyState);
    if (!isChecked && nStars === currentMinRating) {
        queryKeyState.splice(0, 1);
    }
    if (nStars !== currentMinRating) {
        queryKeyState.splice(0, 1, nStars);
    }

    const newState = {
        ...state,
        [RATING_QUERY_KEY]: queryKeyState
    };

    return newState;
};

/**
    Component keeps state of all items that are checked
*/
const mainReducer = (state, action) => {
    const { type } = action;
    switch (type) {
        case TOGGLE_ACTION: {
            const { payload: { item, isChecked } } = action;
            return toggleStateReducer(state, item, isChecked);
        }
        case UPDATE_RATINGS_ACTION: {
            const { payload: { nStars, isChecked } } = action;
            return toggleRatingsReducer(state, nStars, isChecked);
        }
        case CLEAR_ACTION:
            return {};
        default:
            return state;
    }
};

const flattenFilters = rootItems => {
    const flattenGroup = (items, flat = {}, parentId = null) => {
        if (!items) {
            return flat;
        }
        return items.reduce((inner, curItem) => {
            const { queryKey, id, text, url } = curItem;
            return flattenGroup(curItem.items, {
                ...inner,
                [queryKey]: {
                    ...(inner[queryKey] || {}),
                    [id]: { text, url, parentId }
                }
            }, id);
        }, flat);
    };
    return rootItems.reduce(
        (carry, rootItem) => flattenGroup(rootItem.items, carry),
        {}
    );
};

export default function useCategorySidebarState(items, ratingsLabel) {
    // builds a lookup table of all options possible, but flat under their query keys.
    const flatItems = useMemo(
        () => flattenFilters(items),
        [items]
    );

    const {
        setListingPage,
        setFiltersQuery,
        setListingCategories,
        setFiltersDetails
    } = productListDataStore();

    // state management
    const [valuesState, dispatch] = useReducer(mainReducer, { items, flatItems }, initValues);

    // when toggling filters, digest query and send it to the shared store for other components to use
    useEffect(() => {
        // get details of all checked options
        const checkedFiltersDetails = Object.entries(valuesState).reduce((all, [key, vals]) => {
            const mergedCurrentKeyVals = [
                ...(all[key] || []),
                ...vals.map(val => {
                    if (key === RATING_QUERY_KEY) {
                        return {
                            text: `${ratingsLabel} ${val}+`
                        };
                    }
                    return flatItems[key] && flatItems[key][val];
                }).filter(Boolean)
            ];

            return {
                ...all,
                [key]: mergedCurrentKeyVals
            };
        }, {});


        const catNames = (checkedFiltersDetails.cat && checkedFiltersDetails.cat.map(c => c.text)) || [];
        setListingCategories(catNames);
        setFiltersDetails(checkedFiltersDetails);

        // remove categories from selected states when they have subcategories selected,
        // as the filter should use the subcats rather than the broader cat
        const getFilteredValuesState = () => {
            const selectedCats = valuesState[CAT_QUERY_KEY] || [];
            const catsWithSelectedScats = (
                checkedFiltersDetails[SCAT_QUERY_KEY] && checkedFiltersDetails[SCAT_QUERY_KEY].map(
                    scat => scat.parentId
                )
            ) || [];
            const filteredSelectedCats = selectedCats.filter(cat => !catsWithSelectedScats.includes(cat));
            return {
                ...valuesState,
                [CAT_QUERY_KEY]: filteredSelectedCats
            };
        };

        const filteredValuesState = getFilteredValuesState();
        const queries = Object.entries(filteredValuesState).reduce((queryList, [id, vals]) => (
            [
                ...queryList,
                ...vals.map(value => (
                    {
                        id,
                        value
                    }
                ))
            ]
        ), []);

        setFiltersQuery(queries);
    }, [valuesState, items, flatItems, setListingCategories, setFiltersDetails, setFiltersQuery]);

    const onClearFilters = e => {
        e.preventDefault();
        dispatch({ type: CLEAR_ACTION });
        setListingPage(null);
    };
    const onFilterChange = payload => {
        dispatch({ type: TOGGLE_ACTION, payload });
        setListingPage(null);
    };
    const onUpdateRatings = payload => {
        dispatch({ type: UPDATE_RATINGS_ACTION, payload });
        setListingPage(null);
    };

    return { onClearFilters, onFilterChange, onUpdateRatings, valuesState };
}
