import t from 'tap';
import { Marker, ReducerAction } from './types';
import { createChangeStateMethods, markersReducer } from './reducer.js';

const createReducerWithState = () => {
    let markers: Marker[] = [];

    const changeStateMethods = createChangeStateMethods((action) => {
        markers = markersReducer(markers, action);
    });

    return {
        ...changeStateMethods,
        getMarkers: () => markers,
    };
};

const coordinates1 = { lat: 1, lng: 101 };
const coordinates2 = { lat: 2, lng: 102 };
const coordinates3 = { lat: 3, lng: 103 };
const coordinates4 = { lat: 4, lng: 104 };
const coordinates5 = { lat: 5, lng: 105 };

const createReducersWithFivePoints = () => {
    const result = createReducerWithState();
    result.addMarker(coordinates1);
    result.addMarker(coordinates2);
    result.addMarker(coordinates3);
    result.addMarker(coordinates4);
    result.addMarker(coordinates5);
    return result;
};

const markerPattern = {
    key: String,
    moveUp: Function,
    moveDown: Function,
    remove: Function,
};

void t.test('reducer', (t) => {
    void t.test('throws an error for incorrect action type', (t) => {
        // just to get to 100% coverage
        t.throws(() =>
            markersReducer([], { type: 'unknown' } as unknown as ReducerAction),
        );
        t.end();
    });
    t.end();
});

void t.test('reducer methods', (t) => {
    void t.test('create markers with correct labels', (t) => {
        const reducers = createReducersWithFivePoints();
        t.matchOnlyStrict(reducers.getMarkers(), [
            { ...markerPattern, label: '1', coordinates: coordinates1 },
            { ...markerPattern, label: '2', coordinates: coordinates2 },
            { ...markerPattern, label: '3', coordinates: coordinates3 },
            { ...markerPattern, label: '4', coordinates: coordinates4 },
            { ...markerPattern, label: '5', coordinates: coordinates5 },
        ]);
        t.end();
    });

    void t.test('remove first marker', (t) => {
        const reducers = createReducersWithFivePoints();
        reducers.getMarkers()[0]?.remove();
        t.matchOnlyStrict(reducers.getMarkers(), [
            { ...markerPattern, label: '1', coordinates: coordinates2 },
            { ...markerPattern, label: '2', coordinates: coordinates3 },
            { ...markerPattern, label: '3', coordinates: coordinates4 },
            { ...markerPattern, label: '4', coordinates: coordinates5 },
        ]);
        t.end();
    });

    void t.test('remove middle marker', (t) => {
        const reducers = createReducersWithFivePoints();
        reducers.getMarkers()[2]?.remove();
        t.matchOnlyStrict(reducers.getMarkers(), [
            { ...markerPattern, label: '1', coordinates: coordinates1 },
            { ...markerPattern, label: '2', coordinates: coordinates2 },
            { ...markerPattern, label: '3', coordinates: coordinates4 },
            { ...markerPattern, label: '4', coordinates: coordinates5 },
        ]);
        t.end();
    });

    void t.test('remove last marker', (t) => {
        const reducers = createReducersWithFivePoints();
        reducers.getMarkers()[4]?.remove();
        t.matchOnlyStrict(reducers.getMarkers(), [
            { ...markerPattern, label: '1', coordinates: coordinates1 },
            { ...markerPattern, label: '2', coordinates: coordinates2 },
            { ...markerPattern, label: '3', coordinates: coordinates3 },
            { ...markerPattern, label: '4', coordinates: coordinates4 },
        ]);
        t.end();
    });

    void t.test('do not move first marker up', (t) => {
        const reducers = createReducersWithFivePoints();
        reducers.getMarkers()[0]?.moveUp();
        t.matchOnlyStrict(reducers.getMarkers(), [
            { ...markerPattern, label: '1', coordinates: coordinates1 },
            { ...markerPattern, label: '2', coordinates: coordinates2 },
            { ...markerPattern, label: '3', coordinates: coordinates3 },
            { ...markerPattern, label: '4', coordinates: coordinates4 },
            { ...markerPattern, label: '5', coordinates: coordinates5 },
        ]);
        t.end();
    });

    void t.test('move first marker down', (t) => {
        const reducers = createReducersWithFivePoints();
        reducers.getMarkers()[0]?.moveDown();
        t.matchOnlyStrict(reducers.getMarkers(), [
            { ...markerPattern, label: '1', coordinates: coordinates2 },
            { ...markerPattern, label: '2', coordinates: coordinates1 },
            { ...markerPattern, label: '3', coordinates: coordinates3 },
            { ...markerPattern, label: '4', coordinates: coordinates4 },
            { ...markerPattern, label: '5', coordinates: coordinates5 },
        ]);
        t.end();
    });

    void t.test('move second marker up', (t) => {
        const reducers = createReducersWithFivePoints();
        reducers.getMarkers()[1]?.moveUp();
        t.matchOnlyStrict(reducers.getMarkers(), [
            { ...markerPattern, label: '1', coordinates: coordinates2 },
            { ...markerPattern, label: '2', coordinates: coordinates1 },
            { ...markerPattern, label: '3', coordinates: coordinates3 },
            { ...markerPattern, label: '4', coordinates: coordinates4 },
            { ...markerPattern, label: '5', coordinates: coordinates5 },
        ]);
        t.end();
    });

    void t.test('move second marker down', (t) => {
        const reducers = createReducersWithFivePoints();
        reducers.getMarkers()[1]?.moveDown();
        t.matchOnlyStrict(reducers.getMarkers(), [
            { ...markerPattern, label: '1', coordinates: coordinates1 },
            { ...markerPattern, label: '2', coordinates: coordinates3 },
            { ...markerPattern, label: '3', coordinates: coordinates2 },
            { ...markerPattern, label: '4', coordinates: coordinates4 },
            { ...markerPattern, label: '5', coordinates: coordinates5 },
        ]);
        t.end();
    });

    void t.test('move middle marker up', (t) => {
        const reducers = createReducersWithFivePoints();
        reducers.getMarkers()[2]?.moveUp();
        t.matchOnlyStrict(reducers.getMarkers(), [
            { ...markerPattern, label: '1', coordinates: coordinates1 },
            { ...markerPattern, label: '2', coordinates: coordinates3 },
            { ...markerPattern, label: '3', coordinates: coordinates2 },
            { ...markerPattern, label: '4', coordinates: coordinates4 },
            { ...markerPattern, label: '5', coordinates: coordinates5 },
        ]);
        t.end();
    });

    void t.test('move middle marker down', (t) => {
        const reducers = createReducersWithFivePoints();
        reducers.getMarkers()[2]?.moveDown();
        t.matchOnlyStrict(reducers.getMarkers(), [
            { ...markerPattern, label: '1', coordinates: coordinates1 },
            { ...markerPattern, label: '2', coordinates: coordinates2 },
            { ...markerPattern, label: '3', coordinates: coordinates4 },
            { ...markerPattern, label: '4', coordinates: coordinates3 },
            { ...markerPattern, label: '5', coordinates: coordinates5 },
        ]);
        t.end();
    });

    void t.test('move penultimate marker up', (t) => {
        const reducers = createReducersWithFivePoints();
        reducers.getMarkers()[3]?.moveUp();
        t.matchOnlyStrict(reducers.getMarkers(), [
            { ...markerPattern, label: '1', coordinates: coordinates1 },
            { ...markerPattern, label: '2', coordinates: coordinates2 },
            { ...markerPattern, label: '3', coordinates: coordinates4 },
            { ...markerPattern, label: '4', coordinates: coordinates3 },
            { ...markerPattern, label: '5', coordinates: coordinates5 },
        ]);
        t.end();
    });

    void t.test('move penultimate marker down', (t) => {
        const reducers = createReducersWithFivePoints();
        reducers.getMarkers()[3]?.moveDown();
        t.matchOnlyStrict(reducers.getMarkers(), [
            { ...markerPattern, label: '1', coordinates: coordinates1 },
            { ...markerPattern, label: '2', coordinates: coordinates2 },
            { ...markerPattern, label: '3', coordinates: coordinates3 },
            { ...markerPattern, label: '4', coordinates: coordinates5 },
            { ...markerPattern, label: '5', coordinates: coordinates4 },
        ]);
        t.end();
    });

    void t.test('move last marker up', (t) => {
        const reducers = createReducersWithFivePoints();
        reducers.getMarkers()[4]?.moveUp();
        t.matchOnlyStrict(reducers.getMarkers(), [
            { ...markerPattern, label: '1', coordinates: coordinates1 },
            { ...markerPattern, label: '2', coordinates: coordinates2 },
            { ...markerPattern, label: '3', coordinates: coordinates3 },
            { ...markerPattern, label: '4', coordinates: coordinates5 },
            { ...markerPattern, label: '5', coordinates: coordinates4 },
        ]);
        t.end();
    });

    void t.test('do not move last marker down', (t) => {
        const reducers = createReducersWithFivePoints();
        reducers.getMarkers()[4]?.moveDown();
        t.matchOnlyStrict(reducers.getMarkers(), [
            { ...markerPattern, label: '1', coordinates: coordinates1 },
            { ...markerPattern, label: '2', coordinates: coordinates2 },
            { ...markerPattern, label: '3', coordinates: coordinates3 },
            { ...markerPattern, label: '4', coordinates: coordinates4 },
            { ...markerPattern, label: '5', coordinates: coordinates5 },
        ]);
        t.end();
    });

    void t.test('drag second marker to fourth', (t) => {
        const reducers = createReducersWithFivePoints();
        reducers.reorderMarkers({ oldIndex: 1, newIndex: 3 });
        t.matchOnlyStrict(reducers.getMarkers(), [
            { ...markerPattern, label: '1', coordinates: coordinates1 },
            { ...markerPattern, label: '2', coordinates: coordinates3 },
            { ...markerPattern, label: '3', coordinates: coordinates4 },
            { ...markerPattern, label: '4', coordinates: coordinates2 },
            { ...markerPattern, label: '5', coordinates: coordinates5 },
        ]);
        t.end();
    });

    void t.test('drag fourth marker to second', (t) => {
        const reducers = createReducersWithFivePoints();
        reducers.reorderMarkers({ oldIndex: 3, newIndex: 1 });
        t.matchOnlyStrict(reducers.getMarkers(), [
            { ...markerPattern, label: '1', coordinates: coordinates1 },
            { ...markerPattern, label: '2', coordinates: coordinates4 },
            { ...markerPattern, label: '3', coordinates: coordinates2 },
            { ...markerPattern, label: '4', coordinates: coordinates3 },
            { ...markerPattern, label: '5', coordinates: coordinates5 },
        ]);
        t.end();
    });

    t.end();
});
