import * as THREE from 'three';
import { useCallback } from 'react';
import { useSceneContext } from '../../../scene/sceneContext';

const POINT_SNAP_THRESHOLD = 0.5; // Distance threshold for snapping in meters

const CAN_POINT_SNAP = true; // Whether snapping is enabled or not

const ANGLE_SNAP_DISTANCE_THRESHOLD = 0.5;  // Distance threshold for snapping in meters

const CAN_ANGLE_SNAP = true; // Whether snapping is enabled or not

export const usePointSnapping = (currentPoint: { uuid: string, pointType: string, position: [number, number, number] } | null) => {
    const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext();
    const { aisles, getConnectedPoints: getConnectedAislePoints } = aisleStore();
    const { walls, getConnectedPoints: getConnectedWallPoints } = wallStore();
    const { floors, getConnectedPoints: getConnectedFloorPoints } = floorStore();
    const { zones, getConnectedPoints: getConnectedZonePoints } = zoneStore();

    // Wall Snapping

    const getAllOtherWallPoints = useCallback(() => {
        if (!currentPoint) return [];
        return walls.flatMap(wall =>
            wall.points.filter(point => point.pointUuid !== currentPoint.uuid)
        );
    }, [walls, currentPoint]);

    const snapWallPoint = useCallback((position: [number, number, number], tempPoints?: Point) => {
        if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };

        const otherPoints = getAllOtherWallPoints();
        if (tempPoints) {
            otherPoints.push(tempPoints);
        }
        const currentVec = new THREE.Vector3(...position);
        for (const point of otherPoints) {
            const pointVec = new THREE.Vector3(...point.position);
            const distance = currentVec.distanceTo(pointVec);
            if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Wall') {
                return { position: point.position, isSnapped: true, snappedPoint: point };
            }
        }
        return { position: position, isSnapped: false, snappedPoint: null };
    }, [currentPoint, getAllOtherWallPoints]);

    const snapWallAngle = useCallback((newPosition: [number, number, number]): {
        position: [number, number, number],
        isSnapped: boolean,
        snapSources: THREE.Vector3[]
    } => {
        if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };

        const connectedPoints = getConnectedWallPoints(currentPoint.uuid);
        if (connectedPoints.length === 0) {
            return {
                position: newPosition,
                isSnapped: false,
                snapSources: []
            };
        }

        const newPos = new THREE.Vector3(...newPosition);
        let closestX: { pos: THREE.Vector3, dist: number } | null = null;
        let closestZ: { pos: THREE.Vector3, dist: number } | null = null;

        for (const connectedPoint of connectedPoints) {
            const cPos = new THREE.Vector3(...connectedPoint.position);

            const xDist = Math.abs(newPos.x - cPos.x);
            const zDist = Math.abs(newPos.z - cPos.z);

            if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
                if (!closestX || xDist < closestX.dist) {
                    closestX = { pos: cPos, dist: xDist };
                }
            }

            if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
                if (!closestZ || zDist < closestZ.dist) {
                    closestZ = { pos: cPos, dist: zDist };
                }
            }
        }

        const snappedPos = newPos.clone();
        const snapSources: THREE.Vector3[] = [];

        if (closestX) {
            snappedPos.x = closestX.pos.x;
            snapSources.push(closestX.pos.clone());
        }

        if (closestZ) {
            snappedPos.z = closestZ.pos.z;
            snapSources.push(closestZ.pos.clone());
        }

        const isSnapped = snapSources.length > 0;

        return {
            position: [snappedPos.x, snappedPos.y, snappedPos.z],
            isSnapped,
            snapSources
        };
    }, [currentPoint, getConnectedAislePoints]);

    // Aisle Snapping

    const getAllOtherAislePoints = useCallback(() => {
        if (!currentPoint) return [];

        return aisles.flatMap(aisle =>
            aisle.points.filter(point => point.pointUuid !== currentPoint.uuid)
        );
    }, [aisles, currentPoint]);

    const snapAislePoint = useCallback((position: [number, number, number]) => {
        if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };

        const otherPoints = getAllOtherAislePoints();
        const currentVec = new THREE.Vector3(...position);
        for (const point of otherPoints) {
            const pointVec = new THREE.Vector3(...point.position);
            const distance = currentVec.distanceTo(pointVec);
            if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Aisle') {
                return { position: point.position, isSnapped: true, snappedPoint: point };
            }
        }

        return { position: position, isSnapped: false, snappedPoint: null };
    }, [currentPoint, getAllOtherAislePoints]);

    const snapAisleAngle = useCallback((newPosition: [number, number, number]): {
        position: [number, number, number],
        isSnapped: boolean,
        snapSources: THREE.Vector3[]
    } => {
        if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };

        const connectedPoints = getConnectedAislePoints(currentPoint.uuid);
        if (connectedPoints.length === 0) {
            return {
                position: newPosition,
                isSnapped: false,
                snapSources: []
            };
        }

        const newPos = new THREE.Vector3(...newPosition);
        let closestX: { pos: THREE.Vector3, dist: number } | null = null;
        let closestZ: { pos: THREE.Vector3, dist: number } | null = null;

        for (const connectedPoint of connectedPoints) {
            const cPos = new THREE.Vector3(...connectedPoint.position);

            const xDist = Math.abs(newPos.x - cPos.x);
            const zDist = Math.abs(newPos.z - cPos.z);

            if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
                if (!closestX || xDist < closestX.dist) {
                    closestX = { pos: cPos, dist: xDist };
                }
            }

            if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
                if (!closestZ || zDist < closestZ.dist) {
                    closestZ = { pos: cPos, dist: zDist };
                }
            }
        }

        const snappedPos = newPos.clone();
        const snapSources: THREE.Vector3[] = [];

        if (closestX) {
            snappedPos.x = closestX.pos.x;
            snapSources.push(closestX.pos.clone());
        }

        if (closestZ) {
            snappedPos.z = closestZ.pos.z;
            snapSources.push(closestZ.pos.clone());
        }

        const isSnapped = snapSources.length > 0;

        return {
            position: [snappedPos.x, snappedPos.y, snappedPos.z],
            isSnapped,
            snapSources
        };
    }, [currentPoint, getConnectedAislePoints]);

    // Floor Snapping

    const getAllOtherFloorPoints = useCallback(() => {
        if (!currentPoint) return [];

        return floors.flatMap(floor =>
            floor.points.filter(point => point.pointUuid !== currentPoint.uuid)
        );
    }, [floors, currentPoint]);

    const snapFloorPoint = useCallback((position: [number, number, number], tempPoints?: Point[] | []) => {
        if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };

        let otherPoints = getAllOtherFloorPoints();
        if (tempPoints) {
            otherPoints = [...otherPoints, ...tempPoints];
        }
        const currentVec = new THREE.Vector3(...position);

        for (const point of otherPoints) {
            const pointVec = new THREE.Vector3(...point.position);
            const distance = currentVec.distanceTo(pointVec);

            if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Floor') {
                return { position: point.position, isSnapped: true, snappedPoint: point };
            }
        }

        return { position: position, isSnapped: false, snappedPoint: null };
    }, [currentPoint, getAllOtherFloorPoints]);

    const snapFloorAngle = useCallback((newPosition: [number, number, number]): {
        position: [number, number, number],
        isSnapped: boolean,
        snapSources: THREE.Vector3[]
    } => {
        if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };

        const connectedPoints = getConnectedFloorPoints(currentPoint.uuid);
        if (connectedPoints.length === 0) {
            return { position: newPosition, isSnapped: false, snapSources: [] };
        }

        const newPos = new THREE.Vector3(...newPosition);
        let closestX: { pos: THREE.Vector3, dist: number } | null = null;
        let closestZ: { pos: THREE.Vector3, dist: number } | null = null;

        for (const connectedPoint of connectedPoints) {
            const cPos = new THREE.Vector3(...connectedPoint.position);
            const xDist = Math.abs(newPos.x - cPos.x);
            const zDist = Math.abs(newPos.z - cPos.z);

            if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
                if (!closestX || xDist < closestX.dist) {
                    closestX = { pos: cPos, dist: xDist };
                }
            }

            if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
                if (!closestZ || zDist < closestZ.dist) {
                    closestZ = { pos: cPos, dist: zDist };
                }
            }
        }

        const snappedPos = newPos.clone();
        const snapSources: THREE.Vector3[] = [];

        if (closestX) {
            snappedPos.x = closestX.pos.x;
            snapSources.push(closestX.pos.clone());
        }

        if (closestZ) {
            snappedPos.z = closestZ.pos.z;
            snapSources.push(closestZ.pos.clone());
        }

        const isSnapped = snapSources.length > 0;

        return {
            position: [snappedPos.x, snappedPos.y, snappedPos.z],
            isSnapped,
            snapSources
        };
    }, [currentPoint, getConnectedFloorPoints]);

    // Zone Snapping

    const getAllOtherZonePoints = useCallback(() => {
        if (!currentPoint) return [];

        return zones.flatMap(zone =>
            zone.points.filter(point => point.pointUuid !== currentPoint.uuid)
        );
    }, [zones, currentPoint]);

    const snapZonePoint = useCallback((position: [number, number, number], tempPoints?: Point[] | []) => {
        if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };

        let otherPoints = getAllOtherZonePoints();
        if (tempPoints) {
            otherPoints = [...otherPoints, ...tempPoints];
        }
        const currentVec = new THREE.Vector3(...position);

        for (const point of otherPoints) {
            const pointVec = new THREE.Vector3(...point.position);
            const distance = currentVec.distanceTo(pointVec);

            if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Zone') {
                return { position: point.position, isSnapped: true, snappedPoint: point };
            }
        }

        return { position: position, isSnapped: false, snappedPoint: null };
    }, [currentPoint, getAllOtherZonePoints]);

    const snapZoneAngle = useCallback((newPosition: [number, number, number]): {
        position: [number, number, number],
        isSnapped: boolean,
        snapSources: THREE.Vector3[]
    } => {
        if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };

        const connectedPoints = getConnectedZonePoints(currentPoint.uuid);
        if (connectedPoints.length === 0) {
            return { position: newPosition, isSnapped: false, snapSources: [] };
        }

        const newPos = new THREE.Vector3(...newPosition);
        let closestX: { pos: THREE.Vector3, dist: number } | null = null;
        let closestZ: { pos: THREE.Vector3, dist: number } | null = null;

        for (const connectedPoint of connectedPoints) {
            const cPos = new THREE.Vector3(...connectedPoint.position);
            const xDist = Math.abs(newPos.x - cPos.x);
            const zDist = Math.abs(newPos.z - cPos.z);

            if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
                if (!closestX || xDist < closestX.dist) {
                    closestX = { pos: cPos, dist: xDist };
                }
            }

            if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
                if (!closestZ || zDist < closestZ.dist) {
                    closestZ = { pos: cPos, dist: zDist };
                }
            }
        }

        const snappedPos = newPos.clone();
        const snapSources: THREE.Vector3[] = [];

        if (closestX) {
            snappedPos.x = closestX.pos.x;
            snapSources.push(closestX.pos.clone());
        }

        if (closestZ) {
            snappedPos.z = closestZ.pos.z;
            snapSources.push(closestZ.pos.clone());
        }

        const isSnapped = snapSources.length > 0;

        return {
            position: [snappedPos.x, snappedPos.y, snappedPos.z],
            isSnapped,
            snapSources
        };
    }, [currentPoint, getConnectedZonePoints]);

    return {
        snapAislePoint,
        snapAisleAngle,
        snapWallPoint,
        snapWallAngle,
        snapFloorPoint,
        snapFloorAngle,
        snapZonePoint,
        snapZoneAngle,
    };
};