import { nanoid } from 'nanoid';
import type { Dispatch } from 'preact/hooks';
import { reorderElements } from '../../../shared/collections.js';
import type { Coordinates } from '../../../shared/types';
import type { Marker, ReorderListParams } from '../../types';

type GenericReducerAction<TType extends string, TData> = {
    type: TType;
    data: TData;
};

type GenericMarkerChangeReducerAction<TType extends string, TData = unknown> = {
    type: TType;
    data: TData & { key: string };
};

export type ReducerAction =
    | GenericReducerAction<
          'add',
          {
              coordinates: Coordinates;
              dispatcher: Dispatch<ReducerAction>;
          }
      >
    | GenericMarkerChangeReducerAction<'remove'>
    | GenericMarkerChangeReducerAction<'moveUp'>
    | GenericMarkerChangeReducerAction<'moveDown'>
    | GenericMarkerChangeReducerAction<
          'changeLongLabel',
          { newLongLabel: string }
      >
    | GenericReducerAction<'reorder', ReorderListParams>;

const reindexMarkers = (markers: Marker[]) =>
    markers.map((marker, i) => {
        const newShortLabel = `${i + 1}`;
        if (marker.shortLabel === newShortLabel) {
            return marker;
        }

        return {
            ...marker,
            shortLabel: newShortLabel,
        };
    });

const handleDefaultCase = (discriminator: never): never => {
    throw new Error(`Unsupported type ${discriminator}`);
};

export const markersReducer = (
    markers: Marker[],
    action: ReducerAction,
): Marker[] => {
    const type = action.type;
    switch (type) {
        case 'add': {
            const key = nanoid(10);
            return [
                ...markers,
                {
                    key,
                    shortLabel: `${markers.length + 1}`,
                    longLabel: key.substring(0, 4),
                    remove: () =>
                        action.data.dispatcher({
                            type: 'remove',
                            data: { key },
                        }),
                    moveUp: () =>
                        action.data.dispatcher({
                            type: 'moveUp',
                            data: { key },
                        }),
                    moveDown: () =>
                        action.data.dispatcher({
                            type: 'moveDown',
                            data: { key },
                        }),
                    changeLongLabel: (newLongLabel: string) =>
                        action.data.dispatcher({
                            type: 'changeLongLabel',
                            data: { key, newLongLabel },
                        }),
                    coordinates: action.data.coordinates,
                },
            ];
        }
        case 'remove': {
            return reindexMarkers([
                ...markers.filter(({ key }) => key !== action.data.key),
            ]);
        }
        case 'moveUp': {
            const oldIndex = markers.findIndex(
                ({ key }) => key === action.data.key,
            );
            if (oldIndex < 1) {
                return markers;
            }

            return reindexMarkers(
                reorderElements(markers, oldIndex, oldIndex - 1),
            );
        }
        case 'moveDown': {
            const oldIndex = markers.findIndex(
                ({ key }) => key === action.data.key,
            );
            if (oldIndex < 0 || oldIndex + 1 >= markers.length) {
                return markers;
            }

            return reindexMarkers(
                reorderElements(markers, oldIndex, oldIndex + 1),
            );
        }
        case 'changeLongLabel': {
            return markers.map((marker) => {
                if (marker.key !== action.data.key) {
                    return marker;
                }

                return {
                    ...marker,
                    longLabel: action.data.newLongLabel,
                };
            });
        }
        case 'reorder': {
            return reindexMarkers(
                reorderElements(
                    markers,
                    action.data.oldIndex,
                    action.data.newIndex,
                ),
            );
        }
        default:
            return handleDefaultCase(type);
    }
};

export const createChangeStateMethods = (
    dispatchMarkers: Dispatch<ReducerAction>,
) => ({
    addMarker: (coordinates: Coordinates) =>
        dispatchMarkers({
            type: 'add',
            data: {
                coordinates,
                dispatcher: dispatchMarkers,
            },
        }),
    reorderMarkers: ({ oldIndex, newIndex }: ReorderListParams): void =>
        dispatchMarkers({
            type: 'reorder',
            data: { oldIndex, newIndex },
        }),
});
