import * as THREE from 'three';
import * as turf from '@turf/turf';
import * as CONSTANTS from '../../../../types/world/worldConstants';
import * as Types from "../../../../types/world/worldTypes";

function loadOnlyFloors(
    floorGroup: Types.RefGroup,
    linesByLayer: any,
    layer: any,
): void {

    //////////  Creating polygon floor based on the onlyFloorlines.current which does not add roof to it, The lines are still stored in Lines.current as well  //////////

    let floorsInLayer = linesByLayer[layer];
    floorsInLayer = floorsInLayer.filter((line: any) => line[0][3] && line[1][3] === CONSTANTS.lineConfig.floorName);
    const floorResult = floorsInLayer.map((pair: [THREE.Vector3, string, number, string][]) =>
        pair.map((point) => ({
            position: [point[0].x, point[0].z],
            uuid: point[1]
        }))
    );
    const FloorLineFeatures = floorResult.map((line: any) => turf.lineString(line.map((p: any) => p.position)));

    function identifyPolygonsAndConnectedLines(FloorLineFeatures: any) {
        const floorpolygons = [];
        const connectedLines = [];
        const unprocessedLines = [...FloorLineFeatures]; // Copy the features

        while (unprocessedLines.length > 0) {
            const currentLine = unprocessedLines.pop();
            const coordinates = currentLine.geometry.coordinates;

            // Check if the line is closed (forms a polygon)
            if (
                coordinates[0][0] === coordinates[coordinates.length - 1][0] &&
                coordinates[0][1] === coordinates[coordinates.length - 1][1]
            ) {
                floorpolygons.push(turf.polygon([coordinates])); // Add as a polygon
                continue;
            }

            // Check if the line connects to another line
            let connected = false;
            for (let i = unprocessedLines.length - 1; i >= 0; i--) {
                const otherCoordinates = unprocessedLines[i].geometry.coordinates;

                // Check if lines share a start or end point
                if (
                    coordinates[0][0] === otherCoordinates[otherCoordinates.length - 1][0] &&
                    coordinates[0][1] === otherCoordinates[otherCoordinates.length - 1][1]
                ) {
                    // Merge lines
                    const mergedCoordinates = [...otherCoordinates, ...coordinates.slice(1)];
                    unprocessedLines[i] = turf.lineString(mergedCoordinates);
                    connected = true;
                    break;
                } else if (
                    coordinates[coordinates.length - 1][0] === otherCoordinates[0][0] &&
                    coordinates[coordinates.length - 1][1] === otherCoordinates[0][1]
                ) {
                    // Merge lines
                    const mergedCoordinates = [...coordinates, ...otherCoordinates.slice(1)];
                    unprocessedLines[i] = turf.lineString(mergedCoordinates);
                    connected = true;
                    break;
                }
            }

            if (!connected) {
                connectedLines.push(currentLine); // Add unconnected line as-is
            }
        }

        return { floorpolygons, connectedLines };
    }

    const { floorpolygons, connectedLines } = identifyPolygonsAndConnectedLines(FloorLineFeatures);

    function convertConnectedLinesToPolygons(connectedLines: any) {
        return connectedLines.map((line: any) => {
            const coordinates = line.geometry.coordinates;

            // If the line has more than two points, close the polygon
            if (coordinates.length > 2) {
                const firstPoint = coordinates[0];
                const lastPoint = coordinates[coordinates.length - 1];

                // Check if already closed; if not, close it
                if (firstPoint[0] !== lastPoint[0] || firstPoint[1] !== lastPoint[1]) {
                    coordinates.push(firstPoint);
                }

                // Convert the closed line into a polygon
                return turf.polygon([coordinates]);
            }

            // If not enough points for a polygon, return the line unchanged
            return line;
        });
    }

    const convertedConnectedPolygons = convertConnectedLinesToPolygons(connectedLines);

    if (convertedConnectedPolygons.length > 0) {
        const validPolygons = convertedConnectedPolygons.filter(
            (polygon: any) => polygon.geometry?.type === "Polygon"
        );

        if (validPolygons.length > 0) {
            floorpolygons.push(...validPolygons);
        }
    }

    function convertPolygonsToOriginalFormat(floorpolygons: any, originalLines: [THREE.Vector3, string, number, string][][]) {
        return floorpolygons.map((polygon: any) => {
            const coordinates = polygon.geometry.coordinates[0]; // Extract the coordinates array (assume it's a single polygon)

            // Map each coordinate back to its original structure
            const mappedPoints = coordinates.map((coord: [number, number]) => {
                const [x, z] = coord;

                // Find the original point matching this coordinate
                const originalPoint = originalLines.flat().find(([point]) => point.x === x && point.z === z);

                if (!originalPoint) {
                    throw new Error(`Original point for coordinate [${x}, ${z}] not found.`);
                }

                return originalPoint;
            });

            // Create pairs of consecutive points
            const pairs: typeof originalLines = [];
            for (let i = 0; i < mappedPoints.length - 1; i++) {
                pairs.push([mappedPoints[i], mappedPoints[i + 1]]);
            }

            return pairs;
        });
    }

    const convertedFloorPolygons: Types.OnlyFloorLines = convertPolygonsToOriginalFormat(floorpolygons, floorsInLayer);

    convertedFloorPolygons.forEach((floor) => {
        const points: THREE.Vector3[] = [];

        floor.forEach((lineSegment) => {
            const startPoint = lineSegment[0][0];
            points.push(new THREE.Vector3(startPoint.x, startPoint.y, startPoint.z));
        });

        const lastLine = floor[floor.length - 1];
        const endPoint = lastLine[1][0];
        points.push(new THREE.Vector3(endPoint.x, endPoint.y, endPoint.z));

        const shape = new THREE.Shape();
        shape.moveTo(points[0].x, points[0].z);

        points.forEach(point => shape.lineTo(point.x, point.z));
        shape.closePath();

        const extrudeSettings = {
            depth: CONSTANTS.floorConfig.height,
            bevelEnabled: false
        };

        const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
        const material = new THREE.MeshStandardMaterial({ color: CONSTANTS.floorConfig.defaultColor, side: THREE.DoubleSide });
        const mesh = new THREE.Mesh(geometry, material);

        mesh.castShadow = true;
        mesh.receiveShadow = true;

        mesh.position.y = (floor[0][0][2] - 1) * CONSTANTS.wallConfig.height;
        mesh.rotateX(Math.PI / 2);
        mesh.name = `Only_Floor_Line_${floor[0][0][2]}`;

        mesh.userData = floor;
        floorGroup?.current?.add(mesh);
    });
}

export default loadOnlyFloors;
