/**
 * 3D 地图模块
 * 在首页首屏完成首帧渲染后立即预热加载，避免滚动到地图区域才开始请求资源。
 */

const GEO_DATA_URL = new URL("../100000_full.json", import.meta.url).toString();
const THREE_CDN_BASE = "https://cdn.jsdelivr.net/npm/three@0.180.0";

let mapInitialized = false;

async function initMap3D(geoDataPromise = null, threeJSPromise = null) {
    if (mapInitialized) return;
    mapInitialized = true;

    const container = document.getElementById("map");
    if (!container) return;

    // 使用预加载的模块或动态导入
    const threeModule = threeJSPromise 
        ? (await threeJSPromise) ? (await import(THREE_CDN_BASE + '/build/three.module.js')) : null
        : await import(THREE_CDN_BASE + '/build/three.module.js');

    const THREE = threeModule.default || threeModule;

    const [
        { OrbitControls },
        { CSS2DRenderer, CSS2DObject },
        { Line2 },
        { LineMaterial },
        { LineGeometry }
    ] = threeJSPromise && (await threeJSPromise)
        ? [
            await import(THREE_CDN_BASE + '/examples/jsm/controls/OrbitControls.js'),
            await import(THREE_CDN_BASE + '/examples/jsm/renderers/CSS2DRenderer.js'),
            await import(THREE_CDN_BASE + '/examples/jsm/lines/Line2.js'),
            await import(THREE_CDN_BASE + '/examples/jsm/lines/LineMaterial.js'),
            await import(THREE_CDN_BASE + '/examples/jsm/lines/LineGeometry.js')
        ]
        : await Promise.all([
            import(THREE_CDN_BASE + '/examples/jsm/controls/OrbitControls.js'),
            import(THREE_CDN_BASE + '/examples/jsm/renderers/CSS2DRenderer.js'),
            import(THREE_CDN_BASE + '/examples/jsm/lines/Line2.js'),
            import(THREE_CDN_BASE + '/examples/jsm/lines/LineMaterial.js'),
            import(THREE_CDN_BASE + '/examples/jsm/lines/LineGeometry.js')
        ]);

    const scene = new THREE.Scene();

    const ambientLight = new THREE.AmbientLight(0xd4e7fd, 4);
    scene.add(ambientLight);
    const directionalLight = new THREE.DirectionalLight(0xe8eaeb, 0.2);
    directionalLight.position.set(0, 10, 5);
    const directionalLight2 = directionalLight.clone();
    directionalLight2.position.set(0, 10, -5);
    const directionalLight3 = directionalLight.clone();
    directionalLight3.position.set(5, 10, 0);
    const directionalLight4 = directionalLight.clone();
    directionalLight4.position.set(-5, 10, 0);
    scene.add(directionalLight);
    scene.add(directionalLight2);
    scene.add(directionalLight3);
    scene.add(directionalLight4);

    container.style.position = "relative";

    const camera = new THREE.PerspectiveCamera(
        75,
        container.clientWidth / container.clientHeight,
        0.1,
        1000
    );

    camera.position.set(0, 100, 10);
    camera.lookAt(0, 0, 0);
    camera.up.set(0, 1, 0);

    const labelRenderer = new CSS2DRenderer();
    labelRenderer.domElement.style.position = "absolute";
    labelRenderer.domElement.style.top = "0px";
    labelRenderer.domElement.style.left = "0px";
    labelRenderer.domElement.style.zIndex = "3";
    labelRenderer.domElement.style.pointerEvents = "none";
    labelRenderer.setSize(container.clientWidth, container.clientHeight);
    container.appendChild(labelRenderer.domElement);

    const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
    renderer.setSize(container.clientWidth, container.clientHeight);
    renderer.setPixelRatio(window.devicePixelRatio || 1);
    renderer.domElement.style.position = "relative";
    renderer.domElement.style.zIndex = "2";
    container.appendChild(renderer.domElement);

    const controls = new OrbitControls(camera, renderer.domElement);
    controls.enableRotate = false;
    controls.enableZoom = false;
    controls.enablePan = false;
    controls.update();

    const highlightMeshes = [];
    const pulseRings = [];
    const lineMaterials = [];

    const tooltip = document.createElement("div");
    tooltip.style.cssText = "position:absolute;padding:6px 10px;background:rgba(24,118,242,0.92);color:#fff;font-size:13px;border-radius:6px;pointer-events:none;box-shadow:0 2px 6px rgba(0,0,0,0.25);display:none;";
    tooltip.style.zIndex = "4";
    container.appendChild(tooltip);

    const animate = () => {
        requestAnimationFrame(animate);
        const t = performance.now() * 0.001;
        pulseRings.forEach((mesh) => {
            const { minScale, maxScale, period, phase, baseOpacity } = mesh.userData;
            const progress = (t / period + phase) % 1;
            const eased = progress * progress * (3 - 2 * progress);
            const s = minScale + eased * (maxScale - minScale);
            mesh.scale.setScalar(s);
            const fadeIn = progress < 0.12 ? progress / 0.12 : 1.0;
            const fadeOut = progress > 0.7 ? Math.max(0.0, 1.0 - (progress - 0.7) / 0.3) : 1.0;
            const alpha = baseOpacity * Math.max(0.0, Math.min(1.0, fadeIn * fadeOut));
            if (mesh.material && mesh.material.uniforms && mesh.material.uniforms.opacity) {
                mesh.material.uniforms.opacity.value = alpha;
            }
        });
        controls.update();
        renderer.render(scene, camera);
        labelRenderer.render(scene, camera);
    };
    animate();

    const raycaster = new THREE.Raycaster();
    const mouse = new THREE.Vector2();
    let hoverMesh = null;
    const hoverColor = new THREE.Color().setHSL(210 / 360, 0.7, 0.55);

    const hideTooltip = () => { tooltip.style.display = "none"; };

    const applyHoverState = (mesh) => {
        if (hoverMesh && hoverMesh !== mesh) {
            if (hoverMesh.material && hoverMesh.userData.baseColor) {
                hoverMesh.material.color.copy(hoverMesh.userData.baseColor);
            }
        }
        hoverMesh = mesh;
        if (hoverMesh && hoverMesh.material) {
            hoverMesh.material.color.copy(hoverColor);
        }
    };

    const clearHoverState = () => {
        if (hoverMesh && hoverMesh.material && hoverMesh.userData.baseColor) {
            hoverMesh.material.color.copy(hoverMesh.userData.baseColor);
        }
        hoverMesh = null;
    };

    const handlePointerMove = (event) => {
        const rect = container.getBoundingClientRect();
        mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
        mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
        raycaster.setFromCamera(mouse, camera);
        const intersects = raycaster.intersectObjects(highlightMeshes, false);
        if (intersects.length > 0) {
            applyHoverState(intersects[0].object);
        } else {
            clearHoverState();
            hideTooltip();
        }
    };

    container.addEventListener("mousemove", handlePointerMove);
    container.addEventListener("mouseleave", () => {
        clearHoverState();
        hideTooltip();
    });

    window.addEventListener("resize", () => {
        camera.aspect = container.clientWidth / container.clientHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(container.clientWidth, container.clientHeight);
        labelRenderer.setSize(container.clientWidth, container.clientHeight);
        lineMaterials.forEach((m) => {
            if (m && m.resolution) {
                m.resolution.set(container.clientWidth, container.clientHeight);
                m.needsUpdate = true;
            }
        });
    });

    const offsetXY = d3.geoMercator();
    const highlightProvinces = ["山东省", "海南省", "天津市", "广东省", "浙江省", "江苏省", "北京市", "河北省", "辽宁省", "上海市"];
    const pulseProvinces = ["北京市", "上海市", "广东省"];

    const createPulseRings = (data, depth) => {
        const group = new THREE.Object3D();
        data.features.forEach((feature) => {
            const { centroid, center, name } = feature.properties;
            if (!pulseProvinces.includes(name)) return;
            const point = centroid || center || [0, 0];
            const [x, y] = offsetXY(point);
            const geometry = new THREE.RingGeometry(0.0, 18.0, 160);

            const makeMaterial = (colorCenterHex, colorEdgeHex, opacityVal, alphaInner = 0.45, alphaOuter = 1.0) => {
                return new THREE.ShaderMaterial({
                    transparent: true,
                    depthWrite: false,
                    depthTest: false,
                    side: THREE.DoubleSide,
                    uniforms: {
                        colorCenter: { value: new THREE.Color(colorCenterHex) },
                        colorEdge: { value: new THREE.Color(colorEdgeHex) },
                        opacity: { value: opacityVal },
                        innerRatio: { value: geometry.parameters.innerRadius / geometry.parameters.outerRadius },
                        alphaInner: { value: alphaInner },
                        alphaOuter: { value: alphaOuter },
                    },
                    vertexShader: `
                        varying vec2 vUv;
                        void main() {
                            vUv = uv;
                            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                        }
                    `,
                    fragmentShader: `
                        varying vec2 vUv;
                        uniform vec3 colorCenter;
                        uniform vec3 colorEdge;
                        uniform float opacity;
                        uniform float innerRatio;
                        uniform float alphaInner;
                        uniform float alphaOuter;
                        void main() {
                            vec2 p = vUv - vec2(0.5);
                            float rawR = clamp(length(p) * 2.0, 0.0, 1.0);
                            float r = clamp((rawR - innerRatio) / max(1.0 - innerRatio, 0.0001), 0.0, 1.0);
                            float k = smoothstep(0.0, 1.0, r);
                            vec3 color = mix(colorEdge, colorCenter, 1.0 - k);
                            float alpha = opacity * mix(alphaInner, alphaOuter, k);
                            if (alpha < 0.01) discard;
                            gl_FragColor = vec4(color, alpha);
                        }
                    `,
                });
            };

            const baseRing = new THREE.Mesh(geometry.clone(), makeMaterial(0x31bbf7, 0x31bbf7, 0.1, 1.0, 1.0));
            baseRing.rotation.x = 0;
            baseRing.position.set(x, -y, depth + 0.02);
            baseRing.renderOrder = 10;
            group.add(baseRing);

            const pulseRing = new THREE.Mesh(geometry.clone(), makeMaterial(0x9fdcff, 0x31bbf7, 0.45, 0.35, 1.0));
            pulseRing.rotation.x = 0;
            pulseRing.position.set(x, -y, depth + 0.02);
            pulseRing.renderOrder = 11;
            pulseRing.userData = {
                minScale: 1.0,
                maxScale: 1.3,
                period: 3.0,
                phase: Math.random(),
                baseOpacity: 0.25,
            };
            pulseRings.push(pulseRing);
            group.add(pulseRing);
        });
        return group;
    };

    const createMap = (data, depth) => {
        const map = new THREE.Object3D();
        const center = data.features[0].properties.centroid;
        offsetXY.center(center).translate([0, 0]);
        data.features.forEach((feature) => {
            const unit = new THREE.Object3D();
            const { centroid, center, name } = feature.properties;
            const { coordinates, type } = feature.geometry;
            const point = centroid || center || [0, 0];

            let color;
            if (highlightProvinces.includes(name)) {
                color = 0x2658F7;
            } else {
                color = new THREE.Color().setHSL(233 / 360, 0, 1).getHex();
            }

            coordinates.forEach((coordinate) => {
                if (type === "MultiPolygon") coordinate.forEach((item) => fn(item));
                if (type === "Polygon") fn(coordinate);

                function fn(coordinate) {
                    unit.name = name;
                    unit.centroid = point;
                    const mesh = createMesh(coordinate, color, depth);
                    mesh.userData = { name, centroid: point, baseColor: new THREE.Color(color) };
                    if (highlightProvinces.includes(name)) {
                        highlightMeshes.push(mesh);
                    }
                    const line = createLine(coordinate, depth);
                    unit.add(mesh, ...line);
                }
            });
            map.add(unit);
            setCenter(map);
        });
        return map;
    };

    const createMesh = (data, color, depth) => {
        const shape = new THREE.Shape();
        data.forEach((item, idx) => {
            const [x, y] = offsetXY(item);
            if (idx === 0) shape.moveTo(x, -y);
            else shape.lineTo(x, -y);
        });

        const extrudeSettings = { depth: depth, bevelEnabled: false };
        const materialSettings = {
            color: color,
            emissive: 0x000000,
            roughness: 0.45,
            metalness: 0.8,
            transparent: true,
            side: THREE.DoubleSide,
        };
        const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
        const material = new THREE.MeshStandardMaterial(materialSettings);
        return new THREE.Mesh(geometry, material);
    };

    const createLine = (data, depth) => {
        const positions = [];
        let firstX = null, firstY = null;
        data.forEach((item, idx) => {
            const [x, y] = offsetXY(item);
            positions.push(x, -y, 0);
            if (idx === 0) { firstX = x; firstY = y; }
        });
        if (firstX !== null && firstY !== null) {
            positions.push(firstX, -firstY, 0);
        }

        const lineGeometry = new LineGeometry();
        lineGeometry.setPositions(positions);

        const lineMaterialSettings = {
            color: 0xffffff,
            linewidth: 0.5,
            dashed: false,
            transparent: true,
            opacity: 0.9,
        };
        const uplineMaterial = new LineMaterial(lineMaterialSettings);
        const downlineMaterial = new LineMaterial(lineMaterialSettings);

        uplineMaterial.resolution.set(container.clientWidth, container.clientHeight);
        downlineMaterial.resolution.set(container.clientWidth, container.clientHeight);

        lineMaterials.push(uplineMaterial, downlineMaterial);

        const upLine = new Line2(lineGeometry, uplineMaterial);
        const downLine = new Line2(lineGeometry.clone(), downlineMaterial);
        downLine.position.z = -0.0001;
        upLine.position.z = depth + 0.0001;
        return [upLine, downLine];
    };

    const setCenter = (map) => {
        map.rotation.x = -Math.PI / 2;
        const box = new THREE.Box3().setFromObject(map);
        const center = box.getCenter(new THREE.Vector3());
        const offset = [0, 0];
        map.position.x = map.position.x - center.x - offset[0];
        map.position.z = map.position.z - center.z - offset[1];
    };

    // 加载 GeoJSON 数据（使用预加载或重新加载）
    try {
        const data = geoDataPromise ? await geoDataPromise : await (await fetch(GEO_DATA_URL)).json();
        if (!data) throw new Error("No data available");
        const map = createMap(data, 0.05);
        map.add(createPulseRings(data, 0.05));
        scene.add(map);
        container.classList.add("map-enhanced-ready");
    } catch (err) {
        console.error("Failed to load map data:", err);
    }
}

function warmupMapAfterFirstPaint() {
    const mapContainer = document.getElementById("map");
    if (!mapContainer) return;

    // 预加载 GeoJSON 数据
    const preloadGeoData = async () => {
        try {
            const res = await fetch(GEO_DATA_URL);
            return await res.json();
        } catch (err) {
            console.error("Failed to preload map data:", err);
            return null;
        }
    };

    // 预加载 Three.js 模块
    const preloadThreeJS = async () => {
        try {
            await Promise.all([
                import(THREE_CDN_BASE + '/build/three.module.js'),
                import(THREE_CDN_BASE + '/examples/jsm/controls/OrbitControls.js'),
                import(THREE_CDN_BASE + '/examples/jsm/renderers/CSS2DRenderer.js'),
                import(THREE_CDN_BASE + '/examples/jsm/lines/Line2.js'),
                import(THREE_CDN_BASE + '/examples/jsm/lines/LineMaterial.js'),
                import(THREE_CDN_BASE + '/examples/jsm/lines/LineGeometry.js')
            ]);
            return true;
        } catch (err) {
            console.error("Failed to preload Three.js:", err);
            return false;
        }
    };

    const startMapWarmup = () => {
        const geoDataPromise = preloadGeoData();
        const threeJSPromise = preloadThreeJS();
        initMap3D(geoDataPromise, threeJSPromise);
    };

    if ("requestAnimationFrame" in window) {
        // 等首页首屏至少完成一次绘制，再启动地图资源加载。
        requestAnimationFrame(() => {
            requestAnimationFrame(startMapWarmup);
        });
    } else {
        initMap3D();
    }
}

// 页面可操作后启动地图预热
if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', warmupMapAfterFirstPaint);
} else {
    warmupMapAfterFirstPaint();
}
