import * as THREE from 'three';
import { state } from '../state.js';
import { SceneFeature } from './SceneFeature.js';
import sceneFeatureManager from './SceneFeatureManager.js';

// --- Scene dimensions for positioning ---
const stageHeight = 1.5;
const stageDepth = 5;
const length = 24;
const moveSpeed = 0.8;
const movementArea = { x: 15, z: length, y: 0, centerZ: -2 };
const jumpChance = 0.01;
const jumpDuration = 0.3;
const jumpHeight = 0.2;
const jumpVariance = 0.1;
const rushIn = false;
const waitTimeBase = 15;
const waitTimeVariance = 120;

// --- Guest Properties ---
const guestHeight = 1.8; // Approx height of the blob+head

export class PartyGuests extends SceneFeature {
    constructor() {
        super();
        this.guests = [];
        this.guestPool = []; // Store all created guests to reuse them
        sceneFeatureManager.register(this);
        
        this.organizedMode = null;
        this.organizedModeTimer = 0;
        this.wasBlackout = false;
        this.lastOrganizedLogTime = 0;
        this.organizedModeMinDuration = 0;
    }

    startOrganizedMode() {
        this.organizedModeTimer = 10 + Math.random() * 10; // 10-20 seconds
        this.organizedModeMinDuration = 5.0;
        
        const music = state.music;
        this.organizedMode = 'RUNNING'; // Default

        if (music) {
            if (music.beatIntensity > 0.7) {
                // High intensity: Moshpit or Jumping
                this.organizedMode = Math.random() < 0.5 ? 'JUMPING' : 'MOSHPIT';
            } else if (music.loudnessHighs > 0.6) {
                this.organizedMode = 'HANDS_UP';
            }
        }
        
        // Random variation
        if (Math.random() < 0.4) {
             const modes = ['RUNNING', 'JUMPING', 'HANDS_UP', 'CIRCLE_PIT', 'MOSHPIT'];
             this.organizedMode = modes[Math.floor(Math.random() * modes.length)];
        }
        console.log(`Organized mode started: ${this.organizedMode}`);

        this.guests.forEach(guest => {
            let chance = 0.1;
            // Higher chance if in the middle or front
            if (Math.abs(guest.mesh.position.x) < 5) chance += 0.3;
            if (guest.mesh.position.z < -5) chance += 0.3;
            
            guest.isInOrganizedMode = Math.random() < chance;
        });
    }

    init() {
        // Shared Geometries
        // Body: Tall blob (Capsule)
        const bodyGeo = new THREE.CapsuleGeometry(0.3, 0.8, 4, 8);
        // Head: Sphere
        const headGeo = new THREE.SphereGeometry(0.25, 16, 16);
        // Arm: Ellipsoid (Scaled Sphere)
        const armGeo = new THREE.SphereGeometry(0.12, 16, 16);
        
        this.geometries = { bodyGeo, headGeo, armGeo };

        // Initialize with config count
        this.setGuestCount(state.config.guestCount);
    }

    createGuest() {
        // Random Color
        // Dark gray-blue shades
        const color = new THREE.Color().setHSL(
            0.6 + (Math.random() * 0.1 - 0.05), // Hue around 0.6 (blue)
            0.1 + Math.random() * 0.1,          // Low saturation
            0.01 + Math.random() * 0.05          // Much darker lightness
        );
        const material = new THREE.MeshStandardMaterial({
            color: color,
            roughness: 0.6,
            metalness: 0.1,
        });

        const group = new THREE.Group();

        const scale = 0.85 + Math.random() * 0.3;
        group.scale.setScalar(scale);

        // Body
        const body = new THREE.Mesh(this.geometries.bodyGeo, material);
        body.position.y = 0.7; 
        body.castShadow = true;
        body.receiveShadow = true;
        group.add(body);

        // Head
        const head = new THREE.Mesh(this.geometries.headGeo, material);
        head.position.y = 1.55; 
        head.castShadow = true;
        head.receiveShadow = true;
        group.add(head);

        // Arms
        const createArm = (isLeft) => {
            const pivot = new THREE.Group();
            // Shoulder position
            pivot.position.set(isLeft ? -0.35 : 0.35, 1.3, 0);
            
            const arm = new THREE.Mesh(this.geometries.armGeo, material);
            arm.scale.set(1, 3.5, 1); // Ellipsoid
            arm.position.y = -0.4; // Hang down from pivot
            arm.castShadow = true;
            arm.receiveShadow = true;
            
            pivot.add(arm);
            return pivot;
        };

        const leftArm = createArm(true);
        const rightArm = createArm(false);
        
        group.add(leftArm);
        group.add(rightArm);

        // Position
        const pos = new THREE.Vector3(
            (Math.random() - 0.5) * movementArea.x,
            0,
            movementArea.centerZ + ( rushIn
                ? (((Math.random()-0.5) * length * 0.6) - length * 0.3)
                : ((Math.random()-0.5) * length))
        );
        
        group.position.copy(pos);
        group.visible = false;
        state.scene.add(group);

        return {
            mesh: group,
            leftArm,
            rightArm,
            state: 'WAITING',
            targetPosition: pos.clone(),
            waitStartTime: 0,
            waitTime: 3 + Math.random() * 4, 
            isJumping: false,
            jumpStartTime: 0,
            jumpHeight: 0,
            shouldRaiseArms: false,
            handsUpTimer: 0,
            handsRaisedType: 'BOTH',
            randomOffset: Math.random() * 100,
            isInOrganizedMode: false
        };
    }

    setGuestCount(count) {
        // Ensure we have enough guests in the pool
        while (this.guestPool.length < count) {
            this.guestPool.push(this.createGuest());
        }

        // Update visibility and active list
        this.guests = [];
        this.guestPool.forEach((guest, index) => {
            if (index < count) {
                guest.mesh.visible = state.partyStarted; // Only visible if party started
                this.guests.push(guest);
            } else {
                guest.mesh.visible = false;
            }
        });
    }

    update(deltaTime) {
        if (this.guests.length === 0 || !state.partyStarted) return;

        const time = state.clock.getElapsedTime();

        // --- Organized Mode Logic ---
        const isBlackout = state.blackoutMode;
        if (!isBlackout && this.wasBlackout) {
            // Blackout just ended
            if (Math.random() < 0.2) {
                this.startOrganizedMode();
            }
        }
        this.wasBlackout = isBlackout;

        if (this.organizedMode) {
            this.organizedModeTimer -= deltaTime;
            this.organizedModeMinDuration -= deltaTime;
            if (time - this.lastOrganizedLogTime > 1.0) {
                const count = this.guests.filter(g => g.isInOrganizedMode).length;
                console.log(`Organized Event Active: ${this.organizedMode}. Participants: ${count}`);
                this.lastOrganizedLogTime = time;
            }

            if (this.organizedModeTimer <= 0 || (isBlackout && this.organizedModeMinDuration <= 0)) {
                console.log(`Organized mode ended: ${this.organizedMode}`);
                
                this.guests.forEach(guest => {
                    if (guest.isInOrganizedMode) {
                        guest.state = 'MOVING';
                        guest.targetPosition.set(
                            (Math.random() - 0.5) * movementArea.x,
                            0,
                            movementArea.centerZ + (Math.random() - 0.5) * movementArea.z
                        );
                        guest.moveSpeed = moveSpeed * 1.5;
                        guest.isInOrganizedMode = false;
                    }
                });

                this.organizedMode = null;
            }
        }

        const minDistance = 0.8; // Minimum distance to maintain
        const minDistSq = minDistance * minDistance;

        // --- Spatial Grid Optimization ---
        const cellSize = 2.0;
        const grid = {};
        
        // Populate grid
        for (let i = 0; i < this.guests.length; i++) {
            const g = this.guests[i];
            const cx = Math.floor(g.mesh.position.x / cellSize);
            const cz = Math.floor(g.mesh.position.z / cellSize);
            const key = cx + ':' + cz;
            if (!grid[key]) grid[key] = [];
            grid[key].push(i);
        }
        
        this.guests.forEach((guestObj, i) => {
            const { mesh, leftArm, rightArm } = guestObj;

            // Dynamic join/leave logic
            if (this.organizedMode) {
                if (guestObj.isInOrganizedMode) {
                    if (Math.random() < 0.001) guestObj.isInOrganizedMode = false;
                } else {
                    if (Math.random() < 0.001) guestObj.isInOrganizedMode = true;
                }
            } else {
                guestObj.isInOrganizedMode = false;
            }

            let currentMoveSpeed = moveSpeed;
            if (this.organizedMode === 'RUNNING' && guestObj.isInOrganizedMode) {
                currentMoveSpeed = moveSpeed * 2.0;
                if (guestObj.state === 'WAITING') {
                    guestObj.waitTime = 0;
                }
            } else if (this.organizedMode === 'MOSHPIT' && guestObj.isInOrganizedMode) {
                currentMoveSpeed = moveSpeed * 2.0;
                if (guestObj.state === 'WAITING') {
                    guestObj.waitTime = 0;
                }
            } else if (this.organizedMode === 'HANDS_UP' && guestObj.isInOrganizedMode) {
                guestObj.handsUpTimer = 0.5;
                guestObj.handsRaisedType = 'BOTH';
            } else if (this.organizedMode && !guestObj.isInOrganizedMode) {
                currentMoveSpeed = moveSpeed * 0.5;
            }

            // --- Collision Avoidance ---
            let separationX = 0;
            let separationZ = 0;
            
            const cx = Math.floor(mesh.position.x / cellSize);
            const cz = Math.floor(mesh.position.z / cellSize);

            for (let ox = -1; ox <= 1; ox++) {
                for (let oz = -1; oz <= 1; oz++) {
                    const key = (cx + ox) + ':' + (cz + oz);
                    const cell = grid[key];
                    if (!cell) continue;

                    for (let k = 0; k < cell.length; k++) {
                        const j = cell[k];
                        if (i === j) continue;

                        const otherMesh = this.guests[j].mesh;
                        const dx = mesh.position.x - otherMesh.position.x;
                        const dz = mesh.position.z - otherMesh.position.z;
                        const distSq = dx*dx + dz*dz;
                        
                        if (distSq < minDistSq && distSq > 0.0001) {
                            const dist = Math.sqrt(distSq);
                            const force = (minDistance - dist) / minDistance; 
                            separationX += (dx / dist) * force;
                            separationZ += (dz / dist) * force;
                        }
                    }
                }
            }
            
            const separationStrength = 2.0;
            mesh.position.x += separationX * separationStrength * deltaTime;
            mesh.position.z += separationZ * separationStrength * deltaTime;

            if (this.organizedMode === 'CIRCLE_PIT' && guestObj.isInOrganizedMode) {
                // --- Circle Pit Logic ---
                const centerZ = movementArea.centerZ;
                const radius = 3.5;
                const speed = 5.0;
                
                const dx = mesh.position.x;
                const dz = mesh.position.z - centerZ;
                const dist = Math.sqrt(dx*dx + dz*dz);
                
                if (dist > radius + 1.0) {
                    // Run towards circle
                    const dirX = -dx / dist;
                    const dirZ = -dz / dist;
                    
                    mesh.position.x += dirX * speed * deltaTime;
                    mesh.position.z += dirZ * speed * deltaTime;
                    
                    mesh.rotation.y = Math.atan2(dirX, dirZ);
                } else {
                    // Run in circle
                    // Tangent vector (counter-clockwise)
                    let vx = -dz;
                    let vz = dx;
                    
                    // Normalize
                    if (dist > 0.001) {
                        vx /= dist;
                        vz /= dist;
                    }
                    
                    // Move along tangent
                    mesh.position.x += vx * speed * deltaTime;
                    mesh.position.z += vz * speed * deltaTime;
                    
                    // Radius Correction (Pull towards ideal circle)
                    const ratio = 1.0 + (radius - dist) * 2.0 * deltaTime;
                    mesh.position.x *= ratio; // Assumes center X is 0
                    mesh.position.z = centerZ + (mesh.position.z - centerZ) * ratio;

                    // Face movement direction
                    mesh.rotation.y = Math.atan2(vx, vz);
                }

                // Keep state as MOVING so other logic (like jumping) works, but skip standard pathfinding
                guestObj.state = 'MOVING'; 

            } else {
                // --- Standard Logic (includes MOSHPIT) ---

            if (guestObj.state === 'WAITING') {
                // Face the stage (approx z = -20)
                const dx = 0 - mesh.position.x;
                const dz = -20 - mesh.position.z;
                const targetRotation = Math.atan2(dx, dz);
                let rotDiff = targetRotation - mesh.rotation.y;
                while (rotDiff > Math.PI) rotDiff -= Math.PI * 2;
                while (rotDiff < -Math.PI) rotDiff += Math.PI * 2;
                mesh.rotation.y += rotDiff * deltaTime * 2.0;

                // Gentle Bob and Sway
                if (!guestObj.isJumping) {
                    const bobSpeed = 6.0;
                    mesh.position.y = Math.abs(Math.sin(time * bobSpeed + guestObj.randomOffset)) * 0.05;
                    mesh.rotation.z = Math.sin(time * (bobSpeed * 0.5) + guestObj.randomOffset) * 0.05;
                } else {
                    mesh.rotation.z = 0;
                }

                // Moshpit: Don't wait long
                if (this.organizedMode === 'MOSHPIT' && guestObj.isInOrganizedMode) {
                    guestObj.waitTime = 0;
                }

                if (time > guestObj.waitStartTime + guestObj.waitTime) {
                    // Gravitate towards stage (negative Z)
                    const minZ = movementArea.centerZ - movementArea.z / 2;
                    const maxZ = movementArea.centerZ + movementArea.z / 2;
                    const zRatio = Math.pow(Math.random(), 1.5); // Bias towards 0 (front)

                    const newTarget = new THREE.Vector3(
                        (Math.random() - 0.5) * movementArea.x,
                        0,
                        minZ + zRatio * (maxZ - minZ)
                    );
                    guestObj.targetPosition = newTarget;

                    // Moshpit: Target center area more often
                    if (this.organizedMode === 'MOSHPIT' && guestObj.isInOrganizedMode) {
                        const range = 5.0;
                        guestObj.targetPosition.set(
                            (Math.random() - 0.5) * range,
                            0,
                            movementArea.centerZ + (Math.random() - 0.5) * range
                        );
                    }

                    // Non-participants: Disperse away from center
                    if (this.organizedMode && !guestObj.isInOrganizedMode) {
                        const side = Math.sign(mesh.position.x) || (Math.random() < 0.5 ? 1 : -1);
                        const xOffset = (movementArea.x / 2) * (0.5 + Math.random() * 0.4);
                        guestObj.targetPosition.set(
                            side * xOffset,
                            0,
                            movementArea.centerZ + (Math.random() - 0.5) * movementArea.z
                        );
                    }

                    guestObj.state = 'MOVING';
                }
            } else if (guestObj.state === 'MOVING') {
                const currentPosFlat = new THREE.Vector3(mesh.position.x, 0, mesh.position.z);
                const targetPosFlat = new THREE.Vector3(guestObj.targetPosition.x, 0, guestObj.targetPosition.z);
                
                const distance = currentPosFlat.distanceTo(targetPosFlat);

                // Moshpit: Change direction frequently
                if (this.organizedMode === 'MOSHPIT' && guestObj.isInOrganizedMode && Math.random() < 0.05) {
                    const range = 5.0;
                    guestObj.targetPosition.set(
                        (Math.random() - 0.5) * range,
                        0,
                        movementArea.centerZ + (Math.random() - 0.5) * range
                    );
                }

                if (distance > 0.1) {
                    const direction = targetPosFlat.sub(currentPosFlat).normalize();
                    mesh.position.add(direction.multiplyScalar(currentMoveSpeed * deltaTime));
                    
                    // If moving away from stage (positive Z), drop hands
                    if (direction.z > 0.1) {
                        guestObj.handsUpTimer = 0;
                    }

                    // Face direction
                    const targetRotation = Math.atan2(direction.x, direction.z);
                    let rotDiff = targetRotation - mesh.rotation.y;
                    while (rotDiff > Math.PI) rotDiff -= Math.PI * 2;
                    while (rotDiff < -Math.PI) rotDiff += Math.PI * 2;
                    mesh.rotation.y += rotDiff * deltaTime * 5;

                    if (!guestObj.isJumping) {
                        mesh.position.y = 0;
                        mesh.rotation.z = 0;
                    }

                } else {
                    guestObj.state = 'WAITING';
                    guestObj.waitStartTime = time;
                    guestObj.waitTime = waitTimeBase + Math.random() * waitTimeVariance;
                }
            }
            } // End else (Standard Logic)

            // Virtual Barrier to prevent entering the stage
            const movementAreaLimitZ = movementArea.centerZ - movementArea.z/2;
            if (mesh.position.z < movementAreaLimitZ) {
                mesh.position.z = movementAreaLimitZ;
            }

            // Update hands up timer
            if (guestObj.handsUpTimer > 0) {
                guestObj.handsUpTimer -= deltaTime;
            }

            if (guestObj.isJumping) {
                const jumpProgress = (time - guestObj.jumpStartTime) / jumpDuration;
                if (jumpProgress < 1) {
                    mesh.position.y = Math.sin(jumpProgress * Math.PI) * guestObj.jumpHeight;
                } else {
                    guestObj.isJumping = false;
                    mesh.position.y = 0;
                }
            } else {
                let currentJumpChance = jumpChance * deltaTime; // Base chance over time
                if (this.organizedMode === 'JUMPING' && guestObj.isInOrganizedMode) {
                    currentJumpChance = 5.0 * deltaTime;
                } else if (state.music && state.music.isLoudEnough && state.music.beatIntensity > 0.8) {
                    currentJumpChance = 0.1; // High, fixed chance on the beat
                }

                if (Math.random() < currentJumpChance) {
                    guestObj.isJumping = true;
                    guestObj.jumpHeight = jumpHeight + Math.random() * jumpVariance;
                    guestObj.jumpStartTime = time;
                    
                    if (Math.random() < 0.1) {
                        guestObj.handsUpTimer = 2.0 + Math.random() * 3.0; // Keep hands up for 2-5 seconds
                        const r = Math.random();
                        if (r < 0.33) guestObj.handsRaisedType = 'LEFT';
                        else if (r < 0.66) guestObj.handsRaisedType = 'RIGHT';
                        else guestObj.handsRaisedType = 'BOTH';
                    }
                }
            }

            // Apply Arm Rotation
            let targetLeftAngle = 0;
            let targetRightAngle = 0;

            if (guestObj.handsUpTimer > 0) {
                const upAngle = -Math.PI;
                if (guestObj.handsRaisedType === 'LEFT' || guestObj.handsRaisedType === 'BOTH') {
                    targetLeftAngle = upAngle;
                }
                if (guestObj.handsRaisedType === 'RIGHT' || guestObj.handsRaisedType === 'BOTH') {
                    targetRightAngle = upAngle;
                }
            }

            leftArm.rotation.x = THREE.MathUtils.lerp(leftArm.rotation.x, targetLeftAngle, deltaTime * 5);
            rightArm.rotation.x = THREE.MathUtils.lerp(rightArm.rotation.x, targetRightAngle, deltaTime * 5);
            leftArm.rotation.z = 0;
            rightArm.rotation.z = 0;
        });
    }

    onPartyStart() {
        const stageFrontZ = -40 / 2 + 5 + 5; // In front of the stage
        this.guests.forEach((guestObj, index) => {
            guestObj.mesh.visible = true; // Make sure active guests are visible
            // Rush to the stage
            guestObj.state = 'MOVING';
            if (index % 2 === 0) {
                guestObj.targetPosition.z = stageFrontZ + (Math.random() - 0.5) * 5;
            }
        });
    }

    onPartyEnd() {
        this.guests.forEach(guestObj => {
            guestObj.isJumping = false;
            guestObj.state = 'WAITING';
        });
    }
}

new PartyGuests();