// Patch Three.js r128 Sprite raycast camera space vs world space bug
(function() {
    const _worldScale = new THREE.Vector3();
    const _mvPosition = new THREE.Vector3();
    const _vA = new THREE.Vector3();
    const _vB = new THREE.Vector3();
    const _vC = new THREE.Vector3();
    const _uvC = new THREE.Vector2();
    const _intersectPoint = new THREE.Vector3();
    const _modelViewMatrix = new THREE.Matrix4();

    THREE.Sprite.prototype.raycast = function (raycaster, intersects) {
        if (!raycaster.camera) return;

        _worldScale.setFromMatrixScale(this.matrixWorld);
        _modelViewMatrix.multiplyMatrices(raycaster.camera.matrixWorldInverse, this.matrixWorld);
        _mvPosition.setFromMatrixPosition(_modelViewMatrix);

        if (raycaster.camera.isPerspectiveCamera && this.material.sizeAttenuation === false) {
            _worldScale.multiplyScalar(-_mvPosition.z);
        }

        const rotation = this.material.rotation;
        let sin = 0, cos = 1;
        if (rotation !== 0) {
            cos = Math.cos(rotation);
            sin = Math.sin(rotation);
        }

        const center = this.center;

        const transformVertex = (v, mvPosition, center, scale, sin, cos) => {
            v.x -= center.x;
            v.y -= center.y;

            let rx = v.x * scale.x;
            let ry = v.y * scale.y;

            if (sin !== 0) {
                const x = rx;
                const y = ry;
                rx = cos * x - sin * y;
                ry = sin * x + cos * y;
            }

            v.x = rx + mvPosition.x;
            v.y = ry + mvPosition.y;
            v.z = mvPosition.z;

            // Transform from Camera space to World space
            v.applyMatrix4(raycaster.camera.matrixWorld);
        };

        transformVertex(_vA.set(-0.5, -0.5, 0), _mvPosition, center, _worldScale, sin, cos);
        transformVertex(_vB.set(0.5, -0.5, 0), _mvPosition, center, _worldScale, sin, cos);
        transformVertex(_vC.set(0.5, 0.5, 0), _mvPosition, center, _worldScale, sin, cos);

        let intersect = raycaster.ray.intersectTriangle(_vA, _vB, _vC, false, _intersectPoint);

        if (intersect === null) {
            transformVertex(_vB.set(-0.5, 0.5, 0), _mvPosition, center, _worldScale, sin, cos);
            intersect = raycaster.ray.intersectTriangle(_vA, _vC, _vB, false, _intersectPoint);
            if (intersect === null) {
                return;
            }
        }

        const distance = raycaster.ray.origin.distanceTo(_intersectPoint);
        if (distance < raycaster.near || distance > raycaster.far) return;

        intersects.push({
            distance: distance,
            point: _intersectPoint.clone(),
            uv: _uvC.clone(),
            face: null,
            object: this
        });
    };
})();

const palettes = {
    dark: {
        nodeCP: 0x1e1b4b, nodeWorker: 0x1e293b, frameCP: 0xa855f7, frameWorker: 0x3b82f6,
        runColor: 0x10b981, runEmissive: 0x059669,
        pendColor: 0xf59e0b, pendEmissive: 0xd97706,
        succColor: 0x94a3b8, succEmissive: 0x475569,
        failColor: 0xef4444, failEmissive: 0xdc2626,
        unusedColor: 0x64748b, unusedEmissive: 0x334155,
        textColCP: '#f3e8ff', textBgCP: 'rgba(46, 16, 101, 0.92)',
        textColW: '#f8fafc', textBgW: 'rgba(15, 23, 42, 0.85)',
        gridMain: 0x3b82f6, gridBg: 0x1e293b
    },
    neon: {
        nodeCP: 0x101010, nodeWorker: 0x141414, frameCP: 0xff00ff, frameWorker: 0x00ff00,
        runColor: 0x00ff00, runEmissive: 0x00aa00,
        pendColor: 0xffff00, pendEmissive: 0xaaaa00,
        succColor: 0x00ffff, succEmissive: 0x00aaaa,
        failColor: 0xff0000, failEmissive: 0xaa0000,
        unusedColor: 0x004400, unusedEmissive: 0x002200,
        textColCP: '#ff00ff', textBgCP: 'rgba(20, 0, 20, 0.9)',
        textColW: '#00ff00', textBgW: 'rgba(0, 20, 0, 0.9)',
        gridMain: 0x00ff00, gridBg: 0x101010
    },
    light: {
        nodeCP: 0xe2e8f0, nodeWorker: 0xf1f5f9, frameCP: 0x9333ea, frameWorker: 0x2563eb,
        runColor: 0x059669, runEmissive: 0x047857,
        pendColor: 0xd97706, pendEmissive: 0xb45309,
        succColor: 0x64748b, succEmissive: 0x475569,
        failColor: 0xdc2626, failEmissive: 0xb91c1c,
        unusedColor: 0x94a3b8, unusedEmissive: 0x64748b,
        textColCP: '#4c1d95', textBgCP: 'rgba(233, 213, 255, 0.9)',
        textColW: '#0f172a', textBgW: 'rgba(241, 245, 249, 0.9)',
        gridMain: 0x94a3b8, gridBg: 0xe2e8f0
    },
    gray: {
        nodeCP: 0x374151, nodeWorker: 0x4b5563, frameCP: 0x9ca3af, frameWorker: 0xd1d5db,
        runColor: 0x6ee7b7, runEmissive: 0x34d399,
        pendColor: 0xfcd34d, pendEmissive: 0xfbbf24,
        succColor: 0xe5e7eb, succEmissive: 0xd1d5db,
        failColor: 0xfca5a5, failEmissive: 0xf87171,
        unusedColor: 0x9ca3af, unusedEmissive: 0x6b7280,
        textColCP: '#f3f4f6', textBgCP: 'rgba(55, 65, 81, 0.9)',
        textColW: '#f9fafb', textBgW: 'rgba(75, 85, 99, 0.9)',
        gridMain: 0x9ca3af, gridBg: 0x374151
    },
    bw: {
        nodeCP: 0xeeeeee, nodeWorker: 0xf0f0f0, frameCP: 0x000000, frameWorker: 0x555555,
        runColor: 0x666666, runEmissive: 0x444444,
        pendColor: 0x999999, pendEmissive: 0x777777,
        succColor: 0xcccccc, succEmissive: 0xaaaaaa,
        failColor: 0x000000, failEmissive: 0x000000,
        unusedColor: 0xdddddd, unusedEmissive: 0xbbbbbb,
        textColCP: '#000000', textBgCP: 'rgba(255, 255, 255, 0.9)',
        textColW: '#333333', textBgW: 'rgba(255, 255, 255, 0.9)',
        gridMain: 0x000000, gridBg: 0xffffff
    }
};

let currentTheme = 'dark';
let currentMetric = 'cpu';
let clusterData = null;

// Three.js Core Variables
let scene, camera, renderer, controls;
let podMeshes = [];
let nodeMeshes = [];
let raycaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();
let hoveredPod = null;

let maxCpuInCluster = 1;
let maxMemInCluster = 1;
let autoRotateEnabled = false;
let currentSearchTerm = '';
const searchHighlightColor = new THREE.Color(0xffffff);

// Formatters
const formatBytes = (bytes) => {
    if (!bytes || bytes === 0) return '0 B';
    const k = 1024;
    const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};

const formatCpu = (millicores) => {
    if (!millicores || millicores === 0) return '0 m';
    return `${millicores} m`;
};

// UI Controls Setup
document.getElementById('btn-cpu').addEventListener('click', () => {
    document.getElementById('btn-cpu').classList.add('active');
    document.getElementById('btn-mem').classList.remove('active');
    currentMetric = 'cpu';
    updatePodHeights();
});

document.getElementById('btn-mem').addEventListener('click', () => {
    document.getElementById('btn-mem').classList.add('active');
    document.getElementById('btn-cpu').classList.remove('active');
    currentMetric = 'memory';
    updatePodHeights();
});

document.getElementById('btn-reset-cam').addEventListener('click', () => {
    resetCameraView();
});

document.getElementById('btn-auto-rotate').addEventListener('click', () => {
    autoRotateEnabled = !autoRotateEnabled;
    controls.autoRotate = autoRotateEnabled;
    const btn = document.getElementById('btn-auto-rotate');
    const textSpan = document.getElementById('auto-rotate-text');
    if (autoRotateEnabled) {
        btn.classList.add('active');
        textSpan.innerText = 'Rotating...';
    } else {
        btn.classList.remove('active');
        textSpan.innerText = 'Auto Rotate';
    }
});

document.getElementById('btn-speed-down').addEventListener('click', () => {
    if (controls) {
        controls.autoRotateSpeed = Math.max(0.1, controls.autoRotateSpeed - 0.5);
    }
});

document.getElementById('btn-speed-up').addEventListener('click', () => {
    if (controls) {
        controls.autoRotateSpeed += 0.5;
    }
});

document.addEventListener('keydown', (e) => {
    if (e.target.tagName.toLowerCase() === 'input') return;
    if (e.key === 'h' || e.key === 'H') {
        document.body.classList.toggle('ui-hidden');
    }
});

document.getElementById('pod-search').addEventListener('input', (e) => {
    currentSearchTerm = e.target.value.toLowerCase().trim();
    podMeshes.forEach(mesh => {
        if (mesh !== hoveredPod) {
            applySearchStyle(mesh);
        }
    });
});

function applySearchStyle(mesh) {
    mesh.isSearchHighlighted = false;

    if (mesh.userData.type === 'unused') {
        mesh.material.emissive.setHex(mesh.originalEmissive);
        mesh.material.emissiveIntensity = mesh.originalEmissiveIntensity;
        return;
    }

    if (!currentSearchTerm) {
        mesh.material.emissive.setHex(mesh.originalEmissive);
        mesh.material.emissiveIntensity = mesh.originalEmissiveIntensity;
        return;
    }

    const podName = (mesh.userData.name || '').toLowerCase();
    if (podName.includes(currentSearchTerm)) {
        mesh.isSearchHighlighted = true;
    } else {
        mesh.material.emissive.setHex(mesh.originalEmissive);
        mesh.material.emissiveIntensity = 0.05;
    }
}

document.getElementById('theme-select').addEventListener('change', (e) => {
    const theme = e.target.value;
    currentTheme = theme;
    if (theme === 'dark') {
        document.documentElement.removeAttribute('data-theme');
        if (typeof scene !== 'undefined' && scene) {
            scene.background = new THREE.Color(0x090d16);
            scene.fog.color = new THREE.Color(0x090d16);
        }
    } else {
        document.documentElement.setAttribute('data-theme', theme);
        if (typeof scene !== 'undefined' && scene) {
            let bgColor = 0x090d16;
            if (theme === 'neon') bgColor = 0x000000;
            if (theme === 'light') bgColor = 0xf1f5f9;
            if (theme === 'gray') bgColor = 0x4b5563;
            if (theme === 'bw') bgColor = 0xffffff;
            scene.background = new THREE.Color(bgColor);
            scene.fog.color = new THREE.Color(bgColor);
        }
    }
    
    if (typeof scene !== 'undefined' && scene) {
        const grid = scene.getObjectByName('gridHelper');
        if (grid) {
            scene.remove(grid);
            const p = palettes[currentTheme] || palettes.dark;
            const newGrid = new THREE.GridHelper(600, 60, p.gridMain, p.gridBg);
            newGrid.position.y = -0.5;
            newGrid.name = 'gridHelper';
            scene.add(newGrid);
        }
    }
    
    if (clusterData) {
        build3DCluster(clusterData);
    }
});

// Initialize 3D Engine
function init3D() {
    const container = document.getElementById('chart-container');
    const width = container.clientWidth;
    const height = container.clientHeight;

    // Scene
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0x090d16);
    scene.fog = new THREE.FogExp2(0x090d16, 0.0012);

    // Camera
    camera = new THREE.PerspectiveCamera(45, width / height, 1, 2000);
    camera.position.set(0, 140, 210);
    camera.lookAt(0, 0, 0);

    // Renderer
    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setSize(width, height);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    
    // Clear previous elements if any
    container.innerHTML = '';
    container.appendChild(renderer.domElement);

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

    const dirLight = new THREE.DirectionalLight(0xffffff, 0.85);
    dirLight.position.set(120, 220, 120);
    dirLight.castShadow = true;
    dirLight.shadow.mapSize.width = 2048;
    dirLight.shadow.mapSize.height = 2048;
    scene.add(dirLight);

    const bluePointLight = new THREE.PointLight(0x3b82f6, 1.2, 600);
    bluePointLight.position.set(-150, 150, -150);
    scene.add(bluePointLight);

    const purplePointLight = new THREE.PointLight(0x8b5cf6, 1.0, 600);
    purplePointLight.position.set(150, 150, 150);
    scene.add(purplePointLight);

    // Grid Floor
    const p = palettes[currentTheme] || palettes.dark;
    const gridHelper = new THREE.GridHelper(600, 60, p.gridMain, p.gridBg);
    gridHelper.position.y = -0.5;
    gridHelper.name = 'gridHelper';
    scene.add(gridHelper);

    // Orbit Controls
    controls = new THREE.OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    controls.dampingFactor = 0.05;
    controls.maxPolarAngle = Math.PI / 2 - 0.02; // Don't go below ground
    controls.minDistance = 20;
    controls.maxDistance = 800;
    controls.autoRotate = autoRotateEnabled;
    controls.autoRotateSpeed = 1.0;

    // Events
    window.addEventListener('resize', onWindowResize);
    container.addEventListener('mousemove', onPointerMove);
    container.addEventListener('click', onPointerClick);

    // Start animation loop
    animate();
}

function resetCameraView() {
    let startPos = camera.position.clone();
    let targetPos = new THREE.Vector3(0, 140, 210);
    let startLookAt = controls.target.clone();
    let targetLookAt = new THREE.Vector3(0, 0, 0);

    let duration = 600;
    let startTime = performance.now();

    function step(now) {
        let progress = Math.min((now - startTime) / duration, 1);
        // Ease out cubic
        let ease = 1 - Math.pow(1 - progress, 3);

        camera.position.lerpVectors(startPos, targetPos, ease);
        controls.target.lerpVectors(startLookAt, targetLookAt, ease);
        controls.update();

        if (progress < 1) {
            requestAnimationFrame(step);
        }
    }
    requestAnimationFrame(step);
}

function onWindowResize() {
    const container = document.getElementById('chart-container');
    if (!container || !renderer || !camera) return;

    const width = container.clientWidth;
    const height = container.clientHeight;

    camera.aspect = width / height;
    camera.updateProjectionMatrix();

    renderer.setSize(width, height);
}

// Fetch Cluster Data
async function fetchData() {
    try {
        const response = await fetch('/api/cluster-data');
        if (!response.ok) throw new Error('Failed to fetch cluster data');
        clusterData = await response.json();

        document.getElementById('loading').classList.add('hidden');
        
        if (!scene) {
            init3D();
        }
        build3DCluster(clusterData);
    } catch (error) {
        console.error(error);
        document.getElementById('loading').innerHTML = `<p style="color: #ef4444; font-weight: 600;">Error loading cluster data: ${error.message}</p>`;
    }
}

// Helper to create 3D Canvas Text Sprite for Node Labels
function createTextSprite(text, fontSize = 28, textColor = '#f8fafc', bgColor = 'rgba(15, 23, 42, 0.85)', isBold = false, isControlPlane = false) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    
    // Allocate higher resolution canvas for crisp text
    canvas.width = isControlPlane ? 640 : 512;
    canvas.height = isControlPlane ? 160 : 128;

    const pad = 16;
    const w = canvas.width - pad * 2;
    const h = canvas.height - pad * 2;

    // Pill background
    ctx.fillStyle = bgColor;
    ctx.roundRect(pad, pad, w, h, isControlPlane ? 32 : 24);
    ctx.fill();

    // Border
    ctx.strokeStyle = isControlPlane ? 'rgba(168, 85, 247, 0.95)' : 'rgba(59, 130, 246, 0.6)';
    ctx.lineWidth = isControlPlane ? 8 : 6;
    ctx.stroke();

    // Text - use 900 weight for control plane / master, 700 for regular
    const fontWeight = isControlPlane ? '900' : (isBold ? '800' : '700');
    ctx.font = `${fontWeight} ${fontSize}px Inter, sans-serif`;
    ctx.fillStyle = textColor;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(text, canvas.width / 2, canvas.height / 2);

    const texture = new THREE.CanvasTexture(canvas);
    const spriteMaterial = new THREE.SpriteMaterial({ map: texture, transparent: true });
    const sprite = new THREE.Sprite(spriteMaterial);
    
    // Differentiate scale: control plane sprite is larger
    if (isControlPlane) {
        sprite.scale.set(58, 14.5, 1);
    } else {
        sprite.scale.set(45, 11.25, 1);
    }

    // Add invisible Mesh plane for 100% reliable raycasting
    const hitGeo = new THREE.PlaneGeometry(1, 1);
    const hitMat = new THREE.MeshBasicMaterial({ visible: false, depthWrite: false });
    const hitMesh = new THREE.Mesh(hitGeo, hitMat);
    hitMesh.name = 'labelHitMesh';
    sprite.add(hitMesh);

    return sprite;
}

// Build 3D Topology from Data
function build3DCluster(data) {
    // Clear existing node & pod meshes
    podMeshes.forEach(mesh => {
        scene.remove(mesh);
        mesh.geometry.dispose();
        if (mesh.material) mesh.material.dispose();
    });
    podMeshes = [];

    nodeMeshes.forEach(obj => {
        scene.remove(obj);
        if (obj.geometry) obj.geometry.dispose();
        if (obj.material) obj.material.dispose();
    });
    nodeMeshes = [];

    if (!data || !data.children || data.children.length === 0) return;

    // 1. Compute cluster max metrics for scaling height
    maxCpuInCluster = 1;
    maxMemInCluster = 1;

    data.children.forEach(node => {
        if (node.children) {
            node.children.forEach(pod => {
                if (pod.type === 'pod') {
                    if (pod.cpu_raw > maxCpuInCluster) maxCpuInCluster = pod.cpu_raw;
                    if (pod.memory_raw > maxMemInCluster) maxMemInCluster = pod.memory_raw;
                }
            });
        }
    });

    // 2. Layout on X-Z floor using D3 Treemap
    const BOUNDS_SIZE = 220; // 3D world dimension for ground layout

    const root = d3.hierarchy(data)
        .sum(d => {
            if (d.type === 'node') return 0;
            // Ensure minimum footprint for every pod block
            return Math.max(d.cpu_raw || 0, d.memory_raw ? d.memory_raw / 1000000 : 0, 15);
        })
        .sort((a, b) => b.value - a.value);

    d3.treemap()
        .size([BOUNDS_SIZE, BOUNDS_SIZE])
        .paddingTop(36)
        .paddingRight(10)
        .paddingInner(6)
        .paddingBottom(10)
        .paddingLeft(10)(root);

    // 3. Render Nodes and Pods
    const nodes = root.descendants().filter(d => d.depth === 1);
    const pods = root.leaves();

    // Render Node Platforms
    nodes.forEach(n => {
        const nw = Math.max(10, n.x1 - n.x0);
        const nh = Math.max(10, n.y1 - n.y0);
        const centerX = (n.x0 + n.x1) / 2 - BOUNDS_SIZE / 2;
        const centerZ = (n.y0 + n.y1) / 2 - BOUNDS_SIZE / 2;

        const isControlPlane = n.data.isControlPlane || n.data.is_control_plane ||
            (n.data.roles && (n.data.roles.includes('control-plane') || n.data.roles.includes('master'))) ||
            n.data.name.includes('control-plane') || n.data.name.includes('master');

        const p = palettes[currentTheme] || palettes.dark;
        const platformGeo = new THREE.BoxGeometry(nw, 1.5, nh);
        const platformMat = new THREE.MeshStandardMaterial({
            color: isControlPlane ? p.nodeCP : p.nodeWorker,
            roughness: 0.5,
            metalness: 0.3,
            transparent: true,
            opacity: 0.95
        });
        const platformMesh = new THREE.Mesh(platformGeo, platformMat);
        platformMesh.position.set(centerX, 0.75, centerZ);
        platformMesh.receiveShadow = true;

        const nodeUserData = {
            type: 'node',
            name: n.data.name,
            isControlPlane: isControlPlane,
            roles: n.data.roles || (isControlPlane ? ['control-plane'] : ['worker']),
            internalIp: n.data.internalIp || n.data.internal_ip || 'N/A',
            osImage: n.data.osImage || n.data.os_image || 'N/A',
            nonTerminatedPods: n.data.nonTerminatedPods ?? n.data.non_terminated_pods ?? n.data.podCount ?? n.data.pod_count ?? (n.data.children ? n.data.children.filter(c => c.type === 'pod' && c.status !== 'Succeeded' && c.status !== 'Failed').length : 0)
        };
        platformMesh.userData = nodeUserData;

        // Platform Border Frame
        const edges = new THREE.EdgesGeometry(platformGeo);
        const frameColor = isControlPlane ? p.frameCP : p.frameWorker;
        const lineMat = new THREE.LineBasicMaterial({ color: frameColor, linewidth: isControlPlane ? 3 : 2 });
        const wireframe = new THREE.LineSegments(edges, lineMat);
        platformMesh.add(wireframe);

        scene.add(platformMesh);
        nodeMeshes.push(platformMesh);

        // Node Label Sprite (Larger, bold font for control-plane/master nodes)
        const fontSize = isControlPlane ? 34 : 24;
        const textColor = isControlPlane ? p.textColCP : p.textColW;
        const bgColor = isControlPlane ? p.textBgCP : p.textBgW;

        const sprite = createTextSprite(n.data.name, fontSize, textColor, bgColor, true, isControlPlane);
        sprite.position.set(centerX, isControlPlane ? 16 : 14, centerZ - nh / 2 - 2);
        sprite.userData = nodeUserData;
        const labelHit = sprite.getObjectByName('labelHitMesh');
        if (labelHit) {
            labelHit.userData = nodeUserData;
        }
        scene.add(sprite);
        nodeMeshes.push(sprite);
    });

    // Render Pod Towers
    pods.forEach(p => {
        const pw = Math.max(2, (p.x1 - p.x0) * 0.88);
        const pd = Math.max(2, (p.y1 - p.y0) * 0.88);
        const centerX = (p.x0 + p.x1) / 2 - BOUNDS_SIZE / 2;
        const centerZ = (p.y0 + p.y1) / 2 - BOUNDS_SIZE / 2;

        // Calculate height
        const targetHeight = getTargetHeight(p.data);

        // Box geometry: 1 unit height base, scaled in animation loop
        const podGeo = new THREE.BoxGeometry(pw, 1, pd);

        // Color & Material based on Pod Status
        const pTheme = palettes[currentTheme] || palettes.dark;
        let color = pTheme.runColor;
        let emissive = pTheme.runEmissive;
        let opacity = 0.9;
        let transparent = false;

        if (p.data.type === 'unused') {
            color = pTheme.unusedColor;
            emissive = pTheme.unusedEmissive;
            opacity = 0.25;
            transparent = true;
        } else if (p.data.status === 'Pending') {
            color = pTheme.pendColor;
            emissive = pTheme.pendEmissive;
        } else if (p.data.status === 'Succeeded') {
            color = pTheme.succColor;
            emissive = pTheme.succEmissive;
        } else if (p.data.status !== 'Running') {
            color = pTheme.failColor;
            emissive = pTheme.failEmissive;
        }

        const podMat = new THREE.MeshStandardMaterial({
            color: color,
            emissive: emissive,
            emissiveIntensity: p.data.type === 'unused' ? 0.05 : 0.25,
            roughness: 0.3,
            metalness: 0.4,
            transparent: transparent,
            opacity: opacity
        });

        const podMesh = new THREE.Mesh(podGeo, podMat);
        podMesh.castShadow = true;
        podMesh.receiveShadow = true;

        // Initial positions & properties
        podMesh.position.set(centerX, 1.5 + 0.1 / 2, centerZ);
        podMesh.scale.set(1, 0.1, 1);

        podMesh.userData = {
            ...p.data,
            nodeName: p.parent ? p.parent.data.name : 'Unknown Node'
        };
        podMesh.targetHeight = targetHeight;
        podMesh.currentHeight = 0.1;
        podMesh.baseWidth = pw;
        podMesh.baseDepth = pd;
        podMesh.baseCenterX = centerX;
        podMesh.baseCenterZ = centerZ;
        podMesh.originalColor = color;
        podMesh.originalEmissive = emissive;
        podMesh.originalEmissiveColor = new THREE.Color(emissive);
        podMesh.originalEmissiveIntensity = p.data.type === 'unused' ? 0.05 : 0.25;

        applySearchStyle(podMesh);

        scene.add(podMesh);
        podMeshes.push(podMesh);
    });
}

// Calculate target 3D tower height based on current metric
function getTargetHeight(data) {
    if (data.type === 'unused') return 0.5; // low plate for available capacity

    const MAX_HEIGHT = 50; // Maximum tower height units
    const MIN_HEIGHT = 1.5; // Base minimum height for visibility

    if (currentMetric === 'cpu') {
        const val = data.cpu_raw || 0;
        return MIN_HEIGHT + (val / (maxCpuInCluster || 1)) * MAX_HEIGHT;
    } else {
        const val = data.memory_raw || 0;
        return MIN_HEIGHT + (val / (maxMemInCluster || 1)) * MAX_HEIGHT;
    }
}

// Update heights smoothly when toggling metric
function updatePodHeights() {
    podMeshes.forEach(mesh => {
        mesh.targetHeight = getTargetHeight(mesh.userData);
    });
}

// Main Render & Animation Loop
function animate() {
    requestAnimationFrame(animate);

    const time = performance.now();
    const pulseFactor = (Math.sin(time * 0.005) + 1) / 2;

    // Smoothly interpolate pod tower heights (lerp)
    podMeshes.forEach(mesh => {
        if (Math.abs(mesh.currentHeight - mesh.targetHeight) > 0.01) {
            mesh.currentHeight += (mesh.targetHeight - mesh.currentHeight) * 0.1;
            mesh.scale.y = mesh.currentHeight;
            // Keep tower base resting on the node platform at y = 1.5
            mesh.position.y = 1.5 + mesh.currentHeight / 2;
        }

        if (mesh.isSearchHighlighted && mesh !== hoveredPod) {
            if (mesh.originalEmissiveColor) {
                mesh.material.emissive.copy(mesh.originalEmissiveColor).lerp(searchHighlightColor, pulseFactor);
            }
            mesh.material.emissiveIntensity = mesh.originalEmissiveIntensity + (0.8 - mesh.originalEmissiveIntensity) * pulseFactor;
        }
    });

    controls.update();
    renderer.render(scene, camera);
}

// Mouse Pointer Raycasting & Hover Tooltip
function onPointerMove(event) {
    const container = document.getElementById('chart-container');
    const rect = container.getBoundingClientRect();

    mouse.x = ((event.clientX - rect.left) / container.clientWidth) * 2 - 1;
    mouse.y = -((event.clientY - rect.top) / container.clientHeight) * 2 + 1;

    raycaster.setFromCamera(mouse, camera);
    const intersects = raycaster.intersectObjects([...podMeshes, ...nodeMeshes], true);

    const tooltip = document.getElementById('tooltip');

    if (intersects.length > 0) {
        const hitMesh = intersects[0].object;
        const data = (hitMesh.userData && hitMesh.userData.type) ? hitMesh.userData : (hitMesh.parent && hitMesh.parent.userData ? hitMesh.parent.userData : {});

        if (data && data.type) {
            container.style.cursor = 'pointer';

            if (hoveredPod !== hitMesh) {
                // Reset previous hovered pod
                if (hoveredPod && hoveredPod.userData && hoveredPod.userData.type !== 'node' && hoveredPod.material && hoveredPod.material.emissive) {
                    applySearchStyle(hoveredPod);
                }

                hoveredPod = hitMesh;
                // Highlight hovered pod tower
                if (hoveredPod.userData && hoveredPod.userData.type !== 'node' && hoveredPod.material && hoveredPod.material.emissive) {
                    hoveredPod.material.emissive.setHex(0x60a5fa);
                    hoveredPod.material.emissiveIntensity = 0.6;
                }
            }

            // Display Tooltip
            tooltip.classList.remove('hidden');

            // Boundary handling
            const tWidth = 260;
            let left = event.pageX + 15;
            let top = event.pageY + 15;

            if (left + tWidth > window.innerWidth) {
                left = event.pageX - tWidth - 15;
            }

            tooltip.style.left = left + 'px';
            tooltip.style.top = top + 'px';

            if (data.type === 'node') {
                const isCP = data.isControlPlane || (data.roles && (data.roles.includes('control-plane') || data.roles.includes('master')));
                const badgeLabel = data.roles && data.roles.length ? data.roles.join(', ') : (isCP ? 'control-plane' : 'Node');
                const podCount = data.nonTerminatedPods ?? data.non_terminated_pods ?? data.podCount ?? data.pod_count ?? 0;
                tooltip.innerHTML = `
                    <div class="tooltip-title">
                        <span>${data.name}</span>
                        <span style="font-size: 0.7rem; background: ${isCP ? 'rgba(168, 85, 247, 0.35)' : 'rgba(59, 130, 246, 0.3)'}; color: ${isCP ? '#e9d5ff' : '#60a5fa'}; padding: 2px 6px; border-radius: 4px; font-weight: 700;">${badgeLabel}</span>
                    </div>
                    <div class="tooltip-row">
                        <span class="tooltip-label">Internal IP:</span>
                        <span class="tooltip-value">${data.internalIp || 'N/A'}</span>
                    </div>
                    <div class="tooltip-row">
                        <span class="tooltip-label">OS Image:</span>
                        <span class="tooltip-value">${data.osImage || 'N/A'}</span>
                    </div>
                    <div class="tooltip-row">
                        <span class="tooltip-label">Non-terminated Pods:</span>
                        <span class="tooltip-value">${podCount}</span>
                    </div>
                `;
            } else if (data.type === 'unused') {
                const valCpu = formatCpu(data.cpu_raw);
                const valMem = formatBytes(data.memory_raw);
                tooltip.innerHTML = `
                    <div class="tooltip-title">
                        <span>Available Capacity</span>
                        <span class="legend-dot unused"></span>
                    </div>
                    <div class="tooltip-row">
                        <span class="tooltip-label">Node:</span>
                        <span class="tooltip-value">${data.nodeName}</span>
                    </div>
                    <div class="tooltip-row">
                        <span class="tooltip-label">Unused CPU:</span>
                        <span class="tooltip-value">${valCpu}</span>
                    </div>
                    <div class="tooltip-row">
                        <span class="tooltip-label">Unused Memory:</span>
                        <span class="tooltip-value">${valMem}</span>
                    </div>
                `;
            } else {
                const valCpu = formatCpu(data.cpu_raw);
                const valMem = formatBytes(data.memory_raw);
                const statusColor = data.status === 'Running' ? '#10b981' : (data.status === 'Pending' ? '#f59e0b' : (data.status === 'Succeeded' ? '#94a3b8' : '#ef4444'));
                tooltip.innerHTML = `
                    <div class="tooltip-title">
                        <span>${data.name}</span>
                    </div>
                    <div class="tooltip-row">
                        <span class="tooltip-label">Namespace:</span>
                        <span class="tooltip-value">${data.namespace}</span>
                    </div>
                    <div class="tooltip-row">
                        <span class="tooltip-label">Node:</span>
                        <span class="tooltip-value">${data.nodeName}</span>
                    </div>
                    <div class="tooltip-row">
                        <span class="tooltip-label">Status:</span>
                        <span class="tooltip-value" style="color: ${statusColor}; font-weight: 700;">${data.status}</span>
                    </div>
                    <div class="tooltip-row">
                        <span class="tooltip-label">CPU Usage:</span>
                        <span class="tooltip-value ${currentMetric === 'cpu' ? 'active-metric' : ''}">${valCpu}</span>
                    </div>
                    <div class="tooltip-row">
                        <span class="tooltip-label">Memory Usage:</span>
                        <span class="tooltip-value ${currentMetric === 'memory' ? 'active-metric' : ''}">${valMem}</span>
                    </div>
                `;
            }
            return;
        }
    }

    container.style.cursor = 'grab';
    if (hoveredPod) {
        if (hoveredPod.userData && hoveredPod.userData.type !== 'node' && hoveredPod.material && hoveredPod.material.emissive) {
            applySearchStyle(hoveredPod);
        }
        hoveredPod = null;
    }
    tooltip.classList.add('hidden');
}

// Click to focus camera on selected Pod
function onPointerClick(event) {
    if (!hoveredPod || hoveredPod.userData.type === 'node') return;

    let targetPos = hoveredPod.position.clone();

    let startLookAt = controls.target.clone();
    let duration = 500;
    let startTime = performance.now();

    function step(now) {
        let progress = Math.min((now - startTime) / duration, 1);
        let ease = 1 - Math.pow(1 - progress, 3);

        controls.target.lerpVectors(startLookAt, targetPos, ease);
        controls.update();

        if (progress < 1) {
            requestAnimationFrame(step);
        }
    }
    requestAnimationFrame(step);
}

// Initial fetch and auto-polling every 30s
fetchData();
setInterval(fetchData, 30000);

