import u from 'updeep';
import { mapValues, map, get } from 'lodash';
import { Updux } from '../Updux.js';

const middlewareFor = (type, middleware) => (api) => (next) => (action) => {
    if (type !== '*' && action.type !== type) return next(action);

    return middleware(api)(next)(action);
};

const sliceMw = (slice, mw) => (api) => {
    const getSliceState = () => get(api.getState(), slice);
    return mw({ ...api, getState: getSliceState });
};

export function augmentMiddlewareApi(api, actions, selectors) {
    const getState = () => api.getState();
    const dispatch = (action) => api.dispatch(action);

    Object.assign(
        getState,
        mapValues(selectors, (selector) => {
            return (...args) => {
                let result = selector(api.getState());

                if (typeof result === 'function') return result(...args);

                return result;
            };
        })
    );

    Object.assign(
        dispatch,
        mapValues(actions, (action) => {
            return (...args) => api.dispatch(action(...args));
        })
    );

    return {
        ...api,
        getState,
        dispatch,
        actions,
        selectors,
    };
}

export const effectToMiddleware = (effect, actions, selectors) => {
    let mw = effect;
    let action = '*';

    if (Array.isArray(effect)) {
        action = effect[0];
        mw = effect[1];
        mw = middlewareFor(action, mw);
    }

    return (api) => mw(augmentMiddlewareApi(api, actions, selectors));
};

const composeMw = (mws) => (api) => (original_next) =>
    mws.reduceRight((next, mw) => mw(api)(next), original_next);

export function buildMiddleware(
    effects = [],
    actions = {},
    selectors = {},
    sub = {}
) {
    let inner = map(sub, ({ middleware }, slice) =>
        slice !== '*' && middleware ? sliceMw(slice, middleware) : undefined
    ).filter((x) => x);

    const local = effects.map((effect) =>
        effectToMiddleware(effect, actions, selectors)
    );

    const mws = [...local, ...inner];

    return composeMw(mws);
}
