import * as THREE from 'three';
import { Base } from '@react-three/csg';
import { useMemo, useRef, useState } from 'react';
import { MeshDiscardMaterial } from '@react-three/drei';
import { useFrame, useThree } from '@react-three/fiber';
import useModuleStore from '../../../../../store/useModuleStore';
import { useSceneContext } from '../../../../scene/sceneContext';
import { useWallClassification } from './helpers/useWallClassification';
import { useToggleView, useWallVisibility } from '../../../../../store/builder/store';
import { useBuilderStore } from '../../../../../store/builder/useBuilderStore';
import * as Constants from '../../../../../types/world/worldConstants';

import DecalInstance from '../../../Decal/decalInstance/decalInstance';

import defaultMaterial from '../../../../../assets/textures/floor/wall-tex.png';
import material1 from '../../../../../assets/textures/floor/factory wall texture.jpg';

function Wall({ wall }: { readonly wall: Wall }) {
    const { wallStore, wallAssetStore } = useSceneContext();
    const { walls } = wallStore();
    const { wallAssets, getWallAssetsByWall, setVisibility } = wallAssetStore();
    const assets = getWallAssetsByWall(wall.wallUuid);
    const { selectedWall, setSelectedWall, setSelectedDecal } = useBuilderStore();
    const { toggleView } = useToggleView();
    const { activeModule } = useModuleStore();
    const { camera } = useThree();
    const { wallVisibility } = useWallVisibility();
    const { getWallType, isWallFlipped } = useWallClassification(walls);
    const [visible, setVisible] = useState(true);
    const meshRef = useRef<any>();
    const prevVisibleRef = useRef<boolean | null>(null);
    const wallType = getWallType(wall);

    const wallFlipped = isWallFlipped(wall);

    const [rawStart, rawEnd] = wall.points;
    const [startPoint, endPoint] = wallFlipped ? [rawStart, rawEnd] : [rawEnd, rawStart];

    const startX = startPoint.position[0];
    const startZ = startPoint.position[2];
    const endX = endPoint.position[0];
    const endZ = endPoint.position[2];

    const wallLength = Math.sqrt((endX - startX) ** 2 + (endZ - startZ) ** 2);
    const angle = Math.atan2(endZ - startZ, endX - startX);

    const centerX = (startX + endX) / 2;
    const centerZ = (startZ + endZ) / 2;
    const centerY = wall.wallHeight / 2;

    const textureLoader = new THREE.TextureLoader();

    const [defaultWallTexture, material1WallTexture] = useMemo(() => {
        const inside = textureLoader.load(defaultMaterial);
        inside.wrapS = inside.wrapT = THREE.RepeatWrapping;
        inside.repeat.set(wallLength / 10, wall.wallHeight / 10);
        inside.colorSpace = THREE.SRGBColorSpace;

        const outside = textureLoader.load(material1);
        outside.wrapS = outside.wrapT = THREE.RepeatWrapping;
        outside.repeat.set(wallLength / 10, wall.wallHeight / 10);
        outside.colorSpace = THREE.SRGBColorSpace;

        return [inside, outside];
    }, [wallLength, wall.wallHeight]);

    const materials = useMemo(() => {
        return [
            new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible, clipShadows: true }),  // Left
            new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible, clipShadows: true }),  // Right
            new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible, clipShadows: true }),  // Top
            new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide, visible: visible, clipShadows: true }),  // Bottom
            new THREE.MeshStandardMaterial({
                color: Constants.wallConfig.defaultColor,
                side: THREE.DoubleSide,
                map: wall.insideMaterial === 'Default Material' ? defaultWallTexture : material1WallTexture,
            }),
            new THREE.MeshStandardMaterial({
                color: Constants.wallConfig.defaultColor,
                side: THREE.DoubleSide,
                map: wall.outsideMaterial === 'Default Material' ? defaultWallTexture : material1WallTexture,
            }),
        ];
    }, [defaultWallTexture, material1WallTexture, wall, visible]);

    const geometry = useMemo(() => new THREE.BoxGeometry(wallLength, wall.wallHeight, wall.wallThickness), [wallLength, wall.wallHeight, wall.wallThickness]);

    useFrame(() => {
        if (!meshRef.current) return;
        const v = new THREE.Vector3();
        const u = new THREE.Vector3();

        let nextVisible = true;

        if (!wallVisibility && wallType.type === "room") {
            meshRef.current.getWorldDirection(v);
            camera.getWorldDirection(u);
            if (!u || !v) return;
            nextVisible = (2 * v.dot(u)) <= 0.1;
        }

        if (prevVisibleRef.current !== nextVisible) {
            prevVisibleRef.current = nextVisible;
            setVisible(nextVisible);
            assets.forEach((asset) => {
                setVisibility(asset.modelUuid, nextVisible);
            })
        }
    });

    return (
        <mesh
            castShadow
            receiveShadow
            name={`Wall-${wall.wallUuid}`}
            key={wall.wallUuid}
            userData={wall}
        >
            {(assets.length > 0 || (walls[0].wallUuid === wall.wallUuid && wallAssets.length > 0)) ?
                <Base
                    name={`BaseWall${wall.wallUuid}`}
                    castShadow
                    receiveShadow
                    ref={meshRef}
                    geometry={visible ? geometry : new THREE.BoxGeometry(0, 0)}
                    position={[centerX, centerY, centerZ]}
                    rotation={[0, -angle, 0]}
                    userData={wall}
                >
                    {materials.map((material, index) => (
                        <primitive key={index} visible={visible} object={material} attach={`material-${index}`} />
                    ))}
                </Base>
                :
                <mesh
                    castShadow
                    receiveShadow
                    ref={meshRef}
                    geometry={geometry}
                    position={[centerX, centerY, centerZ]}
                    rotation={[0, -angle, 0]}
                    userData={wall}
                >
                    {materials.map((material, index) => (
                        <primitive key={index} visible={visible} object={material} attach={`material-${index}`} />
                    ))}
                </mesh>
            }
            <mesh
                castShadow
                receiveShadow
                geometry={geometry}
                position={[centerX, centerY, centerZ]}
                rotation={[0, -angle, 0]}
                userData={wall}
                name={`WallReference_${wall.wallUuid}`}
                onDoubleClick={(e) => {
                    if (visible && !toggleView && activeModule === 'builder') {
                        if (e.object.userData.wallUuid) {
                            e.stopPropagation();
                            setSelectedWall(e.object);
                            setSelectedDecal(null);
                        }
                    }
                }}
                onPointerMissed={() => {
                    if (selectedWall && selectedWall.userData.wallUuid === wall.wallUuid) {
                        setSelectedWall(null);
                    }
                }}
            >
                <MeshDiscardMaterial />

                {wall.decals.map((decal) => (
                    <DecalInstance parent={wall} visible={visible} key={decal.decalUuid} decal={decal} />
                ))}
            </mesh>
        </mesh>
    );
}

export default Wall;