import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';

interface ZoneStore {
    zones: Zone[];
    setZones: (zones: Zone[]) => void;
    addZone: (zone: Zone) => void;
    updateZone: (uuid: string, updated: Partial<Zone>) => void;
    setZoneName: (uuid: string, name: string) => void;
    setZoneHeight: (uuid: string, height: number) => void;
    setZoneColor: (uuid: string, color: string) => void;
    removeZone: (uuid: string) => void;
    removePoint: (pointUuid: string) => { removedZones: Zone[], updatedZones: Zone[] };
    removeZoneByPoints: (points: Point[]) => { removedZones: Zone[], updatedZones: Zone[] };
    clearZones: () => void;
    setPosition: (
        pointUuid: string,
        position: [number, number, number]
    ) => Zone[] | [];
    setViewPort: (uuid: string, position: [number, number, number], target: [number, number, number]) => void;

    getZoneById: (uuid: string) => Zone | undefined;
    getZonesByPointId: (uuid: string) => Zone[] | [];
    getZonesByPoints: (points: Point[]) => Zone[] | [];
    getZonePointById: (uuid: string) => Point | undefined;
    getConnectedPoints: (uuid: string) => Point[];
}

export const createZoneStore = () => {
    return create<ZoneStore>()(
        immer((set, get) => ({
            zones: [],

            setZones: (zones) => set(state => {
                state.zones = zones;
            }),

            addZone: (zone) => set(state => {
                state.zones.push(zone);
            }),

            updateZone: (uuid, updated) => set(state => {
                const zone = state.zones.find(z => z.zoneUuid === uuid);
                if (zone) {
                    Object.assign(zone, updated);
                }
            }),

            setZoneName: (uuid, name) => set(state => {
                const zone = state.zones.find(z => z.zoneUuid === uuid);
                if (zone) {
                    zone.zoneName = name;
                }
            }),

            setZoneHeight: (uuid, height) => set(state => {
                const zone = state.zones.find(z => z.zoneUuid === uuid);
                if (zone) {
                    zone.zoneHeight = height;
                }
            }),

            setZoneColor: (uuid, color) => set(state => {
                const zone = state.zones.find(z => z.zoneUuid === uuid);
                if (zone) {
                    zone.zoneColor = color;
                }
            }),

            removeZone: (uuid) => set(state => {
                state.zones = state.zones.filter(z => z.zoneUuid !== uuid);
            }),

            removePoint: (pointUuid) => {
                const removedZones: Zone[] = [];
                const updatedZones: Zone[] = [];

                set(state => {
                    const newZones: Zone[] = [];

                    for (const zone of state.zones) {
                        const pointIndex = zone.points.findIndex(p => p.pointUuid === pointUuid);

                        if (pointIndex === -1) {
                            newZones.push(zone);
                            continue;
                        }

                        const remainingPoints = zone.points.filter(p => p.pointUuid !== pointUuid);

                        if (remainingPoints.length <= 2) {
                            removedZones.push(JSON.parse(JSON.stringify(zone)));
                            continue;
                        }

                        const updatedZone = { ...zone, points: remainingPoints };
                        updatedZones.push(JSON.parse(JSON.stringify(updatedZone)));
                        newZones.push(updatedZone);
                    }

                    state.zones = newZones;
                });

                return { removedZones, updatedZones };
            },

            removeZoneByPoints: ([pointA, pointB]) => {
                const removedZones: Zone[] = [];
                const updatedZones: Zone[] = [];

                set(state => {
                    const newZones: Zone[] = [];

                    for (const zone of state.zones) {
                        const indices = zone.points.map((p, i) => ({ uuid: p.pointUuid, index: i }));

                        const idxA = indices.find(i => i.uuid === pointA.pointUuid)?.index ?? -1;
                        const idxB = indices.find(i => i.uuid === pointB.pointUuid)?.index ?? -1;

                        if (idxA === -1 || idxB === -1) {
                            newZones.push(zone);
                            continue;
                        }

                        const areAdjacent =
                            Math.abs(idxA - idxB) === 1 ||
                            (idxA === 0 && idxB === zone.points.length - 1) ||
                            (idxB === 0 && idxA === zone.points.length - 1);

                        if (!areAdjacent) {
                            newZones.push(zone);
                            continue;
                        }

                        const remainingPoints = zone.points.filter(
                            p => p.pointUuid !== pointA.pointUuid && p.pointUuid !== pointB.pointUuid
                        );

                        if (remainingPoints.length > 2) {
                            const updatedZone = { ...zone, points: remainingPoints };
                            updatedZones.push(JSON.parse(JSON.stringify(updatedZone)));
                            newZones.push(updatedZone);
                        } else {
                            removedZones.push(JSON.parse(JSON.stringify(zone)));
                        }
                    }

                    state.zones = newZones;
                });

                return { removedZones, updatedZones };
            },

            clearZones: () => set(state => {
                state.zones = [];
            }),

            setPosition: (pointUuid, position) => {
                let updatedZone: Zone[] = [];
                set((state) => {
                    for (const zone of state.zones) {
                        const point = zone.points.find((p) => p.pointUuid === pointUuid);
                        if (point) {
                            point.position = position;
                            updatedZone.push(JSON.parse(JSON.stringify(zone)));
                        }
                    }
                });
                return updatedZone;
            },

            setViewPort: (uuid, position, target) => set(state => {
                const zone = state.zones.find(z => z.zoneUuid === uuid);
                if (zone) {
                    zone.viewPortPosition = position;
                    zone.viewPortTarget = target;
                }
            }),

            getZoneById: (uuid) => {
                return get().zones.find(z => z.zoneUuid === uuid);
            },

            getZonesByPointId: (pointUuid) => {
                return get().zones.filter(zone => {
                    return zone.points.some(p => p.pointUuid === pointUuid);
                });
            },

            getZonesByPoints: ([pointA, pointB]) => {
                const Zones: Zone[] = [];

                for (const zone of get().zones) {
                    const indices = zone.points.map((p, i) => ({ uuid: p.pointUuid, index: i }));

                    const idxA = indices.find(i => i.uuid === pointA.pointUuid)?.index ?? -1;
                    const idxB = indices.find(i => i.uuid === pointB.pointUuid)?.index ?? -1;

                    if (idxA === -1 || idxB === -1) {
                        continue;
                    }

                    const areAdjacent =
                        Math.abs(idxA - idxB) === 1 ||
                        (idxA === 0 && idxB === zone.points.length - 1) ||
                        (idxB === 0 && idxA === zone.points.length - 1);

                    if (!areAdjacent) {
                        continue;
                    }

                    Zones.push(JSON.parse(JSON.stringify(zone)));
                }

                return Zones;
            },

            getZonePointById: (pointUuid) => {
                for (const zone of get().zones) {
                    const point = zone.points.find(p => p.pointUuid === pointUuid);
                    if (point) return point;
                }
                return undefined;
            },

            getConnectedPoints: (pointUuid) => {
                const connected: Point[] = [];
                for (const zone of get().zones) {
                    if (zone.points.some(p => p.pointUuid === pointUuid)) {
                        connected.push(...zone.points.filter(p => p.pointUuid !== pointUuid));
                    }
                }
                return connected;
            }

        }))
    );
};

export type ZoneStoreType = ReturnType<typeof createZoneStore>;