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

interface FloorStore {
    floors: Floor[];
    setFloors: (floors: Floor[]) => void;
    addFloor: (floor: Floor) => void;
    updateFloor: (uuid: string, updated: Partial<Floor>) => Floor | undefined;
    setFloorName: (uuid: string, name: string) => void;
    removeFloor: (uuid: string) => void;
    removePoint: (pointUuid: string) => { removedFloors: Floor[], updatedFloors: Floor[] };
    removeFloorByPoints: (Points: [Point, Point]) => { removedFloors: Floor[], updatedFloors: Floor[] };
    clearFloors: () => void;
    setPosition: (
        pointUuid: string,
        position: [number, number, number]
    ) => Floor[] | [];
    setIsBeveled: (uuid: string, isBeveled: boolean) => void;
    setBevelStrength: (uuid: string, strength: number) => void;
    setDepth: (uuid: string, depth: number) => void;
    setMaterial: (uuid: string, sideMaterial: string, topMaterial: string) => void;
    addDecal: (floors: string, decal: Decal) => void;
    updateDecal: (decalUuid: string, decal: Decal) => void;
    removeDecal: (decalUuid: string) => void;
    updateDecalPosition: (decalUuid: string, position: [number, number, number]) => void;
    updateDecalRotation: (decalUuid: string, rotation: number) => void;
    updateDecalScale: (decalUuid: string, scale: number) => void;

    getFloorById: (uuid: string) => Floor | undefined;
    getFloorsByPointId: (uuid: string) => Floor[] | [];
    getFloorByPoints: (points: Point[]) => Floor | undefined;
    getFloorPointById: (uuid: string) => Point | undefined;
    getConnectedPoints: (uuid: string) => Point[];
}

export const createFloorStore = () => {
    return create<FloorStore>()(
        immer((set, get) => ({
            floors: [],

            setFloors: (floors) => set(state => {
                state.floors = floors;
            }),

            addFloor: (floor) => set(state => {
                state.floors.push(floor);
            }),

            updateFloor: (uuid, updated) => {
                let updatedFloor: Floor | undefined;
                set(state => {
                    const floor = state.floors.find(f => f.floorUuid === uuid);
                    if (floor) {
                        Object.assign(floor, updated);
                        updatedFloor = JSON.parse(JSON.stringify(floor));
                    }
                });
                return updatedFloor;
            },

            setFloorName: (uuid, name) => set(state => {
                const floor = state.floors.find(f => f.floorUuid === uuid);
                if (floor) {
                    floor.floorName = name;
                }
            }),

            removeFloor: (uuid) => set(state => {
                state.floors = state.floors.filter(f => f.floorUuid !== uuid);
            }),

            removePoint: (pointUuid) => {
                const removedFloors: Floor[] = [];
                const updatedFloors: Floor[] = [];

                set(state => {
                    for (const floor of state.floors) {
                        const pointIndex = floor.points.findIndex(p => p.pointUuid === pointUuid);
                        if (pointIndex === -1) {
                            updatedFloors.push(JSON.parse(JSON.stringify(floor)));
                            continue;
                        }

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

                        if (remainingPoints.length <= 2) {
                            removedFloors.push(JSON.parse(JSON.stringify(floor)));
                            continue;
                        }
                        floor.points = remainingPoints;
                        updatedFloors.push(JSON.parse(JSON.stringify(floor)));
                    }

                    state.floors = updatedFloors;
                });

                return { removedFloors, updatedFloors };
            },

            removeFloorByPoints: ([pointA, pointB]) => {
                const removedFloors: Floor[] = [];
                const updatedFloors: Floor[] = [];

                set(state => {

                    for (const floor of state.floors) {
                        const indices = floor.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) {
                            updatedFloors.push(JSON.parse(JSON.stringify(floor)));
                            continue;
                        }

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

                        if (!areAdjacent) {
                            updatedFloors.push(JSON.parse(JSON.stringify(floor)));
                            continue;
                        }

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

                        if (remainingPoints.length > 2) {
                            floor.points = remainingPoints;
                            updatedFloors.push(JSON.parse(JSON.stringify(floor)));
                        } else {
                            removedFloors.push(JSON.parse(JSON.stringify(floor)));
                        }
                    }

                    state.floors = updatedFloors;
                });

                return { removedFloors, updatedFloors };
            },

            clearFloors: () => set(state => {
                state.floors = [];
            }),

            setPosition: (pointUuid, position) => {
                let updatedFloor: Floor[] = [];
                set((state) => {
                    for (const floor of state.floors) {
                        const point = floor.points.find((p) => p.pointUuid === pointUuid);
                        if (point) {
                            point.position = position;
                            updatedFloor.push(JSON.parse(JSON.stringify(floor)));
                        }
                    }
                });
                return updatedFloor;
            },

            setIsBeveled: (uuid, isBeveled) => set(state => {
                const floor = state.floors.find(f => f.floorUuid === uuid);
                if (floor) {
                    floor.isBeveled = isBeveled;
                }
            }),

            setBevelStrength: (uuid, strength) => set(state => {
                const floor = state.floors.find(f => f.floorUuid === uuid);
                if (floor) {
                    floor.bevelStrength = strength;
                }
            }),

            setDepth: (uuid, depth) => set(state => {
                const floor = state.floors.find(f => f.floorUuid === uuid);
                if (floor) {
                    floor.floorDepth = depth;
                }
            }),

            setMaterial: (uuid, sideMaterial, topMaterial) => set(state => {
                const floor = state.floors.find(f => f.floorUuid === uuid);
                if (floor) {
                    floor.sideMaterial = sideMaterial;
                    floor.topMaterial = topMaterial;
                }
            }),

            addDecal: (floorUuid, decal) => set(state => {
                const floor = state.floors.find(f => f.floorUuid === floorUuid);
                if (floor) {
                    floor.decals.push(decal);
                }
            }),

            updateDecal: (decalUuid, updatedDecal) => set(state => {
                for (const floor of state.floors) {
                    const index = floor.decals.findIndex(d => d.decalUuid === decalUuid);
                    if (index !== -1) {
                        floor.decals[index] = updatedDecal;
                        break;
                    }
                }
            }),

            removeDecal: (decalUuid) => set(state => {
                for (const floor of state.floors) {
                    floor.decals = floor.decals.filter(d => d.decalUuid !== decalUuid);
                }
            }),

            updateDecalPosition: (decalUuid, position) => set(state => {
                for (const floor of state.floors) {
                    const decal = floor.decals.find(d => d.decalUuid === decalUuid);
                    if (decal) {
                        decal.decalPosition = position;
                        break;
                    }
                }
            }),

            updateDecalRotation: (decalUuid, rotation) => set(state => {
                for (const floor of state.floors) {
                    const decal = floor.decals.find(d => d.decalUuid === decalUuid);
                    if (decal) {
                        decal.decalRotation = rotation;
                        break;
                    }
                }
            }),

            updateDecalScale: (decalUuid, scale) => set(state => {
                for (const floor of state.floors) {
                    const decal = floor.decals.find(d => d.decalUuid === decalUuid);
                    if (decal) {
                        decal.decalScale = scale;
                        break;
                    }
                }
            }),


            getFloorById: (uuid) => {
                return get().floors.find(f => f.floorUuid === uuid);
            },

            getFloorsByPointId: (pointUuid) => {
                return get().floors.filter(floor => {
                    return floor.points.some(p => p.pointUuid === pointUuid);
                });
            },

            getFloorByPoints: (points) => {
                return get().floors.find(floor => {
                    const floorPointIds = new Set(floor.points.map(p => p.pointUuid));
                    const givenPointIds = new Set(points.map(p => p.pointUuid));
                    return floorPointIds.size === givenPointIds.size && [...floorPointIds].every(id => givenPointIds.has(id));
                });
            },

            getFloorPointById: (pointUuid) => {
                for (const floor of get().floors) {
                    const point = floor.points.find(p => p.pointUuid === pointUuid);
                    if (point) return point;
                }
                return undefined;
            },

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

        }))
    );
};

export type FloorStoreType = ReturnType<typeof createFloorStore>;