import u from 'updeep';

import { Updux } from './Updux';
import { action } from './actions';

test('subscriptions', () => {
    const inc = action('inc');
    const set_copy = action('set_copy');

    const dux = new Updux({
        initial: {
            x: 0,
            copy: 0,
        },
        actions: {
            inc,
            set_copy,
        },
        mutations: {
            inc: (payload) => u({ x: (x) => x + 1 }),
            set_copy: (copy) => u({ copy }),
        },
    });

    dux.addSubscription((store) => (state, previous, unsubscribe) => {
        if (state.x > 2) return unsubscribe();

        store.dispatch(set_copy(state.x));
    });

    const store = dux.createStore();

    store.dispatch(inc());

    expect(store.getState()).toMatchObject({ x: 1, copy: 1 });

    store.dispatch(inc());
    store.dispatch(inc());

    expect(store.getState()).toMatchObject({ x: 3, copy: 2 });
});

test('subduxes subscriptions', () => {
    const inc_top = action('inc_top');
    const inc_bar = action('inc_bar');
    const transform_bar = action('transform_bar');

    const bar = new Updux({
        initial: 'a',
        actions: { inc_bar, transform_bar },
        mutations: {
            inc_bar: () => (state) => state + 'a',
            transform_bar: (outcome) => () => outcome,
        },
        reactions: [
            (store) => (state, previous, unsubscribe) => {
                if (state.length <= 2) return;
                unsubscribe();
                store.dispatch(transform_bar('look at ' + state));
            },
        ],
    });

    const dux = new Updux({
        initial: {
            count: 0,
        },
        subduxes: { bar },
        actions: {
            inc_top,
        },
        mutations: {
            inc_top: () => u({ count: (count) => count + 1 }),
        },
        effects: {
            '*': () => (next) => (action) => {
                next(action);
            },
        },
        reactions: [
            (store) => {
                return ({ count }, { count: previous } = {}) => {
                    if (count !== previous) {
                        previous = count;
                        store.dispatch.inc_bar();
                    }
                };
            },
        ],
    });

    const store = dux.createStore();

    store.dispatch(inc_top());
    store.dispatch(inc_top());

    expect(store.getState()).toMatchObject({
        count: 2,
        bar: 'look at look at aaa',
    });
    store.dispatch(inc_top());
    expect(store.getState()).toMatchObject({
        count: 3,
        bar: 'look at look at aaaa',
    });
});

test('subscription within subduxes', () => {
    let innerState = jest.fn(() => null);
    let outerState = jest.fn(() => null);

    const resetMocks = () => [innerState, outerState].map((f) => f.mockReset());

    const inner = new Updux({
        initial: 1,
        actions: { inc: null },
        mutations: {
            inc: () => (state) => state + 1,
        },
        reactions: [
            (store) => (state, previous, unsub) => {
                if (!previous) return;
                store.subscribe(innerState);
                unsub();
            },
        ],
    });

    const dux = new Updux({
        initial: { x: 0 },
        subduxes: { inner },
        actions: {
            incOuter: null,
        },
        mutations: {
            incOuter: () => (state) => ({ ...state, x: state.x + 1 }),
        },
        reactions: [
            (store) => (state, previous, unsub) => {
                if (!previous) return;
                store.subscribe(outerState);
                unsub();
            },
        ],
    });

    const store = dux.createStore();

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

    expect(innerState).not.toHaveBeenCalled();
    expect(outerState).not.toHaveBeenCalled();

    store.dispatch.inc();

    // not called yet
    expect(innerState).not.toBeCalled();
    expect(outerState).not.toBeCalled();

    store.dispatch.inc();
    expect(outerState).toBeCalledTimes(1);
    expect(outerState).toHaveBeenCalledWith(
        { inner: 3, x: 0 },
        undefined,
        expect.anything()
    );

    expect(innerState).toHaveBeenCalled();
    expect(innerState).toHaveBeenCalledWith(3, undefined, expect.anything());

    resetMocks();

    store.dispatch.inc();

    expect(outerState).toBeCalled();
    expect(outerState).toBeCalledWith(
        { inner: 4, x: 0 },
        expect.anything(),
        expect.anything()
    );

    expect(innerState).toBeCalledWith(4, 3, expect.anything());

    // state of subdux doesnt change
    resetMocks();

    store.dispatch.incOuter();

    expect(outerState).toBeCalled();
    expect(innerState).not.toBeCalled();
});
