// Named imports instead of the full `icons` map — pulls in only the ~7
// icons the viewer markup actually uses instead of lucide's entire set.
import { Box, createIcons, Maximize, Image as ImageIcon, RotateCcw, X, ZoomIn, ZoomOut } from 'lucide';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { USDZExporter } from 'three/examples/jsm/exporters/USDZExporter.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { HDRLoader } from 'three/examples/jsm/loaders/HDRLoader.js';

/**
 * Lean bundle for Viewer Only Mode ([dprcu-viewer], the "3D Viewer Only"
 * block, or a product's own Viewer Only Mode setting).
 *
 * Mirrors customizer.js's scene/renderer/AR setup exactly, but drops
 * everything that only exists to support customization: material swapping
 * (texture cache, IndexedDB, config application), undo/redo, cart sync,
 * step thumbnails, hotspots, the share modal, PDF export, and analytics.
 * There is no config to apply here — the model displays with whatever
 * materials are baked into the GLB.
 */

// --- Configuration & Globals ---
const modelUrl = customizerData.modelUrl;
const appSettings = customizerData.settings;

const state = {
    model: null,
};

// --- Three.js Globals ---
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 1000);
let canvas;
let renderer;
let controls;
let groundPlane;
let animReq; // Animation frame request ID

function initRenderer() {
    renderer = new THREE.WebGLRenderer({
        canvas,
        antialias: true,
        alpha: true,
        preserveDrawingBuffer: true,
        powerPreference: 'high-performance',
    });

    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFShadowMap;
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 0.8;
    renderer.xr.enabled = true;
    renderer.xr.setReferenceSpaceType('local');
    renderer.outputColorSpace = THREE.SRGBColorSpace;
}

// --- Initialization ---
document.addEventListener('DOMContentLoaded', () => {
    canvas = document.querySelector('#product-canvas');
    if (!canvas) return;

    // Converts the static <i data-lucide="..."> placeholders in the view
    // controls (zoom, reset, fullscreen, background, AR) into SVG icons.
    try {
        createIcons({ icons: { Box, Maximize, Image: ImageIcon, RotateCcw, X, ZoomIn, ZoomOut } });
    } catch { }

    initRenderer();
    initScene();
    initUI();
    loadModel();
    animate();
});

function updateSceneSize() {
    const container = document.getElementById('canvas-container');

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

    const canvasEl = renderer.domElement;
    const pixelRatio = Math.min(window.devicePixelRatio, 2); // Cap at 2x for performance

    const needResize = canvasEl.width !== Math.round(width * pixelRatio) ||
        canvasEl.height !== Math.round(height * pixelRatio);

    if (needResize) {
        camera.aspect = width / height;
        camera.updateProjectionMatrix();

        // IMPORTANT: passing 'false' as the 3rd arg prevents Three.js
        // from changing the canvas CSS style, allowing CSS 100% to work.
        renderer.setPixelRatio(pixelRatio);
        renderer.setSize(width, height, false);

        requestRender();
    }
}

/**
 * Auto-frame the model in view by calculating optimal camera position.
 * Uses bounding box to ensure the entire model is visible with good framing.
 * Distance is multiplied by a user-configurable factor.
 */
function autoFrameModel(model) {
    if (!model) return;

    const box = new THREE.Box3().setFromObject(model);
    const size = new THREE.Vector3();
    box.getSize(size);

    const center = new THREE.Vector3();
    box.getCenter(center);

    model.position.x += (model.position.x - center.x);
    model.position.y += (model.position.y - center.y);
    model.position.z += (model.position.z - center.z);

    const savedPos = customizerData.cameraPosition;
    if (savedPos?.position) {
        camera.position.set(
            savedPos.position.x,
            savedPos.position.y,
            savedPos.position.z
        );
        if (savedPos.target) {
            controls.target.set(
                savedPos.target.x,
                savedPos.target.y,
                savedPos.target.z
            );
        } else {
            controls.target.set(0, 0, 0);
        }
        camera.lookAt(controls.target);
        controls.update();
        return;
    }

    const maxDim = Math.max(size.x, size.y, size.z);
    const fov = camera.fov * (Math.PI / 180);
    let cameraDist = Math.abs(maxDim / 2 / Math.tan(fov / 2));

    const distMultiplier = appSettings.cameraDistanceMultiplier || 1.2;
    cameraDist *= distMultiplier;

    camera.position.set(cameraDist, cameraDist * 0.6, cameraDist);
    camera.lookAt(0, 0, 0);

    controls.target.set(0, 0, 0);
    controls.update();
}

function initScene() {
    if (appSettings.hdrFile) {
        new HDRLoader().load(appSettings.hdrFile, (hdrTexture) => {
            hdrTexture.mapping = THREE.EquirectangularReflectionMapping;
            scene.environment = hdrTexture;
        }, undefined, (err) => {
            console.error('Failed to load HDR:', err);
        });
    }

    // --- Camera Setup ---
    camera.position.set(0, 0, 5); // Temporary default until model loads

    // --- Controls ---
    controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = false;
    controls.target.set(0, 0, 0);

    const zoomLimits = appSettings.zoomLimits || [1, 10];
    controls.minDistance = zoomLimits[0];
    controls.maxDistance = zoomLimits[1];

    controls.addEventListener('change', requestRender);

    controls.addEventListener('start', () => {
        canvas.classList.add('is-dragging');
        requestRender();
    });

    controls.addEventListener('end', () => {
        canvas.classList.remove('is-dragging');
        requestRender();
    });

    // --- Lights ---
    // When no HDR is present the environment provides no illumination, so use
    // stronger lights to keep the scene readable.
    const hasHdr = !!appSettings.hdrFile;
    const ambient = new THREE.AmbientLight(0xffffff, hasHdr ? 0.1 : 0.8);
    const hemi = new THREE.HemisphereLight(0xffffff, 0x444444, hasHdr ? 0.4 : 1.2);
    hemi.position.set(0, 20, 0);

    const keyLight = new THREE.DirectionalLight(0xfff7ea, hasHdr ? 0.3 : 2.0);
    keyLight.position.set(-6, 10, -6);
    keyLight.target.position.set(0, 0, 0);
    keyLight.castShadow = false;
    scene.add(keyLight.target);

    const fillLight = new THREE.DirectionalLight(0xffffff, hasHdr ? 0.3 : 1.2);
    fillLight.position.set(4, 3, 2);
    fillLight.target.position.set(0, 0, 0);
    fillLight.castShadow = true;
    fillLight.shadow.mapSize.set(4096, 4096);
    fillLight.shadow.bias = -0.0005;
    fillLight.shadow.normalBias = 0.02;
    fillLight.shadow.camera.near = 0.1;
    fillLight.shadow.camera.far = 30;
    fillLight.shadow.camera.left = -8;
    fillLight.shadow.camera.right = 8;
    fillLight.shadow.camera.top = 8;
    fillLight.shadow.camera.bottom = -8;
    scene.add(fillLight.target);

    scene.add(ambient, hemi, keyLight, fillLight);

    // --- Background ---
    const bgs = appSettings.bgColors || ['#f0f0f0'];
    renderer.setClearColor(new THREE.Color(bgs[0]), 1);

    // --- Resize Handler ---
    window.addEventListener('resize', updateSceneSize);
    updateSceneSize();
}

function loadModel() {
    toggleLoader(true, customizerData.i18n.loadingModel);
    const loader = new GLTFLoader();

    loader.load(modelUrl, (gltf) => {
        if (state.model) scene.remove(state.model);
        state.model = gltf.scene;
        scene.add(state.model);

        autoFrameModel(state.model);

        if (!groundPlane) {
            const groundGeom = new THREE.PlaneGeometry(50, 50);
            const groundMat = new THREE.ShadowMaterial({ opacity: 0.5 });
            groundPlane = new THREE.Mesh(groundGeom, groundMat);
            groundPlane.rotation.x = -Math.PI / 2;
            groundPlane.receiveShadow = true;
            scene.add(groundPlane);
        }

        const box = new THREE.Box3().setFromObject(state.model);
        groundPlane.position.y = box.min.y;

        state.model.traverse((child) => {
            if (!child.isMesh) return;

            child.castShadow = true;
            child.receiveShadow = true;

            const mat = Array.isArray(child.material) ? child.material[0].clone() : child.material.clone();

            if (mat.name.toLowerCase().includes('glass')) {
                child.material = new THREE.MeshPhysicalMaterial({
                    name: mat.name,
                    color: mat.color,
                    map: mat.map,
                    transmission: 1.0,
                    opacity: 1.0,
                    transparent: true,
                    roughness: 0.05,
                    metalness: 0.0,
                    ior: 1.5,
                    thickness: 0.001,
                    attenuationColor: new THREE.Color(0xffffff),
                    attenuationDistance: 0.25,
                    side: THREE.DoubleSide,
                    envMapIntensity: 1.0,
                });
            } else {
                child.material = mat;
            }
        });

        requestRender();
        toggleLoader(false);
    }, undefined, (err) => {
        console.error('Error loading model:', err);
        toggleLoader(false);
    });
}

function initUI() {
    const bind = (id, fn) => { const el = document.getElementById(id); if (el) el.onclick = fn; };

    const adjustZoom = (factor) => {
        const dir = new THREE.Vector3().copy(camera.position).sub(controls.target);
        const dist = dir.length();
        const newDist = THREE.MathUtils.clamp(dist * factor, controls.minDistance, controls.maxDistance);
        dir.normalize().multiplyScalar(newDist);
        camera.position.copy(controls.target).add(dir);
        controls.update();
        requestRender();
    };

    // "Reset View" here re-frames the camera — there's no configuration to
    // reset in viewer-only mode (see customizer.js's doReset(), which clears
    // saved config and reloads the page; that has nothing to act on here).
    const resetView = () => {
        autoFrameModel(state.model);
        requestRender();
    };

    bind('btn-zoom-in', () => adjustZoom(1 / 1.1));
    bind('btn-zoom-out', () => adjustZoom(1.1));
    bind('btn-reset', resetView);
    bind('btn-fullscreen', () => {
        const el = document.getElementById('canvas-container');
        document.fullscreenElement ? document.exitFullscreen() : el.requestFullscreen().catch(() => { });
    });
    bind('btn-bg', toggleBackground);
    bind('btn-view-ar', startAR);

    setupXRDiagnostics();

    document.addEventListener('keydown', (e) => {
        const isInputFocused = document.activeElement && (
            document.activeElement.tagName === 'INPUT' ||
            document.activeElement.tagName === 'TEXTAREA' ||
            document.activeElement.contentEditable === 'true'
        );
        if (isInputFocused) return;

        if (e.key === '+' || e.key === '=') {
            e.preventDefault();
            adjustZoom(1 / 1.1);
        }
        if (e.key === '-' || e.key === '_') {
            e.preventDefault();
            adjustZoom(1.1);
        }
        if (e.key === 'r' || e.key === 'R') {
            resetView();
        }
        if (e.key === 'f' || e.key === 'F') {
            e.preventDefault();
            const container = document.getElementById('canvas-container');
            document.fullscreenElement ? document.exitFullscreen() : container.requestFullscreen().catch(() => { });
        }
        if (e.key === 'b' || e.key === 'B') {
            e.preventDefault();
            toggleBackground();
        }
    });
}

function toggleBackground() {
    const colors = appSettings.bgColors?.length > 0
        ? appSettings.bgColors
        : ['#f0f0f0', '#ffffff', '#222222'];

    const currentHex = `#${renderer.getClearColor(new THREE.Color()).getHexString()}`;

    let idx = colors.findIndex(c => c.toLowerCase() === currentHex.toLowerCase());
    if (idx < 0) idx = 0;
    idx = (idx + 1) % colors.length;

    const newHex = colors[idx];
    renderer.setClearColor(new THREE.Color(newHex), 1);

    const isDark = new THREE.Color(newHex).getHSL({}).l < 0.5;
    if (groundPlane) groundPlane.material.opacity = isDark ? 0.2 : 0.5;

    requestRender();
}

function toggleLoader(show, text = 'Loading...') {
    const loader = document.getElementById('customizer-loader');
    const label = document.getElementById('loader-text');
    if (!loader) return;

    if (show) {
        if (label) label.innerText = text;
        loader.classList.remove('hidden');
    } else {
        loader.classList.add('hidden');
    }
}

// ========================================================
// AR — mirrors customizer.js exactly (WebXR + iOS Quick Look)
// ========================================================

function setupXRDiagnostics() {
    renderer.getContext().canvas.addEventListener('webglcontextlost', (e) => {
        console.error('WebGL context lost during XR:', e);
        console.error('This indicates GPU memory pressure or device limits reached.');
        if (inXR) {
            alert('AR session ended due to GPU context loss. Try closing other apps.');
        }
    });

    window.addEventListener('error', (e) => {
        if (inXR) {
            console.error('Error during XR session:', e.error, e.message);
            console.error('Stack:', e.error?.stack);
        }
    });

    window.addEventListener('unhandledrejection', (e) => {
        if (inXR) {
            console.error('Unhandled rejection during XR:', e.reason);
        }
    });
}

async function startAR() {
    if (!state.model) return;
    state.model.updateMatrixWorld(true);

    const platform = detectARPlatform();

    try {
        if (platform === 'ios') await launchQuickLook();
        else await launchWebXR();
    } catch (e) {
        console.error('AR Error:', e);
        alert(`AR failed: ${e.message}`);
    }
}

async function launchWebXR() {
    if (!navigator.xr) throw new Error('WebXR unavailable');
    const ok = await navigator.xr.isSessionSupported('immersive-ar');
    if (!ok) throw new Error('Device does not support AR');

    const overlay = document.getElementById('dprcu-ar-overlay');
    overlay.hidden = false;
    setOverlayState('searching');

    const session = await navigator.xr.requestSession('immersive-ar', {
        requiredFeatures: ['hit-test'],
        optionalFeatures: ['dom-overlay', 'light-estimation', 'local-floor'],
        domOverlay: { root: overlay },
    });

    wireOverlayButtons(session);
    setupReticleAndPlacement(session);
    await renderer.xr.setSession(session);
}

function setOverlayState(overlayState) {
    const overlay = document.getElementById('dprcu-ar-overlay');
    const els = overlay.querySelectorAll('[data-state]');
    for (let i = 0; i < els.length; i++) {
        els[i].hidden = els[i].dataset.state !== overlayState;
    }
}

function wireOverlayButtons(session) {
    const overlay = document.getElementById('dprcu-ar-overlay');
    overlay.querySelector('.dprcu-ar-exit').onclick = () => session.end();
}

async function launchQuickLook() {
    const ab = await new USDZExporter().parseAsync(state.model, { quickLookCompatible: true });
    if (!ab || ab.byteLength < 100) throw new Error('USDZ empty');

    const url = URL.createObjectURL(new Blob([ab], { type: 'model/vnd.usdz+zip' }));
    const a = document.createElement('a');
    a.rel = 'ar';
    a.href = url;
    const img = document.createElement('img');
    img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
    a.appendChild(img);
    document.body.appendChild(a);
    a.click();
    setTimeout(() => { a.remove(); URL.revokeObjectURL(url); }, 5000);
}

function detectARPlatform() {
    const ua = navigator.userAgent;
    const uaData = navigator.userAgentData;

    const isIPhone = /iPad|iPhone|iPod/.test(ua);
    const isIPadOS = (
        (uaData?.platform === 'macOS' || navigator.platform === 'MacIntel') &&
        navigator.maxTouchPoints > 1
    );
    const isVisionOS = /Macintosh.*Safari/.test(ua) && navigator.maxTouchPoints > 0 && !('ontouchstart' in window) === false;
    if (isIPhone || isIPadOS || isVisionOS) return 'ios';

    const isAndroid = /android/i.test(ua);
    const isAndroidChromeLike = isAndroid && /Chrome|SamsungBrowser/.test(ua) && !/Firefox/.test(ua);
    if (isAndroidChromeLike) return 'android';

    return 'android';
}

let hitTestSource = null;
let hitTestSourceRequested = false;
let reticle = null;
let arRoot = null;
let arGroup = null;
let arOriginalState = null;
let arController = null;
let arPlaced = false;

/**
 * Swap model materials to simpler Lambert shaders for AR mode.
 * Lambert is ~10x cheaper than MeshStandardMaterial and looks good under camera light.
 */
function swapToARMaterials() {
    arRoot.traverse((o) => {
        if (!o.isMesh || !o.material) return;

        o.userData.originalMaterial = o.material;

        const originalMat = Array.isArray(o.material) ? o.material[0] : o.material;
        o.material = new THREE.MeshLambertMaterial({
            map: originalMat.map,
            color: originalMat.color?.clone() ?? new THREE.Color(0xffffff),
        });
    });
}

/**
 * Restore original PBR materials after exiting AR.
 * Disposes the temporary Lambert materials to free GPU memory.
 */
function restoreOriginalMaterials() {
    arRoot.traverse((o) => {
        if (o.userData.originalMaterial) {
            if (o.material?.dispose) o.material.dispose();
            o.material = o.userData.originalMaterial;
            delete o.userData.originalMaterial;
        }
    });
}

function setupReticleAndPlacement(session) {
    if (reticle) {
        scene.remove(reticle);
        reticle.geometry.dispose();
        reticle.material.dispose();
    }

    reticle = new THREE.Mesh(
        new THREE.RingGeometry(0.07, 0.09, 32).rotateX(-Math.PI / 2),
        new THREE.MeshBasicMaterial({ color: 0xffffff, toneMapped: false })
    );
    reticle.matrixAutoUpdate = false;
    reticle.visible = false;
    scene.add(reticle);

    arGroup = new THREE.Group();
    arGroup.visible = false;
    const modelClone = state.model.clone(true);
    modelClone.traverse((child) => {
        if (child.isMesh && child.material) {
            child.material = child.material.clone();
        }
    });
    arGroup.add(modelClone);
    scene.add(arGroup);
    arRoot = arGroup;

    arOriginalState = {
        background: scene.background,
        clearAlpha: renderer.getClearAlpha(),
        cameraPosition: camera.position.clone(),
        cameraQuaternion: camera.quaternion.clone(),
        cameraFov: camera.fov,
        cameraAspect: camera.aspect,
        cameraNear: camera.near,
        cameraFar: camera.far,
        rendererSize: renderer.getSize(new THREE.Vector2()),
        pixelRatio: renderer.getPixelRatio(),
        controlsTarget: controls.target.clone(),
        shadowsEnabled: renderer.shadowMap.enabled,
        toneMapping: renderer.toneMapping,
        modelVisible: state.model.visible,
    };

    state.model.visible = false;
    scene.background = null;
    renderer.setClearAlpha(0);
    renderer.shadowMap.enabled = false;
    renderer.toneMapping = THREE.NoToneMapping;

    swapToARMaterials();

    arController = renderer.xr.getController(0);
    scene.add(arController);

    arPlaced = false;

    const onSelect = () => {
        if (!reticle.visible || !arRoot) return;

        arGroup.position.setFromMatrixPosition(reticle.matrix);

        if (!arPlaced) {
            arRoot.visible = true;
            arPlaced = true;
            setOverlayState('placed');
        }
    };
    arController.addEventListener('select', onSelect);

    inXR = true;
    if (typeof animReq !== 'undefined' && animReq) {
        cancelAnimationFrame(animReq);
        animReq = null;
    }
    renderer.setAnimationLoop(animate);

    session.addEventListener('end', () => {
        renderer.setAnimationLoop(null);
        inXR = false;

        if (arController) {
            arController.removeEventListener('select', onSelect);
            scene.remove(arController);
            arController = null;
        }

        if (hitTestSource) {
            try { hitTestSource.cancel?.(); } catch { }
            hitTestSource = null;
        }

        if (reticle) {
            scene.remove(reticle);
            reticle.geometry.dispose();
            reticle.material.dispose();
            reticle = null;
        }

        if (arGroup) {
            restoreOriginalMaterials();
            scene.remove(arGroup);
            arGroup = null;
            arRoot = null;
        }

        if (arOriginalState) {
            scene.background = arOriginalState.background;
            renderer.setClearAlpha(arOriginalState.clearAlpha);

            camera.position.copy(arOriginalState.cameraPosition);
            camera.quaternion.copy(arOriginalState.cameraQuaternion);
            camera.fov = arOriginalState.cameraFov;
            camera.aspect = arOriginalState.cameraAspect;
            camera.near = arOriginalState.cameraNear;
            camera.far = arOriginalState.cameraFar;
            camera.updateProjectionMatrix();
            camera.updateMatrixWorld(true);

            renderer.setPixelRatio(arOriginalState.pixelRatio);
            renderer.setSize(arOriginalState.rendererSize.x, arOriginalState.rendererSize.y, false);

            renderer.shadowMap.enabled = arOriginalState.shadowsEnabled;
            renderer.toneMapping = arOriginalState.toneMapping;

            if (state.model) state.model.visible = arOriginalState.modelVisible;

            controls.target.copy(arOriginalState.controlsTarget);
            controls.update();
        }

        arOriginalState = null;
        document.getElementById('dprcu-ar-overlay').hidden = true;

        requestRender();
    });
}

// ========================================================
// Render loop
// ========================================================

let inXR = false;

function requestRender() {
    if (inXR) return; // setAnimationLoop is driving frames
    if (!animReq) animReq = requestAnimationFrame(animate);
}

function animate(_time, frame) {
    animReq = null;

    if (frame) {
        const session = renderer.xr.getSession();

        if (inXR && !hitTestSourceRequested) {
            hitTestSourceRequested = true;
            session.requestReferenceSpace('viewer')
                .then((space) => session.requestHitTestSource({ space }))
                .then((src) => { hitTestSource = src; })
                .catch((e) => {
                    console.error('hit-test setup failed:', e);
                    hitTestSourceRequested = false;
                });
            session.addEventListener('end', () => {
                hitTestSourceRequested = false;
                hitTestSource = null;
            }, { once: true });
        }

        if (hitTestSource) {
            const refSpace = renderer.xr.getReferenceSpace();
            try {
                const hits = frame.getHitTestResults(hitTestSource);
                if (hits.length) {
                    const pose = hits[0].getPose(refSpace);
                    reticle.visible = true;
                    reticle.matrix.fromArray(pose.transform.matrix);
                    if (arRoot && !arRoot.visible) setOverlayState('ready');
                } else {
                    reticle.visible = false;
                }
            } catch (e) {
                console.warn('hit-test error:', e);
            }
        }
    }

    if (!inXR) controls.update();

    renderer.render(scene, camera);
}
