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

interface WallStore {
    walls: Wall[];
    setWalls: (walls: Wall[]) => void;
    addWall: (wall: Wall) => void;
    updateWall: (wallUuid: string, updated: Partial<Wall>) => Wall | undefined;
    peekUpdateWall: (wallUuid: string, updated: Partial<Wall>) => Wall | undefined;
    removeWall: (wallUuid: string) => void;
    clearWalls: () => void;
    removeWallByPoints: (Points: [Point, Point]) => Wall | undefined;
    peekRemoveWallByPoints: (points: [Point, Point]) => Wall | undefined;
    addDecal: (wallUuid: string, decal: Decal) => Decal | undefined;
    peekAddDecal: (wallUuid: string, decal: Decal) => Wall | undefined;
    updateDecal: (decalUuid: string, decal: Partial<Decal>) => Wall | undefined;
    peekUpdateDecal: (decalUuid: string, decal: Partial<Decal>) => Wall | undefined;
    removeDecal: (decalUuid: string) => Wall | undefined;
    updateDecalPosition: (decalUuid: string, position: [number, number, number]) => Wall | undefined;
    updateDecalRotation: (decalUuid: string, rotation: number) => void;
    updateDecalScale: (decalUuid: string, scale: number) => void;

    removePoint: (pointUuid: string) => Wall[];
    peekRemovePoint: (pointUuid: string) => Wall[];

    setPosition: (pointUuid: string, position: [number, number, number]) => Wall[] | [];
    setLayer: (pointUuid: string, layer: number) => void;

    getWallById: (wallUuid: string) => Wall | undefined;
    getWallsByPointId: (wallUuid: string) => Wall[] | [];
    getWallByPoints: (points: Point[]) => Wall | undefined;
    getWallPointById: (wallUuid: string) => Point | undefined;
    getConnectedPoints: (wallUuid: string) => Point[];
    getConnectedWallsByWallId: (wallUuid: string, skipSelf: boolean) => Wall[];
}

export const createWallStore = () => {
    return create<WallStore>()(
        immer((set, get) => ({
            walls: [],

            setWalls: (walls) =>
                set((state) => {
                    state.walls = walls;
                }),

            addWall: (wall) =>
                set((state) => {
                    state.walls.push(wall);
                }),

            updateWall: (wallUuid, updated) => {
                let updatedWall: Wall | undefined;
                set((state) => {
                    const wall = state.walls.find((w) => w.wallUuid === wallUuid);
                    if (wall) {
                        Object.assign(wall, updated);
                        updatedWall = JSON.parse(JSON.stringify(wall));
                    }
                });
                return updatedWall;
            },

            peekUpdateWall: (wallUuid, updated) => {
                const wall = get().walls.find((w) => w.wallUuid === wallUuid);
                if (!wall) return undefined;

                const clonedWall: Wall = JSON.parse(JSON.stringify(wall));
                Object.assign(clonedWall, updated);

                return clonedWall;
            },

            removeWall: (wallUuid) =>
                set((state) => {
                    state.walls = state.walls.filter((w) => w.wallUuid !== wallUuid);
                }),

            clearWalls: () => {
                set((state) => {
                    state.walls = [];
                });
            },

            removeWallByPoints: (points) => {
                let removedWall: Wall | undefined;
                const [pointA, pointB] = points;

                set((state) => {
                    state.walls = state.walls.filter((wall) => {
                        const wallPoints = wall.points.map((p) => p.pointUuid);
                        const hasBothPoints = wallPoints.includes(pointA.pointUuid) && wallPoints.includes(pointB.pointUuid);

                        if (hasBothPoints) {
                            removedWall = JSON.parse(JSON.stringify(wall));
                            return false;
                        }
                        return true;
                    });
                });

                return removedWall;
            },

            peekRemoveWallByPoints: (points) => {
                const [pointA, pointB] = points;

                const wall = get().walls.find((w) => {
                    const wallPoints = w.points.map((p) => p.pointUuid);
                    return wallPoints.includes(pointA.pointUuid) && wallPoints.includes(pointB.pointUuid);
                });

                return wall ? JSON.parse(JSON.stringify(wall)) : undefined;
            },

            addDecal: (wallUuid, decal) => {
                let addedDecal: Decal | undefined;
                set((state) => {
                    const wallToUpdate = state.walls.find((w) => w.wallUuid === wallUuid);
                    if (wallToUpdate) {
                        wallToUpdate.decals.push(decal);
                        addedDecal = JSON.parse(JSON.stringify(decal));
                    }
                });
                return addedDecal;
            },

            peekAddDecal: (wallUuid, decal) => {
                const wall = get().walls.find((w) => w.wallUuid === wallUuid);
                if (!wall) return undefined;

                const clonedWall: Wall = JSON.parse(JSON.stringify(wall));
                clonedWall.decals.push(JSON.parse(JSON.stringify(decal)));

                return clonedWall;
            },

            updateDecal: (decalUuid, decal) => {
                let affectedWall: Wall | undefined;
                set((state) => {
                    for (const wall of state.walls) {
                        const decalToUpdate = wall.decals.find((d) => d.decalUuid === decalUuid);
                        if (decalToUpdate) {
                            Object.assign(decalToUpdate, decal);
                            affectedWall = JSON.parse(JSON.stringify(wall));
                        }
                    }
                });
                return affectedWall;
            },

            peekUpdateDecal: (decalUuid, decal) => {
                const wall = get().walls.find((w) => w.decals.some((d) => d.decalUuid === decalUuid));
                if (!wall) return undefined;

                const clonedWall: Wall = JSON.parse(JSON.stringify(wall));

                const decalToUpdate = clonedWall.decals.find((d) => d.decalUuid === decalUuid);
                if (decalToUpdate) {
                    Object.assign(decalToUpdate, decal);
                }

                return clonedWall;
            },

            removeDecal: (decalUuid) => {
                let affectedWall: Wall | undefined;
                set((state) => {
                    for (const wall of state.walls) {
                        const hasDecal = wall.decals.some((d) => d.decalUuid === decalUuid);
                        if (hasDecal) {
                            wall.decals = wall.decals.filter((d) => d.decalUuid !== decalUuid);
                            affectedWall = JSON.parse(JSON.stringify(wall));
                        }
                    }
                });
                return affectedWall;
            },

            updateDecalPosition: (decalUuid, position) => {
                let affectedWall: Wall | undefined;
                set((state) => {
                    for (const wall of state.walls) {
                        const decal = wall.decals.find((d) => d.decalUuid === decalUuid);
                        if (decal) {
                            decal.decalPosition = position;
                            affectedWall = JSON.parse(JSON.stringify(wall));
                            break;
                        }
                    }
                });
                return affectedWall;
            },

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

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

            removePoint: (pointUuid) => {
                const removedWalls: Wall[] = [];
                set((state) => {
                    state.walls = state.walls.filter((wall) => {
                        const hasPoint = wall.points.some((p) => p.pointUuid === pointUuid);
                        if (hasPoint) {
                            removedWalls.push(JSON.parse(JSON.stringify(wall)));
                            return false;
                        }
                        return true;
                    });
                });
                return removedWalls;
            },

            peekRemovePoint: (pointUuid) => {
                const wallsToRemove = get().walls.filter((wall) => wall.points.some((p) => p.pointUuid === pointUuid));

                return wallsToRemove.map((w) => JSON.parse(JSON.stringify(w)));
            },

            setPosition: (pointUuid, position) => {
                let updatedWalls: Wall[] = [];
                set((state) => {
                    for (const wall of state.walls) {
                        const point = wall.points.find((p) => p.pointUuid === pointUuid);
                        if (point) {
                            point.position = position;
                            updatedWalls.push(wall);
                        }
                    }
                });
                return updatedWalls;
            },

            setLayer: (pointUuid, layer) =>
                set((state) => {
                    for (const wall of state.walls) {
                        const point = wall.points.find((p) => p.pointUuid === pointUuid);
                        if (point) {
                            point.layer = layer;
                        }
                    }
                }),

            getWallById: (wallUuid) => {
                return get().walls.find((w) => w.wallUuid === wallUuid);
            },

            getWallsByPointId: (wallUuid) => {
                return get().walls.filter((a) => {
                    return JSON.parse(JSON.stringify(a.points.some((p) => p.pointUuid === wallUuid)));
                });
            },

            getWallByPoints: (point) => {
                for (const wall of get().walls) {
                    if (
                        (wall.points[0].pointUuid === point[0].pointUuid || wall.points[1].pointUuid === point[0].pointUuid) &&
                        (wall.points[0].pointUuid === point[1].pointUuid || wall.points[1].pointUuid === point[1].pointUuid)
                    ) {
                        return wall;
                    }
                }
                return undefined;
            },

            getWallPointById: (wallUuid) => {
                for (const wall of get().walls) {
                    const point = wall.points.find((p) => p.pointUuid === wallUuid);
                    if (point) return point;
                }
                return undefined;
            },

            getConnectedPoints: (wallUuid) => {
                const connected: Point[] = [];
                for (const wall of get().walls) {
                    for (const point of wall.points) {
                        if (point.pointUuid === wallUuid) {
                            connected.push(...wall.points.filter((p) => p.pointUuid !== wallUuid));
                        }
                    }
                }
                return connected;
            },

            getConnectedWallsByWallId: (wallUuid, skipSelf) => {
                const wall = get().walls.find((w) => w.wallUuid === wallUuid);
                if (!wall) return [];

                const pointUuids = wall.points.map((p) => p.pointUuid);

                return get().walls.filter((w) => {
                    if (skipSelf && w.wallUuid === wallUuid) return false;
                    return w.points.some((p) => pointUuids.includes(p.pointUuid));
                });
            },
        }))
    );
};

export type WallStoreType = ReturnType<typeof createWallStore>;
