import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';

let scene, camera, renderer, cssRenderer, schoolModel, npcModel, gameRoom, oldSchool, ground, blackBox, room;
const schoolModelPath = './assets/game_pirate_adventure_map.glb';
const npcModelPath = './assets/minecraft_villager_animatable.glb';

// Manual NPC Position Configuration
const npcPositionX = 121.20;
const npcPositionY = 0.26;
const npcPositionZ = -28.05;

// AI Guru Configuration - API key now on server (config.php)
let isChatting = false;
let chatClosedTime = 0; // Cooldown timer to prevent auto-reopen
let chatOverlay, chatInput, chatHistory, sendChatBtn, closeChatBtn, micBtn, micStatus;
let recognition = null; // Speech Recognition instance
let isListening = false;

// Tutorial System
let tutorialModal, tutTitle, tutText, tutBtn, currentTaskEl;
let hasSeenWelcome = sessionStorage.getItem('hasSeenWelcome') === 'true';
let hasSeenClassroom = sessionStorage.getItem('hasSeenClassroom') === 'true';
let hasSeenGaming = sessionStorage.getItem('hasSeenGaming') === 'true';

// Tutorial Messages
const TUTORIAL_MESSAGES = {
    'WELCOME': {
        title: '✨ Selamat Datang!',
        text: 'Gunakan Joystick/WASD untuk bergerak. Temukan 2 Teleporter di depan untuk mulai Belajar atau Bermain.',
        task: 'Eksplorasi Area'
    },
    'CLASSROOM': {
        title: '📚 Ruang Kelas',
        text: 'Temukan Pak Guru (Villager) dan dekati dia. Klik tombol interaksi untuk mulai tanya jawab dengan AI.',
        task: 'Temui Pak Guru'
    },
    'GAMING': {
        title: '🎮 Game Station',
        text: 'Dekati meja komputer. Klik tombol hijau yang muncul untuk memilih permainan.',
        task: 'Pilih Permainan'
    }
};

function showTutorial(type) {
    console.log('🎓 showTutorial called with:', type);
    console.log('🎓 tutorialModal:', tutorialModal);
    console.log('🎓 tutTitle:', tutTitle);
    console.log('🎓 tutText:', tutText);

    if (!tutorialModal || !tutTitle || !tutText) {
        console.log('🎓 ERROR: Tutorial elements not found!');
        return;
    }

    const msg = TUTORIAL_MESSAGES[type];
    if (!msg) return;

    tutTitle.textContent = msg.title;
    tutText.textContent = msg.text;
    tutorialModal.style.display = 'flex';  // Show modal
    tutorialModal.classList.remove('hidden');
    console.log('🎓 Tutorial modal should now be visible!');

    // Update current task HUD
    if (currentTaskEl) {
        currentTaskEl.textContent = msg.task;
    }

    // Mark as seen
    if (type === 'WELCOME') {
        hasSeenWelcome = true;
        sessionStorage.setItem('hasSeenWelcome', 'true');
    } else if (type === 'CLASSROOM') {
        hasSeenClassroom = true;
        sessionStorage.setItem('hasSeenClassroom', 'true');
    } else if (type === 'GAMING') {
        hasSeenGaming = true;
        sessionStorage.setItem('hasSeenGaming', 'true');
    }
}

function closeTutorial() {
    if (tutorialModal) {
        tutorialModal.style.display = 'none';  // Hide modal
        tutorialModal.classList.add('hidden');
    }
}





// Movement State
let moveForward = 0;
let moveSide = 0;
let keyMoveForward = 0;
let keyMoveSide = 0;
const speed = 0.2; // Walking speed (standard)
const playerHeight = 2.2; // Eye level (Adjusted to 2.2)

// Physics / Realism
let raycaster;
const downVector = new THREE.Vector3(0, -1, 0);
let bobTimer = 0;

// Low Graphics Mode System
let isLowGraphicsMode = localStorage.getItem('lowGraphicsMode') === 'true';
window.isLowGraphicsMode = isLowGraphicsMode;

// Listen for Low Graphics Mode changes from index.php
window.addEventListener('lowGraphicsModeChanged', (event) => {
    isLowGraphicsMode = event.detail.enabled;
    applyGraphicsSettings();
    console.log(`🎮 Graphics Mode Changed: ${isLowGraphicsMode ? 'LOW' : 'NORMAL'}`);
});

function applyGraphicsSettings() {
    if (!renderer) return;

    if (isLowGraphicsMode) {
        // LOW GRAPHICS MODE - Optimize for performance
        renderer.setPixelRatio(0.5); // Reduce resolution by 50%
        renderer.shadowMap.enabled = false; // Disable shadows completely

        // Reduce shadow quality if shadows are re-enabled
        if (renderer.shadowMap.enabled) {
            renderer.shadowMap.type = THREE.BasicShadowMap;
        }

        // Disable antialiasing effect by using lower quality
        renderer.antialias = false;

        // Reduce fog distance for less rendering
        if (scene && scene.fog) {
            scene.fog.near = 5;
            scene.fog.far = 50;
        }

        console.log('🎮 Low Graphics applied: shadows OFF, pixelRatio 0.5');
    } else {
        // NORMAL GRAPHICS MODE - Full quality
        renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        renderer.shadowMap.enabled = true;
        renderer.shadowMap.type = THREE.PCFSoftShadowMap;

        // Restore fog
        if (scene && scene.fog) {
            scene.fog.near = 10;
            scene.fog.far = 150;
        }

        console.log('🎮 Normal Graphics applied: shadows ON, full pixelRatio');
    }

    // Force resize to apply pixel ratio changes
    renderer.setSize(window.innerWidth, window.innerHeight);
}

// Camera Rotation State
let lon = 0, lat = 0;
let phi = 0, theta = 0;
let isDragging = false;
let onPointerDownPointerX = 0, onPointerDownPointerY = 0;
let onPointerDownLon = 0, onPointerDownLat = 0;

// Elements
const loadingBarElement = document.getElementById('loading-bar');
const loadingBarContainer = document.getElementById('loading-bar-container');
const modelFileNameElement = document.getElementById('modelFileName');
const resetCameraButton = document.getElementById('resetCameraButton');
const instructions = document.getElementById('instructions');
const debugLog = document.getElementById('debug-log');
const interactBtn = document.getElementById('interactBtn');

function log(msg) {
    debugLog.innerHTML += '<br>' + msg;
    console.log(msg);
}

// Helper to update NPC Text
function updateNpcText(message) {
    if (!npcModel) return;

    // Remove old sprite if exists
    const oldSprite = npcModel.getObjectByName("npcTextSprite");
    if (oldSprite) npcModel.remove(oldSprite);

    // Create new sprite
    const textSprite = createTextLabel(message);
    textSprite.name = "npcTextSprite"; // Tag it for removal later

    // Re-apply scale logic (keep size consistent)
    // Reduced Halo Size (approx 30% smaller than doubled size)
    textSprite.scale.set(
        1.4 / npcModel.scale.x,
        0.7 / npcModel.scale.y,
        1 / npcModel.scale.z
    );

    // Re-apply position logic
    const finalBox = new THREE.Box3().setFromObject(npcModel);
    const localTop = (finalBox.max.y - npcModel.position.y) / npcModel.scale.y;
    textSprite.position.set(0, localTop + 0.7, 0);

    npcModel.add(textSprite);
}

function createTextLabel(message) {
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    canvas.width = 350; // Larger canvas width
    canvas.height = 150; // Larger canvas height

    // Bubble style
    context.fillStyle = 'rgba(255, 255, 255, 0.9)';
    context.strokeStyle = 'rgba(0, 0, 0, 0.5)';
    context.lineWidth = 4;

    // Draw rounded rectangle manually for compatibility
    const x = 10, y = 10, w = canvas.width - 2 * x, h = canvas.height - 2 * y, r = 20;
    context.beginPath();
    context.moveTo(x + r, y);
    context.lineTo(x + w - r, y);
    context.quadraticCurveTo(x + w, y, x + w, y + r);
    context.lineTo(x + w, y + h - r);
    context.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
    context.lineTo(x + r, y + h);
    context.quadraticCurveTo(x, y + h, x, y + h - r);
    context.lineTo(x, y + r);
    context.quadraticCurveTo(x, y, x + r, y);
    context.closePath();
    context.fill();
    context.stroke();

    // Text
    context.font = 'bold 40px Arial'; // Smaller font size
    context.fillStyle = '#000000';
    context.textAlign = 'center';
    context.textBaseline = 'middle';
    context.fillText(message, canvas.width / 2, canvas.height / 2); // Center text

    const texture = new THREE.CanvasTexture(canvas);
    const material = new THREE.SpriteMaterial({ map: texture, depthTest: false, depthWrite: false });
    const sprite = new THREE.Sprite(material);
    // Adjust base sprite scale to new canvas aspect ratio
    sprite.scale.set(canvas.width / 100, canvas.height / 100, 1);
    sprite.renderOrder = 999;

    return sprite;
}

// --- AI GURU FUNCTIONS ---

function speakText(text) {
    if ('speechSynthesis' in window) {
        // Cancel any ongoing speech
        window.speechSynthesis.cancel();

        const utterance = new SpeechSynthesisUtterance(text);
        utterance.rate = 0.9; // Slightly slower for authority
        utterance.pitch = 1.0;

        // Try to find Indonesian voice
        const voices = window.speechSynthesis.getVoices();
        const indoVoice = voices.find(voice => voice.lang.includes('id'));
        if (indoVoice) {
            utterance.voice = indoVoice;
        }

        window.speechSynthesis.speak(utterance);
    }
}

async function askTeacher(question) {
    // Using PHP Backend Proxy (API key aman di server)
    const apiUrl = 'api.php';

    console.log("Calling API via PHP proxy...");

    const payload = {
        message: question,
        system_prompt: "Kamu adalah Pak Guru yang bijaksana, ramah, dan pintar di sebuah sekolah virtual. Jawablah pertanyaan muridmu dengan bahasa Indonesia yang mendidik, singkat (maksimal 2 kalimat), dan jelas.",
        max_tokens: 150,
        temperature: 0.7
    };

    try {
        const response = await fetch(apiUrl, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(payload)
        });

        console.log("API Response Status:", response.status);
        const data = await response.json();
        console.log("API Response Data:", data);

        if (data.choices && data.choices[0] && data.choices[0].message) {
            return data.choices[0].message.content;
        } else if (data.error) {
            console.error("API Error:", data.error);
            throw new Error(data.error.message || "API Error");
        } else {
            throw new Error("Invalid API response structure");
        }
    } catch (error) {
        console.error("API Proxy Error:", error);
        return "Maaf, Pak Guru sedang pusing memikirkan nilai ujian. Coba tanya lagi nanti ya.";
    }
}

function addChatMessage(sender, text) {
    const div = document.createElement('div');
    div.className = 'flex items-start gap-3 ' + (sender === 'user' ? 'flex-row-reverse' : '');

    const icon = sender === 'user' ? '👤' : '🤖';
    const bgClass = sender === 'user' ? 'bg-blue-600 text-white rounded-tr-none' : 'bg-blue-900 bg-opacity-50 text-blue-100 rounded-tl-none border border-blue-500';

    div.innerHTML = `
        <div class="w-8 h-8 rounded-full ${sender === 'user' ? 'bg-gray-600' : 'bg-blue-500'} flex-shrink-0 flex items-center justify-center text-sm">${icon}</div>
        <div class="${bgClass} p-3 rounded-lg shadow-md max-w-[85%]">
            ${text}
        </div>
    `;

    chatHistory.appendChild(div);
    chatHistory.scrollTop = chatHistory.scrollHeight;
}

async function handleChatSubmit() {
    const text = chatInput.value.trim();
    if (!text) return;

    // 1. User Message
    addChatMessage('user', text);
    chatInput.value = '';

    // 2. Loading State
    const loadingId = 'loading-' + Date.now();
    const loadingDiv = document.createElement('div');
    loadingDiv.id = loadingId;
    loadingDiv.className = 'flex items-center gap-2 text-gray-400 text-sm italic ml-12';
    loadingDiv.innerHTML = '<span class="animate-pulse">Pak Guru sedang berpikir...</span>';
    chatHistory.appendChild(loadingDiv);
    chatHistory.scrollTop = chatHistory.scrollHeight;

    // 3. Call API
    const answer = await askTeacher(text);

    // 4. Remove Loading & Show Answer
    const loadingEl = document.getElementById(loadingId);
    if (loadingEl) loadingEl.remove();

    addChatMessage('teacher', answer);

    // 5. Speak
    speakText(answer);
}

function toggleChat(show) {
    isChatting = show;
    if (show) {
        // Use direct style manipulation for guaranteed visibility
        chatOverlay.style.display = 'flex';
        chatOverlay.classList.remove('hidden');
        setTimeout(() => chatInput.focus(), 100); // Delay focus for mobile keyboard
        // Reset movement keys to prevent getting stuck moving
        keyMoveForward = 0;
        keyMoveSide = 0;
        moveForward = 0;
        moveSide = 0;
        console.log("Chat opened!"); // Debug
    } else {
        chatOverlay.style.display = 'none';
        chatOverlay.classList.add('hidden');
        window.speechSynthesis.cancel(); // Stop talking if closed
        chatClosedTime = Date.now(); // Set cooldown timer
        console.log("Chat closed!"); // Debug
    }
}

export function init() {
    log("Starting init()...");

    // Initialize Chat Elements (must be inside init() because DOM isn't ready at module load time)
    chatOverlay = document.getElementById('chat-overlay');
    chatInput = document.getElementById('chatInput');
    chatHistory = document.getElementById('chat-history');
    sendChatBtn = document.getElementById('sendChatBtn');
    closeChatBtn = document.getElementById('closeChatBtn');
    micBtn = document.getElementById('micBtn');
    micStatus = document.getElementById('micStatus');

    // Initialize Tutorial Elements
    tutorialModal = document.getElementById('tutorial-modal');
    tutTitle = document.getElementById('tut-title');
    tutText = document.getElementById('tut-text');
    tutBtn = document.getElementById('tut-btn');
    currentTaskEl = document.getElementById('current-task');

    console.log('🎓 Tutorial Elements Found:', {
        modal: !!tutorialModal,
        title: !!tutTitle,
        text: !!tutText,
        btn: !!tutBtn,
        task: !!currentTaskEl
    });
    console.log('🎓 hasSeenWelcome:', hasSeenWelcome);

    // Tutorial Button Listener
    if (tutBtn) {
        tutBtn.addEventListener('click', closeTutorial);
    }

    // Show WELCOME tutorial on first visit
    if (!hasSeenWelcome) {
        console.log('🎓 Scheduling WELCOME tutorial...');
        setTimeout(() => showTutorial('WELCOME'), 1000); // Delay 1s for scene to load
    } else {
        console.log('🎓 SKIPPING welcome - already seen (check sessionStorage)');
    }

    // Debug: Check if elements were found
    console.log("Chat Elements Check:", {
        chatOverlay: !!chatOverlay,
        chatInput: !!chatInput,
        chatHistory: !!chatHistory,
        sendChatBtn: !!sendChatBtn,
        closeChatBtn: !!closeChatBtn,
        micBtn: !!micBtn
    });

    // Setup Speech Recognition
    const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (SpeechRecognition) {
        recognition = new SpeechRecognition();
        recognition.lang = 'id-ID'; // Bahasa Indonesia
        recognition.continuous = true; // Keep listening until manually stopped
        recognition.interimResults = true; // Show partial results

        recognition.onstart = () => {
            isListening = true;
            if (micBtn) micBtn.classList.add('bg-red-500');
            if (micBtn) micBtn.classList.remove('bg-gray-600');
            if (micStatus) {
                micStatus.classList.remove('hidden');
                micStatus.textContent = "🎤 Sedang mendengarkan... (bicara sekarang)";
            }
            console.log("🎤 Listening...");
        };

        recognition.onend = () => {
            isListening = false;
            if (micBtn) micBtn.classList.remove('bg-red-500');
            if (micBtn) micBtn.classList.add('bg-gray-600');
            if (micStatus) micStatus.classList.add('hidden');
            console.log("🎤 Stopped listening");
        };

        recognition.onresult = (event) => {
            let finalTranscript = '';
            let interimTranscript = '';

            for (let i = event.resultIndex; i < event.results.length; i++) {
                const transcript = event.results[i][0].transcript;
                if (event.results[i].isFinal) {
                    finalTranscript += transcript;
                } else {
                    interimTranscript += transcript;
                }
            }

            // Show interim results in input
            if (interimTranscript && chatInput) {
                chatInput.value = interimTranscript;
                if (micStatus) micStatus.textContent = "🎤 " + interimTranscript;
            }

            // When we have final result, submit
            if (finalTranscript) {
                console.log("🎤 Heard:", finalTranscript);
                if (chatInput) chatInput.value = finalTranscript;
                recognition.stop(); // Stop after getting final result
                handleChatSubmit();
            }
        };

        recognition.onerror = (event) => {
            console.error("🎤 Error:", event.error);

            // Handle specific errors
            if (event.error === 'no-speech') {
                if (micStatus) micStatus.textContent = "🎤 Tidak ada suara terdeteksi, coba lagi...";
                // Don't stop, keep listening
                return;
            }

            isListening = false;
            if (micBtn) micBtn.classList.remove('bg-red-500');
            if (micBtn) micBtn.classList.add('bg-gray-600');
            if (micStatus) micStatus.classList.add('hidden');
        };

        // Mic Button Click Handler
        if (micBtn) {
            micBtn.addEventListener('click', () => {
                if (isListening) {
                    recognition.stop();
                } else {
                    recognition.start();
                }
            });
        }
    } else {
        console.warn("Speech Recognition not supported in this browser");
        if (micBtn) micBtn.style.display = 'none'; // Hide mic button if not supported
    }

    // Interaction Button Listener
    // Interaction Button Listener
    interactBtn.addEventListener('click', () => {
        if (npcModel) {
            // New Logic: Open Chat
            toggleChat(true);
            interactBtn.style.display = 'none';
        }
    });

    // Chat Event Listeners
    if (sendChatBtn) {
        sendChatBtn.addEventListener('click', handleChatSubmit);
    }

    if (chatInput) {
        chatInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') handleChatSubmit();
        });
    }

    if (closeChatBtn) {
        closeChatBtn.addEventListener('click', () => toggleChat(false));
    }

    // Safety Timeout for Loading Bar
    setTimeout(() => {
        if (loadingBarContainer.style.display !== 'none') {
            log("Loading timeout: forcing hide.");
            loadingBarContainer.style.display = 'none';
        }
    }, 5000);

    // Scene
    scene = new THREE.Scene();
    const roomColor = 0x1a1a1a; // Dark Grey for closed room
    scene.background = new THREE.Color(roomColor);
    scene.fog = new THREE.Fog(roomColor, 10, 400); // Adjust fog to blend with walls

    // Enclosure (Box Room) - "Kubah Kotak"
    // Bounds based on user input: X[-34, 26], Z[-96, 96]
    // Width: 60, Depth: 192
    // Center: X=-4, Z=0
    const roomGeo = new THREE.BoxGeometry(60, 100, 192);
    const roomMat = new THREE.MeshStandardMaterial({
        color: roomColor,
        side: THREE.BackSide, // Visible from inside
        roughness: 0.8,
        metalness: 0.2
    });
    room = new THREE.Mesh(roomGeo, roomMat);
    room.position.set(-4, 50, 0); // Center Y at 50 (half height)
    room.receiveShadow = true;
    scene.add(room);



    // Camera
    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.rotation.order = 'YXZ';
    resetCamera();

    // Physics (Init after camera for sprite support)
    raycaster = new THREE.Raycaster();
    raycaster.camera = camera;

    // Detect Mobile
    const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth < 1024;

    // Renderer (WebGL)
    // Optimize: Disable antialias on mobile for performance
    renderer = new THREE.WebGLRenderer({ antialias: !isMobile });

    // Optimize: Reduce resolution on mobile (1.0 is standard, native can be 3.0+)
    renderer.setPixelRatio(isMobile ? 1.0 : window.devicePixelRatio);

    renderer.setSize(window.innerWidth, window.innerHeight);

    // Optimize: Disable shadows on mobile
    renderer.shadowMap.enabled = !isMobile;

    renderer.domElement.style.position = 'absolute';
    renderer.domElement.style.top = '0';
    renderer.domElement.style.zIndex = '1'; // Behind CSS3D

    // Append to mobile-frame container on mobile, or body on desktop
    const targetContainer = isMobile ? (document.getElementById('mobile-frame') || document.body) : document.body;
    targetContainer.appendChild(renderer.domElement);

    // Renderer (CSS3D)
    cssRenderer = new CSS3DRenderer();
    cssRenderer.setSize(window.innerWidth, window.innerHeight);
    cssRenderer.domElement.style.position = 'absolute';
    cssRenderer.domElement.style.top = '0';
    cssRenderer.domElement.style.zIndex = '2'; // On top
    cssRenderer.domElement.style.pointerEvents = 'none'; // Allow clicks to pass through to WebGL
    targetContainer.appendChild(cssRenderer.domElement);

    // Apply Low Graphics Mode if enabled (after renderer is ready)
    applyGraphicsSettings();

    // --- GAME OBJECT & STAND REMOVED ---
    // User requested to remove the videotron game screen.


    // Lighting
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.8);
    scene.add(ambientLight);

    const dirLight = new THREE.DirectionalLight(0xffffff, 1.0);
    dirLight.position.set(10, 20, 10);
    // Optimize: No shadows on mobile
    dirLight.castShadow = !isMobile;
    scene.add(dirLight);

    // Ground Plane - REPLACED with tanah.glb
    // We will load it below with the other GLTFs

    // GLTF Manager
    const manager = new THREE.LoadingManager();
    manager.onProgress = (item, loaded, total) => {
        loadingBarElement.style.width = (loaded / total * 100) + '%';
    };
    manager.onLoad = () => {
        loadingBarContainer.style.display = 'none';
    };

    const loader = new GLTFLoader(manager);

    // 0. Load Ground (tanah.glb)
    const groundPath = './assets/tanah.glb';
    log("Loading ground: " + groundPath);
    loader.load(groundPath, (gltf) => {
        log("Ground loaded!");
        const groundModel = gltf.scene;

        // Measure size
        const box = new THREE.Box3().setFromObject(groundModel);
        const size = new THREE.Vector3();
        box.getSize(size);
        log(`Ground Tile Size: ${size.x.toFixed(2)}, ${size.z.toFixed(2)}`);

        // Tile it to cover 500x500
        const gridSize = 500;
        const tileX = size.x || 10; // Fallback if 0
        const tileZ = size.z || 10;

        const cols = Math.ceil(gridSize / tileX);
        const rows = Math.ceil(gridSize / tileZ);

        const startX = -(cols * tileX) / 2;
        const startZ = -(rows * tileZ) / 2;

        const groundGroup = new THREE.Group();

        for (let i = 0; i < cols; i++) {
            for (let j = 0; j < rows; j++) {
                const clone = groundModel.clone();
                clone.position.set(startX + i * tileX + tileX / 2, 0, startZ + j * tileZ + tileZ / 2);
                clone.traverse((node) => {
                    if (node.isMesh) {
                        node.receiveShadow = true;
                    }
                });
                groundGroup.add(clone);
            }
        }

        // Lower slightly to avoid z-fighting if map touches 0, but map is at 5 now.
        // Let's keep it at 0.
        ground = groundGroup;
        scene.add(groundGroup);

    }, undefined, (error) => {
        log("Error loading Ground: " + error.message);
        // Fallback to simple plane if fails
        const groundGeo = new THREE.PlaneGeometry(500, 500);
        const groundMat = new THREE.MeshStandardMaterial({ color: 0x3b2d22 });
        const ground = new THREE.Mesh(groundGeo, groundMat);
        ground.rotation.x = -Math.PI / 2;
        ground.receiveShadow = true;
        ground = ground;
        scene.add(ground);
    });

    // 1. Load School/Environment (model.glb)
    log("Loading map: " + schoolModelPath);
    loader.load(schoolModelPath, (gltf) => {
        log("Map loaded!");
        schoolModel = gltf.scene;
        schoolModel.traverse((node) => {
            if (node.isMesh) {
                node.castShadow = true;
                node.receiveShadow = true;
            }
        });

        // Auto-scale and Center Logic
        const box = new THREE.Box3().setFromObject(schoolModel);
        const size = new THREE.Vector3();
        box.getSize(size);
        const maxDim = Math.max(size.x, size.y, size.z);

        log(`Original Size: ${size.x.toFixed(2)}, ${size.y.toFixed(2)}, ${size.z.toFixed(2)}`);

        let scaleFactor = 1;
        if (maxDim > 500) {
            scaleFactor = 200 / maxDim; // Normalize to ~200 units
            log(`Map is huge (${maxDim.toFixed(2)}), scaling by ${scaleFactor.toFixed(5)}`);
        } else if (maxDim < 10) {
            scaleFactor = 50 / maxDim; // Normalize to ~50 units if too small
            log(`Map is tiny (${maxDim.toFixed(2)}), scaling by ${scaleFactor.toFixed(5)}`);
        }

        schoolModel.scale.set(scaleFactor, scaleFactor, scaleFactor);

        // Re-calculate box after scaling to center it correctly
        schoolModel.updateMatrixWorld(true);
        const newBox = new THREE.Box3().setFromObject(schoolModel);
        const newCenter = new THREE.Vector3();
        newBox.getCenter(newCenter);

        // Center X/Z only. 
        schoolModel.position.x = -newCenter.x;
        schoolModel.position.z = -newCenter.z;
        schoolModel.position.y = 5; // Raised more as requested

        log(`Repositioned to: ${schoolModel.position.x.toFixed(2)}, ${schoolModel.position.y.toFixed(2)}, ${schoolModel.position.z.toFixed(2)}`);

        scene.add(schoolModel);
        modelFileNameElement.textContent = "Loaded: Pirate Map & Villager";

        // ===== HITUNG UKURAN PETA (BOUNDING BOX) =====
        const mapBox = new THREE.Box3().setFromObject(schoolModel);
        const mapSize = new THREE.Vector3();
        mapBox.getSize(mapSize);
        const mapCenter = new THREE.Vector3();
        mapBox.getCenter(mapCenter);

        log(`Map Size: ${mapSize.x.toFixed(2)} x ${mapSize.y.toFixed(2)} x ${mapSize.z.toFixed(2)}`);

        // ===== KUBAH (ROOM) - KOORDINAT TETAP =====
        // Berdasarkan koordinat yang sudah ditest:
        // X: -18 sampai +15 (lebar 33 + padding 7 = 40)
        // Z: -95 sampai +90 (kedalaman 185 + padding 5 = 190)
        const roomWidth = 40;   // X axis
        const roomHeight = 60;  // Y axis (tinggi)
        const roomDepth = 190;  // Z axis

        if (room) {
            room.geometry.dispose();
            room.geometry = new THREE.BoxGeometry(roomWidth, roomHeight, roomDepth);
            room.position.set(0, 25, 0); // Center di 0,0 dengan Y dinaikkan
            log(`Kubah KOTAK disesuaikan: ${roomWidth} x ${roomHeight} x ${roomDepth}`);
        } else {
            const roomGeo = new THREE.BoxGeometry(roomWidth, roomHeight, roomDepth);
            const roomMat = new THREE.MeshBasicMaterial({
                color: 0x87CEEB, // Sky blue
                side: THREE.BackSide // Visible from inside
            });
            room = new THREE.Mesh(roomGeo, roomMat);
            room.position.set(0, 25, 0);
            scene.add(room);
            log(`Kubah KOTAK dibuat: ${roomWidth} x ${roomHeight} x ${roomDepth}`);
        }


    }, undefined, (error) => {
        log("Error loading Map: " + error.message);
        console.error('Error loading Map GLB:', error);
    });

    // 2. Load NPC (Minecraft Villager)
    log("Loading NPC: " + npcModelPath);
    loader.load(npcModelPath, (gltf) => {
        log("NPC loaded!");
        npcModel = gltf.scene;

        npcModel.traverse((node) => {
            if (node.isMesh) {
                node.castShadow = true;
                node.receiveShadow = true;
            }
        });

        // Reset transforms first
        npcModel.position.set(0, 0, 0);
        npcModel.scale.set(1, 1, 1);
        npcModel.updateMatrixWorld(true);

        // Normalize Height to ~2 meters
        const box = new THREE.Box3().setFromObject(npcModel);
        const initialHeight = box.max.y - box.min.y;
        if (initialHeight > 0) {
            const scaleFactor = 2.0 / initialHeight;
            npcModel.scale.set(scaleFactor, scaleFactor, scaleFactor);
        }
        npcModel.updateMatrixWorld(true);

        // Snap to Ground logic + Manual Offset
        const finalBox = new THREE.Box3().setFromObject(npcModel);
        const yOffset = -finalBox.min.y; // Raise so feet are at 0

        // Apply Position (Absolute from config)
        log(`Setting NPC Position to: ${npcPositionX}, ${npcPositionY + yOffset}, ${npcPositionZ}`);
        npcModel.position.set(npcPositionX, npcPositionY + yOffset, npcPositionZ);
        npcModel.rotation.y = -Math.PI / 4;

        // Initialize State
        npcModel.userData.interactionState = 'halo';

        // Add Initial Halo Text
        scene.add(npcModel);
        updateNpcText("Halo");

    }, undefined, (error) => {
        log("Error loading NPC: " + error.message);
    });

    // 3. Load Teleport (teleport.glb)
    const teleportPath = './assets/teleport.glb';
    log("Loading Teleport: " + teleportPath);
    loader.load(teleportPath, (gltf) => {
        log("Teleport loaded!");
        const teleportModel = gltf.scene;

        teleportModel.traverse((node) => {
            if (node.isMesh) {
                node.castShadow = true;
                node.receiveShadow = true;
            }
        });

        // Diagnostic Log
        const box = new THREE.Box3().setFromObject(teleportModel);
        const size = new THREE.Vector3();
        box.getSize(size);
        log(`Teleport Size: ${size.x.toFixed(2)}, ${size.y.toFixed(2)}, ${size.z.toFixed(2)}`);

        // Auto-scale if tiny
        if (size.y < 0.5) {
            log("Teleport is tiny, scaling up 10x");
            teleportModel.scale.set(10, 10, 10);
        } else if (size.y > 50) {
            log("Teleport is huge, scaling down 0.1x");
            teleportModel.scale.set(0.1, 0.1, 0.1);
        } else {
            teleportModel.scale.set(1, 1, 1);
        }

        // Teleport 1 (Original - Middle) -> To Classroom
        const t1 = teleportModel.clone();
        t1.position.set(-3.15, 5.00, 44.65);
        // Add logic for teleportation
        t1.userData = {
            isTeleporter: true,
            isTeleporter: true,
            destination: new THREE.Vector3(116.80, 0.26, -18.58),
            isTeleporter: true,
            destination: new THREE.Vector3(116.80, 0.26, -18.58),
            facing: -90 // Face North (90 deg right from West)
        };
        teleporters.push(t1);
        scene.add(t1);

        // Label for Teleport 1
        const t1Label = createTextLabel("Classroom");
        t1Label.position.set(-3.15, 5.00 + 3, 44.65); // Position above
        t1Label.scale.set(5, 2.5, 1); // Make it big enough to see
        scene.add(t1Label);

        // Teleport 2 (Right Side) -> To Gaming Room
        const t2 = teleportModel.clone();
        t2.position.set(0.38, 5.90, 45.06); // Coordinates from photo
        t2.userData = {
            isTeleporter: true,
            isTeleporter: true,
            destination: new THREE.Vector3(118.72, 0.30, -51.13),
            isTeleporter: true,
            destination: new THREE.Vector3(118.72, 0.30, -51.13),
            facing: 160 // Face South-South-West (25 deg right from 135)
        };
        teleporters.push(t2); // Add to interactable array
        scene.add(t2);

        // Label for Teleport 2
        const t2Label = createTextLabel("Gaming Room");
        t2Label.position.set(0.38, 5.90 + 3, 45.06);
        t2Label.scale.set(5, 2.5, 1);
        scene.add(t2Label);

        // --- Black Box Enclosure for Gaming Room ---
        // Coordinates: Center approx (114.7, 5.26, -54.26), Size 11x10x11
        const bbGeo = new THREE.BoxGeometry(11, 10, 11);
        const bbMat = new THREE.MeshBasicMaterial({
            color: 0x000000,
            side: THREE.DoubleSide // Visible from inside
        });
        blackBox = new THREE.Mesh(bbGeo, bbMat);
        blackBox.position.set(114.7, 5.26, -54.26);
        scene.add(blackBox);



        log("Placed 3 Teleporters. Middle one is active.");

    }, undefined, (error) => {
        log("Error loading Teleport: " + error.message);
    });

    // 4. Load Old School Model (model.glb) at NPC Location
    const oldSchoolPath = './assets/model.glb';
    log("Loading Old School: " + oldSchoolPath);
    loader.load(oldSchoolPath, (gltf) => {
        log("Old School loaded!");
        oldSchool = gltf.scene;

        oldSchool.traverse((node) => {
            if (node.isMesh) {
                node.castShadow = true;
                node.receiveShadow = true;
            }
        });

        // Position at NPC location
        // Position at Fixed Location (Previous NPC Spot)
        // User requested: "posisi model.glb atau ruangan kelas jangan di pindah"
        // Previous NPC Position: 113.69, 0.26, -18.43
        oldSchool.position.set(113.69, 0.26, -18.43);

        // Optional: Scale it down if it's too big for a prop, or keep as is?
        // The user just said "load model.glb", assuming they want the building there.
        // Let's keep scale 1 for now, or maybe slightly adjusted if it clips.
        oldSchool.scale.set(1, 1, 1);

        scene.add(oldSchool);

    }, undefined, (error) => {
        log("Error loading Old School: " + error.message);
    });

    // 5. Load Game Room (gaming_room.glb)
    const gameRoomPath = './assets/gaming_room.glb';
    log("Loading Game Room: " + gameRoomPath);
    loader.load(gameRoomPath, (gltf) => {
        log("Game Room loaded!");
        gameRoom = gltf.scene;

        // Optimize Performance: Disable shadows for this heavy model
        gameRoom.traverse((node) => {
            if (node.isMesh) {
                node.castShadow = false; // Disable to reduce lag
                node.receiveShadow = false;
            }
        });

        // Position at User Specified Coordinates
        gameRoom.position.set(113.95, 0.26, -54.28);

        // Normalize Scale to "Classroom Size" (approx 15 meters wide)
        const box = new THREE.Box3().setFromObject(gameRoom);
        const size = new THREE.Vector3();
        box.getSize(size);

        const targetSize = 15.0; // Target 15 meters
        const maxDim = Math.max(size.x, size.y, size.z);

        if (maxDim > 0) {
            const scaleFactor = targetSize / maxDim;
            gameRoom.scale.set(scaleFactor, scaleFactor, scaleFactor);
            log(`Scaled Game Room from ${maxDim.toFixed(2)}m to ${targetSize}m (Factor: ${scaleFactor.toFixed(4)})`);
        }

        scene.add(gameRoom);

    }, undefined, (error) => {
        log("Error loading Game Room: " + error.message);
    });

    // --- JOYSTICK & INPUT SETUP ---
    const joystickZone = document.getElementById('joystick-zone');
    const joystickManager = nipplejs.create({
        zone: joystickZone,
        mode: 'static',
        position: { left: '8%', bottom: '8%' }, // Moved closer to corner
        color: 'white',
        size: 120 // Slightly larger for better touch
    });

    joystickManager.on('move', (evt, data) => {
        if (data && data.vector) {
            moveForward = data.vector.y;
            moveSide = data.vector.x;
        }
    });

    joystickManager.on('end', (evt) => {
        moveForward = 0;
        moveSide = 0;
    });

    // Input Event Listeners
    // Use 'document' for move/up to catch drags that go off-canvas
    renderer.domElement.addEventListener('mousedown', onPointerDown);
    renderer.domElement.addEventListener('touchstart', onPointerDown, { passive: false });

    document.addEventListener('mouseup', onPointerUp);

    // ===== KLIK UNTUK LIHAT KOORDINAT =====
    renderer.domElement.addEventListener('dblclick', () => {
        const coords = `Koordinat Anda:\nX: ${camera.position.x.toFixed(2)}\nY: ${camera.position.y.toFixed(2)}\nZ: ${camera.position.z.toFixed(2)}`;
        alert(coords);
        console.log(coords);
    });
    document.addEventListener('touchend', onPointerUp);

    document.addEventListener('mousemove', onPointerMove);
    document.addEventListener('touchmove', onPointerMove, { passive: false });

    window.addEventListener('resize', onWindowResize, false);
    resetCameraButton.addEventListener('click', () => resetCamera(true));

    document.addEventListener('keydown', onKeyDown);
    document.addEventListener('keyup', onKeyUp);

    // Fullscreen Button Logic
    const fsBtn = document.getElementById('fullscreen-btn');
    if (fsBtn) {
        fsBtn.addEventListener('click', () => {
            if (!document.fullscreenElement) {
                document.documentElement.requestFullscreen().catch(err => {
                    console.log(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`);
                });
            } else {
                document.exitFullscreen();
            }
        });
    }

    animate();
}

function onKeyDown(event) {
    switch (event.code) {
        case 'KeyW': keyMoveForward = 1; break;
        case 'KeyS': keyMoveForward = -1; break;
        case 'KeyA': keyMoveSide = -1; break;
        case 'KeyD': keyMoveSide = 1; break;
    }
}

function onKeyUp(event) {
    switch (event.code) {
        case 'KeyW': if (keyMoveForward === 1) keyMoveForward = 0; break;
        case 'KeyS': if (keyMoveForward === -1) keyMoveForward = 0; break;
        case 'KeyA': if (keyMoveSide === -1) keyMoveSide = 0; break;
        case 'KeyD': if (keyMoveSide === 1) keyMoveSide = 0; break;
    }
}

function resetCamera(forceDefault = false) {
    const urlParams = new URLSearchParams(window.location.search);
    const spawnLocation = urlParams.get('spawn');
    const gameId = urlParams.get('game_id');

    // Logic:
    // 1. If spawn=gaming_room -> Gaming Room
    // 2. If game_id is set AND spawn != 'entrance' -> Gaming Room (Implicit default for "Back" button)
    // 3. Else -> Default Spawn (Entrance)

    let shouldSpawnInGamingRoom = false;

    if (!forceDefault && spawnLocation === 'gaming_room') {
        shouldSpawnInGamingRoom = true;
    } else if (!forceDefault && gameId && spawnLocation !== 'entrance') {
        shouldSpawnInGamingRoom = true;
    }

    if (shouldSpawnInGamingRoom) {
        // Spawn in Gaming Room at user specified coordinates
        // X=112.36, Y=0.30, Z=-57.25
        camera.position.set(112.36, 0.30 + playerHeight, -57.25);
        lat = 0; lon = 90; // Facing South (+Z)

        // Clean the URL but PRESERVE game_id
        // This ensures that if they refresh, they go back to spawn (because spawn param is removed),
        // BUT the game_id is kept so the interaction button still works.
        let newUrl = window.location.pathname;
        if (gameId) {
            newUrl += `?game_id=${gameId}`;
        }
        window.history.replaceState({}, document.title, newUrl);

        // Trigger GAMING tutorial if not seen
        if (!hasSeenGaming) {
            setTimeout(() => showTutorial('GAMING'), 1000);
        }
        // Update current task
        if (currentTaskEl) {
            currentTaskEl.textContent = 'Pilih Permainan';
        }
    } else {
        // Default Spawn: X=-3.25, Y=5.00, Z=18.39
        camera.position.set(-3.25, 5 + playerHeight, 18.39);
        lat = 0; lon = 90; // Facing South/Backward
    }

    updateCameraRotation();
}

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;

    // Adjust FOV for Mobile Landscape to "Zoom Out"
    const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth < 1024;
    if (isMobile && window.innerWidth > window.innerHeight) {
        camera.fov = 85; // Wider FOV for landscape mobile
    } else {
        camera.fov = 75; // Default FOV
    }

    camera.updateProjectionMatrix();

    // Update renderer size and pixel ratio on resize (handles orientation change)
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(isMobile ? 1.0 : window.devicePixelRatio);
    cssRenderer.setSize(window.innerWidth, window.innerHeight);
}

// --- MULTI-TOUCH CAMERA CONTROL ---
let dragTouchId = null; // ID of the finger controlling the camera

function onPointerDown(event) {
    // Ignore if clicking on joystick zone (though z-index usually handles this)
    if (event.target.closest('#joystick-zone')) return;

    // Mouse
    if (event.type === 'mousedown') {
        isDragging = true;
        onPointerDownPointerX = event.clientX;
        onPointerDownPointerY = event.clientY;
        onPointerDownLon = lon;
        onPointerDownLat = lat;
        return;
    }

    // Touch
    if (event.type === 'touchstart') {
        // Find a touch that is NOT in the joystick zone
        // We iterate through changedTouches to find a new finger
        for (let i = 0; i < event.changedTouches.length; i++) {
            const t = event.changedTouches[i];

            // Double check target just in case
            if (t.target.closest('#joystick-zone')) continue;

            // Found a valid look finger!
            dragTouchId = t.identifier;
            isDragging = true;
            onPointerDownPointerX = t.clientX;
            onPointerDownPointerY = t.clientY;
            onPointerDownLon = lon;
            onPointerDownLat = lat;
            break; // Only track one finger for looking
        }
    }
}

function onPointerMove(event) {
    if (!isDragging) return;

    let clientX, clientY;

    // Mouse
    if (event.type === 'mousemove') {
        clientX = event.clientX;
        clientY = event.clientY;
    }
    // Touch
    else if (event.type === 'touchmove') {
        // Find the tracked touch
        let touch = null;
        for (let i = 0; i < event.touches.length; i++) {
            if (event.touches[i].identifier === dragTouchId) {
                touch = event.touches[i];
                break;
            }
        }

        if (!touch) return; // The look finger isn't moving or isn't found

        clientX = touch.clientX;
        clientY = touch.clientY;
    }

    const factor = 0.2;
    lon = (clientX - onPointerDownPointerX) * factor + onPointerDownLon;
    lat = (onPointerDownPointerY - clientY) * factor + onPointerDownLat;
    updateCameraRotation();
}

function onPointerUp(event) {
    // Mouse
    if (event.type === 'mouseup') {
        isDragging = false;
    }
    // Touch
    else if (event.type === 'touchend') {
        for (let i = 0; i < event.changedTouches.length; i++) {
            if (event.changedTouches[i].identifier === dragTouchId) {
                isDragging = false;
                dragTouchId = null;
            }
        }
    }
}

function updateCameraRotation() {
    lat = Math.max(-85, Math.min(85, lat));
    phi = THREE.MathUtils.degToRad(90 - lat);
    theta = THREE.MathUtils.degToRad(lon);
    const targetX = 500 * Math.sin(phi) * Math.cos(theta);
    const targetY = 500 * Math.cos(phi);
    const targetZ = 500 * Math.sin(phi) * Math.sin(theta);
    camera.lookAt(camera.position.x + targetX, camera.position.y + targetY, camera.position.z + targetZ);
}

// Global Teleporters Array
let teleporters = [];
let lastTeleportTime = 0; // Cooldown to prevent spam

function updateMovement() {
    // Prevent physics/falling until map is loaded
    if (!schoolModel) return;

    const currentMoveForward = moveForward + keyMoveForward;
    const currentMoveSide = moveSide + keyMoveSide;
    const isMoving = (currentMoveForward !== 0 || currentMoveSide !== 0) && !isChatting; // Disable movement when chatting

    if (isMoving) {
        // A. Hitung Vektor Gerakan
        const moveVector = new THREE.Vector3();
        const direction = new THREE.Vector3();
        camera.getWorldDirection(direction);
        direction.y = 0;
        direction.normalize();

        const right = new THREE.Vector3();
        right.crossVectors(direction, camera.up);

        moveVector.addScaledVector(direction, currentMoveForward * speed);
        moveVector.addScaledVector(right, currentMoveSide * speed);

        // B. Cek Tabrakan Tembok (Horizontal Collision)
        const startPos = camera.position.clone();
        startPos.y -= 1.0; // Turun ke level dada

        const rayDir = moveVector.clone().normalize();
        const collisionRay = new THREE.Raycaster(startPos, rayDir, 0, 1.0); // Cek 1 meter

        // Array Collider Lengkap
        const colliders = [];
        if (schoolModel) colliders.push(schoolModel);
        if (gameRoom) colliders.push(gameRoom);
        if (oldSchool) colliders.push(oldSchool);
        if (blackBox) colliders.push(blackBox);
        if (room) colliders.push(room);

        const hits = collisionRay.intersectObjects(colliders, true);

        if (hits.length > 0) {
            // Ada tembok! Berhenti total.
            moveVector.set(0, 0, 0);
        }

        // --- LOGIKA BATAS ORGANIK (STAY ON GROUND) ---
        // Hanya jalankan jika kita sedang bergerak
        if (moveVector.lengthSq() > 0) {
            // 1. Prediksi posisi langkah berikutnya
            const nextPos = camera.position.clone().add(moveVector);

            // 2. Siapkan Raycaster dari posisi prediksi (dari atas kepala)
            const rayOriginNext = nextPos.clone();
            rayOriginNext.y += 2.0;
            const groundRay = new THREE.Raycaster(rayOriginNext, new THREE.Vector3(0, -1, 0), 0, 10); // Cek 10 meter ke bawah

            // 3. Objek yang dianggap "Tanah Aman"
            const safeGrounds = [];
            if (schoolModel) safeGrounds.push(schoolModel);
            if (gameRoom) safeGrounds.push(gameRoom);
            if (oldSchool) safeGrounds.push(oldSchool);
            if (typeof ground !== 'undefined' && ground) safeGrounds.push(ground);

            if (safeGrounds.length > 0) {
                const groundHits = groundRay.intersectObjects(safeGrounds, true);

                // 4. JIKA TIDAK ADA TANAH DI DEPAN -> JANGAN JALAN
                if (groundHits.length === 0) {
                    moveVector.set(0, 0, 0);
                    // console.log("Awas jurang! Berhenti.");
                }
            }
        }

        // C. Terapkan Gerakan
        camera.position.add(moveVector);
        bobTimer += speed * 0.8;
    }

    // ===== KOORDINAT DEBUG DISPLAY =====
    let coordDisplay = document.getElementById('coord-display');
    if (!coordDisplay) {
        coordDisplay = document.createElement('div');
        coordDisplay.id = 'coord-display';
        coordDisplay.style.cssText = 'position:fixed;top:10px;left:10px;background:rgba(0,0,0,0.7);color:#0f0;font-family:monospace;font-size:12px;padding:8px 12px;border-radius:5px;z-index:9999;';
        document.body.appendChild(coordDisplay);
    }
    coordDisplay.innerHTML = `X: ${camera.position.x.toFixed(2)}<br>Y: ${camera.position.y.toFixed(2)}<br>Z: ${camera.position.z.toFixed(2)}`;

    // D. Cek Gravitasi (Ground Check)
    let groundHeight = 0;
    const rayOrigin = camera.position.clone();
    rayOrigin.y += 2.0; // Cast from just above head
    raycaster.set(rayOrigin, downVector);

    // Collect objects to collide with (ALL MODELS)
    const objectsToCheck = [];
    if (schoolModel) objectsToCheck.push(schoolModel);
    if (gameRoom) objectsToCheck.push(gameRoom);
    if (oldSchool) objectsToCheck.push(oldSchool);
    if (ground) objectsToCheck.push(ground);
    if (blackBox) objectsToCheck.push(blackBox);
    if (room) objectsToCheck.push(room);

    if (objectsToCheck.length > 0) {
        try {
            const intersects = raycaster.intersectObjects(objectsToCheck, true);
            if (intersects.length > 0) {
                groundHeight = intersects[0].point.y;
            }
        } catch (err) {
            // Ignore errors
        }
    }

    // --- PROXIMITY LOGIC ---
    if (npcModel) {
        const dist = camera.position.distanceTo(npcModel.position);
        const sprite = npcModel.getObjectByName("npcTextSprite");

        if (dist < 5) { // Within 5 meters - AUTO OPEN CHAT
            // Show Sprite
            if (sprite) sprite.visible = true;

            // Auto-open chat if not already chatting AND cooldown passed (3 seconds)
            const cooldownPassed = (Date.now() - chatClosedTime) > 3000;
            if (!isChatting && chatOverlay && cooldownPassed) {
                toggleChat(true);
                updateNpcText("Silakan bertanya!");
            }
        } else {
            // Far away
            if (sprite) sprite.visible = false;

            // Auto-close chat when walking away
            if (isChatting && chatOverlay) {
                toggleChat(false);
                updateNpcText("Halo");
            }
        }
    }

    // --- TELEPORT LOGIC ---
    const now = Date.now();
    if (now - lastTeleportTime > 2000) { // 2 second cooldown
        for (const tp of teleporters) {
            // Check horizontal distance (ignore Y difference for trigger, assuming we are on it)
            const dist = Math.sqrt(
                Math.pow(camera.position.x - tp.position.x, 2) +
                Math.pow(camera.position.z - tp.position.z, 2)
            );

            if (dist < 3.0) { // Increased to 3.0 meter radius
                log("Teleporting to " + tp.userData.destination.x);
                const dest = tp.userData.destination;
                // Teleport!
                camera.position.set(dest.x, dest.y + playerHeight, dest.z);

                // Apply Rotation if defined
                if (typeof tp.userData.facing !== 'undefined') {
                    lon = tp.userData.facing;
                    lat = 0;
                    updateCameraRotation();
                }

                // Trigger Tutorial based on destination
                // Classroom: dest ~(116.80, 0.26, -18.58)
                // Gaming: dest ~(118.72, 0.30, -51.13)
                if (dest.z > -30 && !hasSeenClassroom) {
                    setTimeout(() => showTutorial('CLASSROOM'), 500);
                } else if (dest.z < -40 && !hasSeenGaming) {
                    setTimeout(() => showTutorial('GAMING'), 500);
                }

                lastTeleportTime = now;
                break; // Only one teleport at a time
            } else if (dist < 10.0) {
                // Debug log if close but not triggering
                // console.log(`Distance to teleporter: ${dist.toFixed(2)}`);
            }
        }
    }

    const bobOffset = isMoving ? Math.sin(bobTimer * 15) * 0.05 : 0;
    const targetY = groundHeight + playerHeight + bobOffset;
    camera.position.y += (targetY - camera.position.y) * 0.2;

    // --- Game Interaction Logic ---
    const computerPos = new THREE.Vector3(112.76, 0.30, -56.02); // Updated coords
    const distToComputer = camera.position.distanceTo(computerPos);
    const gamePopup = document.getElementById('game-popup');

    if (distToComputer < 5.0) { // Increased radius to 5 meters
        gamePopup.style.display = 'block';

        // Update Button Text based on Game ID
        const urlParams = new URLSearchParams(window.location.search);
        const gameId = urlParams.get('game_id');

        if (gameId === '1') {
            gamePopup.innerText = "MAIN LAGI (PICO PARK)";
            gamePopup.style.backgroundColor = "rgba(0, 255, 0, 0.8)"; // Green
        } else if (gameId === '2') {
            gamePopup.innerText = "MAIN LAGI (SPACE MISSION)";
            gamePopup.style.backgroundColor = "rgba(0, 100, 255, 0.8)"; // Blue
        } else if (gameId === '3') {
            gamePopup.innerText = "MAIN LAGI (NEON DASH)";
            gamePopup.style.backgroundColor = "rgba(236, 72, 153, 0.9)"; // Neon Pink
            gamePopup.style.boxShadow = "0 0 20px rgba(139, 92, 246, 0.6)"; // Purple glow
        } else if (gameId === '4') {
            gamePopup.innerText = "MAIN LAGI (FLAPPY ENGLISH)";
            gamePopup.style.backgroundColor = "rgba(249, 115, 22, 0.9)"; // Orange
            gamePopup.style.boxShadow = "0 0 20px rgba(234, 179, 8, 0.6)"; // Yellow glow
        } else if (gameId === '5') {
            gamePopup.innerText = "MAIN LAGI (BURGER MASTER)";
            gamePopup.style.backgroundColor = "rgba(217, 119, 6, 0.9)"; // Amber
            gamePopup.style.boxShadow = "0 0 20px rgba(146, 64, 14, 0.6)"; // Brown glow
        } else if (gameId === '6') {
            gamePopup.innerText = "MAIN LAGI (WORD NINJA)";
            gamePopup.style.backgroundColor = "rgba(132, 204, 22, 0.9)"; // Lime
            gamePopup.style.boxShadow = "0 0 20px rgba(54, 83, 20, 0.6)"; // Dark green glow
        } else if (gameId === '7') {
            gamePopup.innerText = "MAIN LAGI (IPS HOOPS)";
            gamePopup.style.backgroundColor = "rgba(234, 88, 12, 0.9)"; // Orange
            gamePopup.style.boxShadow = "0 0 20px rgba(30, 41, 59, 0.6)"; // Slate glow
        } else if (gameId === '8') {
            gamePopup.innerText = "MAIN LAGI (MOTO QUIZ GP)";
            gamePopup.style.backgroundColor = "rgba(239, 68, 68, 0.9)"; // Red
            gamePopup.style.boxShadow = "0 0 20px rgba(250, 204, 21, 0.6)"; // Yellow glow
        } else if (gameId === '9') {
            gamePopup.innerText = "MAIN LAGI (PAC-SISWA)";
            gamePopup.style.backgroundColor = "rgba(251, 191, 36, 0.9)"; // Yellow
            gamePopup.style.boxShadow = "0 0 20px rgba(30, 58, 138, 0.6)"; // Blue glow
        } else {
            gamePopup.innerText = "PILIH GAME (DASHBOARD)";
            gamePopup.style.backgroundColor = "rgba(255, 165, 0, 0.8)"; // Orange
        }
    } else {
        gamePopup.style.display = 'none';
    }
}

// --- FIX NAVIGASI GAME POPUP ---
const oldBtn = document.getElementById('game-popup');
if (oldBtn) {
    // 1. CLONE tombol untuk membuang semua event listener lama (yang mungkin hardcoded ke game 1)
    const newBtn = oldBtn.cloneNode(true);
    oldBtn.parentNode.replaceChild(newBtn, oldBtn);

    // 2. Pasang Listener BARU yang Bersih
    newBtn.addEventListener('click', () => {
        // Ambil ID dari URL saat tombol DIKLIK (Real-time)
        const urlParams = new URLSearchParams(window.location.search);
        const gameId = urlParams.get('game_id');

        console.log("🛠️ DEBUG CLICK: Game ID =", gameId);

        if (gameId === '1') {
            console.log("🚀 Meluncur ke Pico Park...");
            window.location.href = 'game.php?game_id=1';
        } else if (gameId === '2') {
            console.log("🚀 Meluncur ke Space Mission...");
            window.location.href = 'game2.php?game_id=2';
        } else if (gameId === '3') {
            console.log("🚀 Meluncur ke Neon Dash...");
            window.location.href = 'game3.php?game_id=3';
        } else if (gameId === '4') {
            console.log("🚀 Meluncur ke Flappy English...");
            window.location.href = 'game4.php?game_id=4';
        } else if (gameId === '5') {
            console.log("🚀 Meluncur ke Burger Master...");
            window.location.href = 'game5.php?game_id=5';
        } else if (gameId === '6') {
            console.log("🚀 Meluncur ke Word Ninja...");
            window.location.href = 'game6.php?game_id=6';
        } else if (gameId === '7') {
            console.log("🚀 Meluncur ke IPS Hoops...");
            window.location.href = 'game7.php?game_id=7';
        } else if (gameId === '8') {
            console.log("🚀 Meluncur ke Moto Quiz GP...");
            window.location.href = 'game8.php?game_id=8';
        } else if (gameId === '9') {
            console.log("🚀 Meluncur ke Pac-Siswa...");
            window.location.href = 'game9.php?game_id=9';
        } else {
            // Jika tidak ada ID, kembalikan ke Dashboard untuk memilih
            console.log("⚠️ Tidak ada ID, kembali ke Dashboard.");
            window.location.href = 'dashboard.php';
        }
    });
}

function animate() {
    requestAnimationFrame(animate);
    updateMovement();
    renderer.render(scene, camera);
    cssRenderer.render(scene, camera);
}
