import { difference, omit } from 'lodash';

import { Updux } from './Updux';

test('initial', () => {
    const dux = new Updux({
        initial: {},
        subduxes: {
            '*': {
                initial: { a: 1 },
            },
        },
    });

    expect(dux.initial).toMatchObject({});
});

test('actions', () => {
    const dux = new Updux({
        initial: {},
        subduxes: {
            '*': {
                initial: { a: 1 },
                actions: { foo: null },
            },
        },
    });

    expect(typeof dux.actions.foo).toBe('function');
});

test('selectors', () => {
    const dux = new Updux({
        initial: {},
        subduxes: {
            '*': {
                initial: { a: 1 },
                selectors: {
                    getA: ({ a }) => a,
                },
            },
        },
    });

    expect(dux.selectors).toMatchObject({});

    const getInner = (state) => (index) => state[index];
    dux.setMappedSelector('getInner', getInner);

    expect(typeof dux.selectors.getInner).toBe('function');

    expect(
        dux.selectors.getInner({
            one: { a: 1 },
            two: { a: 2 },
        })('one')()
    ).toMatchObject({ a: 1 });

    expect(
        dux.selectors
            .getInner({
                one: { a: 1 },
                two: { a: 2 },
            })('one')
            .getA()
    ).toBe(1);

    // and now with the store
    const store = dux.createStore({
        one: { a: 1 },
        two: { a: 2 },
    });

    expect(store.getState.getInner('two')()).toMatchObject({ a: 2 });
    expect(store.getState.getInner('two').getA()).toEqual(2);
});

test('splat middleware', () => {
    const snitch = jest.fn(() => true);

    const dux = new Updux({
        initial: {},
        subduxes: {
            '*': {
                initial: { a: 1 },
                actions: { foo: null },
                effects: {
                    foo: (api) => (next) => (action) => {
                        snitch();
                        next(action);
                    },
                },
            },
        },
    });

    const store = dux.createStore({ one: { a: 1 }, two: { a: 2 } });
    store.dispatch.foo();

    expect(snitch).not.toBeCalled();
});

test('splat subscriptions', () => {
    const snitch = jest.fn(() => true);

    const dux = new Updux({
        initial: {},
        subduxes: {
            '*': {
                initial: { a: 1 },
                actions: { foo: null },
                mutations: {
                    foo: (id) => (state) =>
                        state.a === i ? { ...state, b: 1 } : state,
                },
                subscriptions: [
                    (store) => (state, previous, unsub) => {
                        snitch(state);
                    },
                ],
            },
        },
    });

    const store = dux.createStore({ one: { a: 1 }, two: { a: 2 } });
    store.dispatch({ type: 'noop' });

    expect(snitch).not.toBeCalled();
});

test('splat subscriptions, more', () => {
    const snitch = jest.fn(() => true);

    const inner = new Updux({
        initial: { a: 1 },
        actions: { foo: null, incAll: null },
        mutations: {
            foo: (id) => (state) => state.a === id ? { ...state, b: 1 } : state,
            incAll: () => (state) => ({ ...state, a: state.a + 1 }),
        },
        reactions: [() => snitch],
    });

    const dux = new Updux({
        initial: {},
        actions: {
            delete: null,
            newEntry: null,
        },
        mutations: {
            delete: (id) => (state) => omit(state, id),
            newEntry: (name) => (state) => ({ ...state, [name]: { a: 10 } }),
        },
        mappedReaction: true,
        subduxes: {
            '*': inner,
        },
    });

    const store = dux.createStore({ one: { a: 1 }, two: { a: 2 } });
    store.dispatch({ type: 'noop' });

    expect(snitch).toHaveBeenCalledTimes(2);

    expect(snitch).toHaveBeenCalledWith({ a: 1 }, undefined, expect.anything());
    expect(snitch).toHaveBeenCalledWith({ a: 2 }, undefined, expect.anything());

    snitch.mockReset();

    store.dispatch.foo(2);

    expect(snitch).toHaveBeenCalledWith(
        { a: 2, b: 1 },
        expect.anything(),
        expect.anything()
    );

    snitch.mockReset();

    store.dispatch.delete('one');

    expect(store.getState()).toMatchObject({
        two: { a: 2, b: 1 },
    });

    expect(snitch).toHaveBeenCalledTimes(1);

    expect(snitch).toHaveBeenCalledWith(undefined, { a: 1 }, expect.anything());

    // only one subscriber left
    snitch.mockReset();

    store.dispatch.incAll();

    expect(snitch).toHaveBeenCalledTimes(1);

    expect(snitch).toHaveBeenCalledWith(
        { a: 3, b: 1 },
        { a: 2, b: 1 },
        expect.anything()
    );

    // new entry gets subscribed
    snitch.mockReset();
    store.dispatch.newEntry('newbie');

    expect(snitch).toHaveBeenCalledTimes(1);

    expect(snitch).toHaveBeenCalledWith(
        { a: 10 },
        undefined,
        expect.anything()
    );
});

test('many levels down', () => {
    const snitch = jest.fn(() => true);

    const dux = new Updux({
        mappedReaction: true,
        actions: { remove: null },
        mutations: {
            remove: () => (state) => ({}),
        },
        subduxes: {
            '*': {
                subduxes: {
                    a: {
                        initial: 1,
                        actions: {
                            add: null,
                        },
                        mutations: {
                            add: () => (x) => x + 1,
                        },
                        reactions: [(store) => (state) => snitch(state, store)],
                    },
                },
            },
        },
    });

    const store = dux.createStore({ one: { a: 1 } });
    store.dispatch({ type: 'foo' });

    expect(snitch).toHaveBeenCalled();
    expect(snitch).toHaveBeenCalledWith(1, expect.anything());

    snitch.mockReset();

    store.dispatch.remove();

    expect(snitch).toBeCalled();
    expect(snitch).toHaveBeenCalledWith(undefined, expect.anything());
});

test('inherit info via the store', () => {
    const snitch = jest.fn(() => true);
    const splatSnitch = jest.fn(() => true);

    const dux = new Updux({
        mappedReaction: (store, key) => {
            store.itemId = key;

            splatSnitch();
            return { itemId: key };
        },
        subduxes: {
            '*': {
                subduxes: {
                    a: {
                        initial: 1,
                        actions: {
                            add: null,
                        },
                        mutations: {
                            add: () => (x) => x + 1,
                        },
                        reactions: [
                            (store) => (state) => snitch(state, store.itemId),
                        ],
                    },
                },
            },
        },
    });

    const store = dux.createStore({
        one: { a: 1 },
        two: { a: 2 },
    });

    store.dispatch({ type: 'noop' });

    expect(splatSnitch).toBeCalledTimes(2);

    expect(snitch).toBeCalledTimes(2);
    expect(snitch).toHaveBeenCalledWith(1, 'one');
    expect(snitch).toHaveBeenCalledWith(2, 'two');
});
