/**
 * AUDIOBoxx help buddy — polished floating robot, canvas OLED face, tablet attachment.
 */
(function (global) {
    'use strict';

    const BRAND = 0x6366f1;
    const FACE_CYAN = 0x38bdf8;
    const SIZE = { fab: [108, 116], head: [72, 80], sm: [52, 58] };
    const FACE_W = 512;
    const FACE_H = 384;

    const instances = new WeakMap();

    function getThree() {
        return global.THREE || null;
    }

    function configureRenderer(renderer, T) {
        renderer.setClearColor(0x000000, 0);
        if (T.sRGBEncoding) renderer.outputEncoding = T.sRGBEncoding;
        if (T.ACESFilmicToneMapping) {
            renderer.toneMapping = T.ACESFilmicToneMapping;
            renderer.toneMappingExposure = 1.14;
        }
    }

    function mat(T, opts) {
        return new T.MeshStandardMaterial(opts);
    }

    function bodyMat(T, opts) {
        if (T.MeshPhysicalMaterial) {
            return new T.MeshPhysicalMaterial({
                color: 0xf8fafc,
                roughness: 0.28,
                metalness: 0.08,
                clearcoat: 0.55,
                clearcoatRoughness: 0.18,
                ...opts
            });
        }
        return mat(T, { color: 0xf8fafc, roughness: 0.22, metalness: 0.12, ...opts });
    }

    function metalMat(T, color, opts) {
        return mat(T, {
            color,
            metalness: 0.82,
            roughness: 0.22,
            ...opts
        });
    }

    /** Unlit OLED — always drawn on top of head geometry so the face is never hidden */
    function faceScreenMat(T, texture) {
        if (T.sRGBEncoding) texture.encoding = T.sRGBEncoding;
        texture.flipY = true;
        texture.needsUpdate = true;
        return new T.MeshBasicMaterial({
            map: texture,
            side: T.DoubleSide,
            depthTest: false,
            depthWrite: false
        });
    }

    function hexStr(hex) {
        return '#' + (hex >>> 0).toString(16).padStart(6, '0').slice(-6);
    }

    function colorAlpha(hexColor, a) {
        const h = hexColor.replace('#', '');
        const r = parseInt(h.slice(0, 2), 16);
        const g = parseInt(h.slice(2, 4), 16);
        const b = parseInt(h.slice(4, 6), 16);
        return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
    }

    function roundRect(ctx, x, y, w, h, r) {
        ctx.beginPath();
        ctx.moveTo(x + r, y);
        ctx.lineTo(x + w - r, y);
        ctx.quadraticCurveTo(x + w, y, x + w, y + r);
        ctx.lineTo(x + w, y + h - r);
        ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
        ctx.lineTo(x + r, y + h);
        ctx.quadraticCurveTo(x, y + h, x, y + h - r);
        ctx.lineTo(x, y + r);
        ctx.quadraticCurveTo(x, y, x + r, y);
        ctx.closePath();
    }

    function drawOledEye(ctx, x, y, color, style) {
        const glow = ctx.createRadialGradient(x, y, 2, x, y, style === 'panic' ? 38 : 32);
        glow.addColorStop(0, color);
        glow.addColorStop(0.45, colorAlpha(color, 0.55));
        glow.addColorStop(1, colorAlpha(color, 0));

        const ew = style === 'panic' ? 44 : 36;
        const eh = style === 'panic' ? 52 : 40;
        ctx.save();
        ctx.shadowColor = color;
        ctx.shadowBlur = style === 'panic' ? 28 : 16;
        ctx.fillStyle = glow;
        ctx.beginPath();
        ctx.ellipse(x, y, ew * 0.55, eh * 0.55, 0, 0, Math.PI * 2);
        ctx.fill();
        ctx.shadowBlur = 8;
        ctx.fillStyle = color;
        ctx.beginPath();
        ctx.ellipse(x, y, ew * 0.38, eh * 0.42, 0, 0, Math.PI * 2);
        ctx.fill();
        ctx.shadowBlur = 0;
        ctx.fillStyle = '#030508';
        const pr = style === 'panic' ? 11 : 9;
        ctx.beginPath();
        ctx.ellipse(x, y + (style === 'sad' ? 6 : 3), pr, pr * 1.15, 0, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = 'rgba(255,255,255,0.92)';
        ctx.beginPath();
        ctx.ellipse(x - pr * 0.45, y - pr * 0.55, pr * 0.28, pr * 0.22, -0.4, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();
    }

    function drawBlinkLids(ctx, cx, eyeY, color) {
        ctx.strokeStyle = color;
        ctx.lineWidth = 9;
        ctx.lineCap = 'round';
        ctx.beginPath();
        ctx.moveTo(cx - 72, eyeY);
        ctx.quadraticCurveTo(cx - 52, eyeY - 6, cx - 32, eyeY);
        ctx.moveTo(cx + 32, eyeY);
        ctx.quadraticCurveTo(cx + 52, eyeY - 6, cx + 72, eyeY);
        ctx.stroke();
    }

    function drawFaceToCanvas(ctx, expr, colorHex, blink) {
        const w = FACE_W;
        const h = FACE_H;
        const color = hexStr(colorHex);
        ctx.clearRect(0, 0, w, h);

        roundRect(ctx, 12, 12, w - 24, h - 24, 40);
        ctx.strokeStyle = '#1e293b';
        ctx.lineWidth = 6;
        ctx.stroke();
        roundRect(ctx, 20, 20, w - 40, h - 40, 32);
        const bg = ctx.createLinearGradient(0, 20, 0, h);
        bg.addColorStop(0, '#111827');
        bg.addColorStop(0.55, '#070b12');
        bg.addColorStop(1, '#020408');
        ctx.fillStyle = bg;
        ctx.fill();

        const gloss = ctx.createLinearGradient(0, 24, 0, h * 0.42);
        gloss.addColorStop(0, 'rgba(255,255,255,0.14)');
        gloss.addColorStop(1, 'rgba(255,255,255,0)');
        ctx.fillStyle = gloss;
        ctx.fill();

        const scan = ctx.createLinearGradient(0, 0, w, 0);
        scan.addColorStop(0, 'rgba(255,255,255,0)');
        scan.addColorStop(0.5, 'rgba(255,255,255,0.03)');
        scan.addColorStop(1, 'rgba(255,255,255,0)');
        ctx.fillStyle = scan;
        ctx.fillRect(20, 20, w - 40, h - 40);

        const cx = w / 2;
        const eyeY = h * 0.37;
        const eyeOff = 58;

        if (blink) {
            drawBlinkLids(ctx, cx, eyeY, color);
        } else if (expr === 'think' || expr === 'work') {
            ctx.fillStyle = color;
            ctx.shadowColor = color;
            ctx.shadowBlur = 10;
            ctx.fillRect(cx - eyeOff - 8, eyeY - 3, 40, 6);
            ctx.fillRect(cx + eyeOff - 32, eyeY - 3, 40, 6);
            ctx.shadowBlur = 0;
        } else if (expr === 'sad') {
            drawOledEye(ctx, cx - eyeOff, eyeY + 14, color, 'sad');
            drawOledEye(ctx, cx + eyeOff, eyeY + 14, color, 'sad');
        } else {
            const eyeStyle = expr === 'panic' ? 'panic' : 'happy';
            drawOledEye(ctx, cx - eyeOff, eyeY, color, eyeStyle);
            drawOledEye(ctx, cx + eyeOff, eyeY, color, eyeStyle);
        }

        ctx.shadowColor = color;
        ctx.shadowBlur = expr === 'celebrate' ? 20 : 14;
        ctx.strokeStyle = color;
        ctx.fillStyle = color;
        ctx.lineWidth = 10;
        ctx.lineCap = 'round';

        const mouthY = h * 0.64;
        if (expr === 'panic') {
            ctx.lineWidth = 8;
            ctx.beginPath();
            ctx.ellipse(cx, mouthY, 16, 20, 0, 0, Math.PI * 2);
            ctx.stroke();
        } else if (expr === 'celebrate') {
            ctx.beginPath();
            ctx.moveTo(cx, mouthY + 10);
            ctx.bezierCurveTo(cx - 22, mouthY - 6, cx - 10, mouthY - 24, cx, mouthY - 12);
            ctx.bezierCurveTo(cx + 10, mouthY - 24, cx + 22, mouthY - 6, cx, mouthY + 10);
            ctx.fill();
        } else if (expr === 'sad') {
            ctx.beginPath();
            ctx.arc(cx, mouthY + 8, 28, Math.PI * 1.15, Math.PI * 1.85);
            ctx.stroke();
        } else if (expr === 'think' || expr === 'work') {
            ctx.beginPath();
            ctx.moveTo(cx - 26, mouthY);
            ctx.lineTo(cx + 26, mouthY);
            ctx.stroke();
        } else {
            ctx.beginPath();
            ctx.arc(cx, mouthY - 4, 30, 0.22, Math.PI - 0.22);
            ctx.stroke();
        }
        ctx.shadowBlur = 0;

        const vig = ctx.createRadialGradient(cx, h * 0.45, h * 0.12, cx, h * 0.45, h * 0.52);
        vig.addColorStop(0, 'rgba(0,0,0,0)');
        vig.addColorStop(1, 'rgba(0,0,0,0.45)');
        ctx.fillStyle = vig;
        ctx.fillRect(20, 20, w - 40, h - 40);
    }

    function easeOutCubic(x) {
        return 1 - Math.pow(1 - x, 3);
    }

    function easeInOutSine(x) {
        return -(Math.cos(Math.PI * x) - 1) / 2;
    }

    function setEmissive(mesh, hex, intensity) {
        if (!mesh?.material) return;
        if (mesh.material.emissive) mesh.material.emissive.setHex(hex);
        if ('emissiveIntensity' in mesh.material) mesh.material.emissiveIntensity = intensity;
    }

    class BuddyRenderer {
        constructor(host, sizeKey) {
            const T = getThree();
            if (!T) throw new Error('THREE not loaded');

            this.host = host;
            this.sizeKey = sizeKey || 'fab';
            this.mood = 'idle';
            this.expression = 'happy';
            this.faceColor = FACE_CYAN;
            this.stunt = null;
            this.clock = new T.Clock();
            this.raf = 0;
            this._dead = false;
            this.taskPulse = 0;
            this.baseYaw = 0.14;
            this.spinYaw = 0.14;
            this.camOrbit = 0;
            this.blinkT = 0;
            this._blinkShut = false;

            let canvas = host.querySelector('.abox-3d-canvas');
            if (!canvas) {
                canvas = document.createElement('canvas');
                canvas.className = 'abox-3d-canvas';
                host.appendChild(canvas);
            }

            const dims = SIZE[this.sizeKey] || SIZE.fab;
            canvas.width = dims[0];
            canvas.height = dims[1];

            this.renderer = new T.WebGLRenderer({ canvas, alpha: true, antialias: true });
            if (!this.renderer.getContext()) throw new Error('WebGL unavailable');
            this.renderer.sortObjects = true;
            this.renderer.shadowMap.enabled = true;
            if (T.PCFSoftShadowMap) this.renderer.shadowMap.type = T.PCFSoftShadowMap;
            configureRenderer(this.renderer, T);
            this.renderer.setPixelRatio(Math.min(2.5, global.devicePixelRatio || 1));
            this.renderer.setSize(dims[0], dims[1], false);

            this.scene = new T.Scene();
            this.scene.fog = null;

            this.camera = new T.PerspectiveCamera(28, dims[0] / dims[1], 0.06, 28);
            this.camera.position.set(0.22, 0.56, 2.65);
            this.camera.lookAt(0.02, 0.58, 0);

            this.scene.add(new T.AmbientLight(0xe8eef6, 0.42));
            this.scene.add(new T.HemisphereLight(0xffffff, 0x64748b, 0.55));

            const key = new T.DirectionalLight(0xfff7ed, 1.15);
            key.position.set(2.8, 5.5, 4.2);
            key.castShadow = true;
            key.shadow.mapSize.width = 512;
            key.shadow.mapSize.height = 512;
            key.shadow.camera.near = 0.5;
            key.shadow.camera.far = 12;
            key.shadow.camera.left = -1.2;
            key.shadow.camera.right = 1.2;
            key.shadow.camera.top = 1.2;
            key.shadow.camera.bottom = -1.2;
            key.shadow.bias = -0.0008;
            this.scene.add(key);
            this.keyLight = key;

            const rim = new T.DirectionalLight(0xc7d2fe, 0.62);
            rim.position.set(-3.5, 2.2, 2.5);
            this.scene.add(rim);

            const fill = new T.DirectionalLight(0xffffff, 0.38);
            fill.position.set(0.2, 0.5, 5.5);
            this.scene.add(fill);

            const faceGlow = new T.PointLight(0x38bdf8, 0.55, 2.5);
            faceGlow.position.set(0, 0.72, 0.55);
            this.scene.add(faceGlow);
            this.faceGlow = faceGlow;

            this.ground = new T.Mesh(
                new T.CircleGeometry(0.55, 48),
                mat(T, { color: 0x000000, transparent: true, opacity: 0.01, roughness: 1 })
            );
            this.ground.rotation.x = -Math.PI / 2;
            this.ground.position.y = 0.01;
            this.ground.receiveShadow = true;
            this.scene.add(this.ground);

            this.hoverShadow = new T.Mesh(
                new T.CircleGeometry(0.34, 48),
                mat(T, { color: 0x000000, transparent: true, opacity: 0.18, roughness: 1 })
            );
            this.hoverShadow.rotation.x = -Math.PI / 2;
            this.hoverShadow.position.y = 0.015;
            this.scene.add(this.hoverShadow);

            this.robot = this._buildRobot(T);
            this.scene.add(this.robot);

            host.classList.add('abox-3d-ready');
            this._setExpression('happy');
            this._setFaceColor(FACE_CYAN);
            if (this.faceScreen?.texture) this.faceScreen.texture.needsUpdate = true;
            if (typeof this.renderer.compile === 'function') {
                this.renderer.compile(this.scene, this.camera);
            }

            this._resizeObs = typeof ResizeObserver !== 'undefined'
                ? new ResizeObserver(() => this._resize())
                : null;
            this._resizeObs?.observe(host);
            this._animate = this._animate.bind(this);
            this.raf = requestAnimationFrame(this._animate);
            this._resize();
            this.renderer.render(this.scene, this.camera);
        }

        _white(T) {
            return bodyMat(T);
        }

        _shadow(mesh) {
            if (mesh) {
                mesh.castShadow = true;
                mesh.receiveShadow = true;
            }
        }

        _buildFaceTexture(T) {
            const c = document.createElement('canvas');
            c.width = FACE_W;
            c.height = FACE_H;
            const ctx = c.getContext('2d');
            const tex = new T.CanvasTexture(c);
            tex.minFilter = T.LinearFilter;
            tex.magFilter = T.LinearFilter;
            tex.flipY = true;
            if (T.sRGBEncoding) tex.encoding = T.sRGBEncoding;
            return { canvas: c, ctx, texture: tex };
        }

        _buildRobot(T) {
            const g = new T.Group();
            const white = this._white(T);
            const trim = metalMat(T, 0x94a3b8);
            const dark = mat(T, { color: 0x1e293b, roughness: 0.35, metalness: 0.45 });

            const hoverRing = new T.Mesh(
                new T.TorusGeometry(0.2, 0.028, 20, 40),
                mat(T, { color: 0x6366f1, emissive: 0x4f46e5, emissiveIntensity: 0.25, metalness: 0.6, roughness: 0.3 })
            );
            hoverRing.rotation.x = Math.PI / 2;
            hoverRing.position.y = 0.08;
            g.add(hoverRing);
            this._shadow(hoverRing);

            const bodyProfile = [
                new T.Vector2(0.001, 0),
                new T.Vector2(0.2, 0.02),
                new T.Vector2(0.24, 0.12),
                new T.Vector2(0.22, 0.22),
                new T.Vector2(0.17, 0.32),
                new T.Vector2(0.14, 0.38),
                new T.Vector2(0.001, 0.4)
            ];
            this.torso = new T.Mesh(new T.LatheGeometry(bodyProfile, 40), white);
            this.torso.position.y = 0.12;
            g.add(this.torso);
            this._shadow(this.torso);

            const bellyCap = new T.Mesh(new T.SphereGeometry(0.2, 32, 24, 0, Math.PI * 2, 0, Math.PI * 0.55), white);
            bellyCap.position.y = 0.2;
            bellyCap.scale.set(1.05, 0.65, 0.9);
            g.add(bellyCap);
            this._shadow(bellyCap);

            const neck = new T.Mesh(new T.CylinderGeometry(0.1, 0.13, 0.09, 28), white);
            neck.position.y = 0.48;
            g.add(neck);
            this._shadow(neck);

            const collar = new T.Mesh(new T.TorusGeometry(0.13, 0.018, 16, 36), trim);
            collar.rotation.x = Math.PI / 2;
            collar.position.y = 0.52;
            g.add(collar);

            this.head = new T.Group();
            this.head.position.y = 0.68;
            g.add(this.head);

            const headBack = new T.Mesh(
                new T.SphereGeometry(0.32, 40, 36),
                white
            );
            headBack.scale.set(1.05, 0.95, 0.55);
            headBack.position.set(0, 0.04, -0.1);
            this.head.add(headBack);
            this._shadow(headBack);

            const headBox = new T.Mesh(
                new T.BoxGeometry(0.62, 0.48, 0.34),
                white
            );
            headBox.position.set(0, 0.02, -0.05);
            this.head.add(headBox);
            this._shadow(headBox);

            const brow = new T.Mesh(new T.SphereGeometry(0.16, 28, 20), white);
            brow.scale.set(1.2, 0.4, 0.45);
            brow.position.set(0, 0.2, -0.02);
            this.head.add(brow);
            this._shadow(brow);

            this.faceModule = new T.Group();
            this.faceModule.position.set(0, 0.02, 0.14);
            this.head.add(this.faceModule);

            const pw = 0.54;
            const ph = 0.38;
            const recessD = 0.034;
            const wellMat = mat(T, { color: 0x020408, roughness: 0.9, metalness: 0.05 });
            const lipMat = bodyMat(T, { color: 0xe8eef4, clearcoat: 0.65 });

            const wellBack = new T.Mesh(new T.PlaneGeometry(pw, ph), wellMat);
            wellBack.position.z = -recessD;
            wellBack.renderOrder = 4;
            this.faceModule.add(wellBack);

            const wallT = 0.014;
            const addWellWall = (w, h, x, y, z) => {
                const wall = new T.Mesh(new T.BoxGeometry(w, h, wallT), wellMat);
                wall.position.set(x, y, z);
                wall.renderOrder = 5;
                this.faceModule.add(wall);
            };
            addWellWall(pw + 0.02, wallT, 0, ph * 0.5, -recessD * 0.5);
            addWellWall(pw + 0.02, wallT, 0, -ph * 0.5, -recessD * 0.5);
            addWellWall(wallT, ph, -pw * 0.5, 0, -recessD * 0.5);
            addWellWall(wallT, ph, pw * 0.5, 0, -recessD * 0.5);

            this.faceScreen = this._buildFaceTexture(T);
            drawFaceToCanvas(this.faceScreen.ctx, 'happy', FACE_CYAN, false);
            this.faceScreen.texture.needsUpdate = true;

            this.faceMesh = new T.Mesh(
                new T.PlaneGeometry(pw * 0.94, ph * 0.94),
                faceScreenMat(T, this.faceScreen.texture)
            );
            this.faceMesh.position.z = -recessD * 0.42;
            this.faceMesh.renderOrder = 30;
            this.faceModule.add(this.faceMesh);

            const lipThick = 0.02;
            const lipZ = -0.005;
            const addFrameLip = (w, h, x, y) => {
                const lip = new T.Mesh(new T.BoxGeometry(w, h, lipThick), lipMat);
                lip.position.set(x, y, lipZ);
                lip.renderOrder = 8;
                this.faceModule.add(lip);
                this._shadow(lip);
            };
            const fw = pw + 0.08;
            const fh = ph + 0.08;
            addFrameLip(fw, 0.034, 0, fh * 0.5);
            addFrameLip(fw, 0.034, 0, -fh * 0.5);
            addFrameLip(0.034, fh, -fw * 0.5, 0);
            addFrameLip(0.034, fh, fw * 0.5, 0);

            const frameTrim = new T.Mesh(
                new T.BoxGeometry(fw + 0.02, fh + 0.02, 0.01),
                dark
            );
            frameTrim.position.z = -recessD - 0.004;
            frameTrim.renderOrder = 3;
            this.faceModule.add(frameTrim);

            const chinPlate = new T.Mesh(
                new T.BoxGeometry(0.6, 0.085, 0.05),
                lipMat
            );
            chinPlate.position.set(0, -ph * 0.5 - 0.055, lipZ - 0.01);
            chinPlate.renderOrder = 8;
            this.faceModule.add(chinPlate);
            this._shadow(chinPlate);

            this.faceGlass = null;

            const antenna = new T.Mesh(new T.CylinderGeometry(0.012, 0.018, 0.08, 12), trim);
            antenna.position.set(0.14, 0.28, -0.05);
            this.head.add(antenna);

            const mkArm = (side) => {
                const arm = new T.Group();
                const joint = new T.Mesh(new T.SphereGeometry(0.055, 20, 16), white);
                arm.add(joint);
                this._shadow(joint);
                const upper = new T.Mesh(new T.CylinderGeometry(0.04, 0.05, 0.14, 16), white);
                upper.rotation.z = Math.PI / 2;
                upper.position.set(side * 0.07, -0.02, 0);
                arm.add(upper);
                this._shadow(upper);
                const fore = new T.Mesh(new T.SphereGeometry(0.045, 16, 14), white);
                fore.scale.set(0.55, 1.2, 0.55);
                fore.position.set(side * 0.12, -0.1, 0.02);
                arm.add(fore);
                this._shadow(fore);
                arm.position.set(side * 0.34, 0.34, 0.04);
                arm.rotation.z = side * 0.32;
                return arm;
            };
            this.armL = mkArm(-1);
            this.armR = mkArm(1);
            g.add(this.armL);
            g.add(this.armR);

            this.attachment = new T.Group();
            this.attachment.position.set(0.42, 0.3, 0.1);
            this.attachment.rotation.z = -0.32;
            g.add(this.attachment);

            const clip = new T.Mesh(
                new T.BoxGeometry(0.05, 0.11, 0.02),
                trim
            );
            clip.position.set(-0.038, 0.03, 0);
            this.attachment.add(clip);

            this.tablet = new T.Mesh(
                new T.BoxGeometry(0.18, 0.24, 0.025),
                mat(T, { color: 0x0f172a, roughness: 0.25, metalness: 0.55 })
            );
            this.tablet.position.set(0.02, 0, 0.018);
            this.attachment.add(this.tablet);
            this._shadow(this.tablet);

            const tabletBezel = new T.Mesh(
                new T.BoxGeometry(0.2, 0.26, 0.018),
                trim
            );
            tabletBezel.position.set(0.02, 0, 0.01);
            this.attachment.add(tabletBezel);

            this.tabletGlow = new T.Mesh(
                new T.BoxGeometry(0.14, 0.18, 0.006),
                mat(T, { color: BRAND, emissive: BRAND, emissiveIntensity: 0.45, roughness: 0.35 })
            );
            this.tabletGlow.position.set(0.02, 0, 0.034);
            this.attachment.add(this.tabletGlow);

            this.tabletBars = [];
            for (let i = 0; i < 3; i++) {
                const bar = new T.Mesh(
                    new T.BoxGeometry(0.022, 0.035 + i * 0.018, 0.006),
                    mat(T, { color: 0xc7d2fe, emissive: 0xa5b4fc, emissiveIntensity: 0.4 })
                );
                bar.position.set(-0.018 + i * 0.018, -0.015 + i * 0.008, 0.038);
                this.attachment.add(bar);
                this.tabletBars.push(bar);
            }

            this.coins = new T.Group();
            this.coinMeshes = [];
            const coinMat = metalMat(T, 0xfbbf24, { emissive: 0xf59e0b, emissiveIntensity: 0.5 });
            for (let i = 0; i < 5; i++) {
                const coin = new T.Mesh(new T.CylinderGeometry(0.035, 0.035, 0.008, 20), coinMat);
                coin.rotation.x = Math.PI / 2;
                coin.visible = false;
                coin.castShadow = true;
                this.coins.add(coin);
                this.coinMeshes.push(coin);
            }
            this.coins.position.set(0.12, 0.78, 0.14);
            g.add(this.coins);

            g.position.y = 0.06;
            return g;
        }

        _drawFace(expr, colorHex, blink) {
            drawFaceToCanvas(this.faceScreen.ctx, expr, colorHex, !!blink);
            const tex = this.faceScreen.texture;
            tex.needsUpdate = true;
            if (this.faceMesh?.material?.map !== tex) {
                this.faceMesh.material.map = tex;
                this.faceMesh.material.needsUpdate = true;
            }
        }

        _setFaceColor(hex) {
            this.faceColor = hex;
            this._drawFace(this.expression, hex, this._blinkShut);
        }

        _setExpression(name) {
            this.expression = name;
            this._drawFace(name, this.faceColor, this._blinkShut);
        }

        _hideCoins() {
            this.coinMeshes.forEach((c) => { c.visible = false; });
        }

        _startStunt(def) {
            this.stunt = {
                duration: def.duration,
                elapsed: 0,
                loop: !!def.loop,
                update: def.update,
                done: def.done,
                fullRotate: !!def.fullRotate
            };
        }

        playStunt(kind) {
            switch (kind) {
                case 'fingerprint-start':
                    this._setExpression('work');
                    this._startStunt({
                        duration: 0.55,
                        update: (p, t) => {
                            this.head.rotation.x = easeOutCubic(p) * 0.1;
                            this.attachment.rotation.z = -0.32 + Math.sin(t * 5) * 0.06;
                            setEmissive(this.tabletGlow, 0xfbbf24, 0.45 + p * 0.35);
                        }
                    });
                    break;
                case 'fingerprint-decode':
                    this._setExpression('think');
                    this._startStunt({
                        duration: 0.75,
                        update: (p, t) => {
                            this.head.rotation.y = Math.sin(t * 2.5) * 0.12;
                            this.tabletBars.forEach((b, i) => {
                                b.scale.y = 0.6 + Math.abs(Math.sin(t * 7 + i)) * 0.9;
                            });
                        }
                    });
                    break;
                case 'fingerprint-hash':
                    this._setExpression('work');
                    this._startStunt({
                        duration: 0.5,
                        update: (p, t) => {
                            this.tabletBars.forEach((b, i) => {
                                b.scale.y = 0.55 + Math.abs(Math.sin(t * 12 + i)) * 0.85;
                            });
                        }
                    });
                    break;
                case 'identify-start':
                    this._setExpression('think');
                    this._startStunt({
                        duration: 0.6,
                        update: (p) => {
                            const e = easeOutCubic(p);
                            this.attachment.rotation.z = -0.32 - e * 0.18;
                            this.head.rotation.x = -e * 0.07;
                            setEmissive(this.tabletGlow, 0x22d3ee, 0.4 + e * 0.45);
                        }
                    });
                    break;
                case 'identify-search':
                    this._setExpression('work');
                    this._startStunt({
                        duration: 0.85,
                        update: (p, t) => {
                            this.robot.rotation.y = this.baseYaw + Math.sin(t * 2.2) * 0.18;
                            setEmissive(this.tabletGlow, 0x22d3ee, 0.55 + Math.sin(t * 4) * 0.25);
                        },
                        done: () => { this.robot.rotation.y = this.baseYaw; }
                    });
                    break;
                case 'task-done':
                    this._setExpression('celebrate');
                    this._startStunt({
                        duration: 0.5,
                        update: (p) => {
                            const n = easeInOutSine(p < 0.5 ? p * 2 : (1 - p) * 2);
                            this.robot.position.y = 0.06 + n * 0.05;
                            setEmissive(this.tabletGlow, 0x4ade80, 0.5);
                        },
                        done: () => this._applyMoodExpression()
                    });
                    break;
                case 'task-error':
                    this._setExpression('sad');
                    this._setFaceColor(0xf87171);
                    this._startStunt({
                        duration: 0.45,
                        update: (p, t) => {
                            this.robot.position.x = Math.sin(t * 26) * 0.025 * (1 - p);
                        },
                        done: () => { this._setFaceColor(FACE_CYAN); this._applyMoodExpression(); }
                    });
                    break;
                case 'task-cancel':
                    this._setExpression('sad');
                    this._startStunt({ duration: 0.35, update: (p) => { this.head.rotation.z = easeOutCubic(p) * -0.08; } });
                    break;
                case 'think':
                    this._setExpression('think');
                    this._startStunt({
                        duration: 0.4,
                        update: (p, t) => {
                            this.head.rotation.y = Math.sin(t * 1.8) * 0.08 * easeOutCubic(p);
                            setEmissive(this.tabletGlow, 0xa78bfa, 0.5);
                        }
                    });
                    break;
                case 'system-panic':
                    this._setExpression('panic');
                    this._setFaceColor(0xf87171);
                    this._startStunt({
                        duration: 1.1,
                        update: (p, t) => {
                            this.robot.position.x = Math.sin(t * 20) * 0.045;
                            this.robot.position.y = 0.06 + Math.abs(Math.sin(t * 16)) * 0.05;
                            this.robot.rotation.z = Math.sin(t * 10) * 0.06;
                            this.head.rotation.z = Math.sin(t * 14) * 0.12;
                            this.head.rotation.x = Math.sin(t * 8) * 0.05;
                            this.armL.rotation.z = 0.28 + Math.sin(t * 18) * 0.35;
                            this.armR.rotation.z = -0.28 - Math.sin(t * 18) * 0.35;
                            setEmissive(this.tabletGlow, 0xef4444, 0.55 + Math.sin(t * 12) * 0.2);
                        },
                        done: () => {
                            this.robot.position.x = 0;
                            this.robot.rotation.z = 0;
                        }
                    });
                    break;
                case 'match-money':
                    this._setExpression('celebrate');
                    this._setFaceColor(0xfbbf24);
                    this._startStunt({
                        duration: 1.05,
                        update: (p, t) => {
                            this.robot.position.y = 0.06 + Math.sin(p * Math.PI) * 0.08;
                            this.armR.rotation.z = -0.28 - Math.sin(t * 7) * 0.35;
                            this.coinMeshes.forEach((coin, i) => {
                                coin.visible = p > 0.05;
                                const ph = t * 2.8 + i;
                                coin.position.set(Math.sin(ph) * 0.14, 0.08 + Math.sin(ph) * 0.1, Math.cos(ph) * 0.06);
                            });
                        },
                        done: () => {
                            this._hideCoins();
                            this._setFaceColor(FACE_CYAN);
                            this._applyMoodExpression();
                        }
                    });
                    break;
                case 'match-nomatch':
                    this._setExpression('sad');
                    this._startStunt({
                        duration: 0.5,
                        update: (p) => { this.head.rotation.z = easeOutCubic(p) * -0.07; },
                        done: () => this._applyMoodExpression()
                    });
                    break;
                case 'spin-360':
                    this._setExpression('happy');
                    this.spinYaw = this.robot.rotation.y;
                    this._startStunt({
                        duration: 1.45,
                        fullRotate: true,
                        update: (p) => {
                            const e = easeInOutSine(p);
                            this.robot.rotation.y = this.spinYaw + e * Math.PI * 2;
                        },
                        done: () => {
                            this.robot.rotation.y = this.baseYaw;
                            this.spinYaw = this.baseYaw;
                        }
                    });
                    break;
                case 'wave':
                    this._setExpression('happy');
                    this._startStunt({
                        duration: 0.9,
                        update: (p, t) => {
                            const e = easeInOutSine(Math.min(1, p * 1.4));
                            this.armR.rotation.z = -0.28 - e * 0.9;
                            this.armR.rotation.x = Math.sin(t * 8) * 0.15 * e;
                            this.head.rotation.z = Math.sin(t * 4) * 0.04 * e;
                        },
                        done: () => { this.armR.rotation.z = -0.28; this.armR.rotation.x = 0; }
                    });
                    break;
                case 'nod':
                    this._setExpression('happy');
                    this._startStunt({
                        duration: 0.65,
                        update: (p, t) => {
                            const e = easeInOutSine(p);
                            this.head.rotation.x = Math.sin(t * 10) * 0.12 * e;
                        },
                        done: () => { this.head.rotation.x = 0; }
                    });
                    break;
                case 'peek':
                    this._setExpression('think');
                    this._startStunt({
                        duration: 0.7,
                        update: (p, t) => {
                            const e = easeOutCubic(p);
                            this.head.rotation.y = Math.sin(t * 2.2) * 0.22 * e;
                            this.head.rotation.x = -0.06 * e;
                        },
                        done: () => { this.head.rotation.y = 0; this.head.rotation.x = 0; }
                    });
                    break;
                case 'bounce':
                    this._setExpression('celebrate');
                    this._startStunt({
                        duration: 0.75,
                        update: (p, t) => {
                            const e = Math.abs(Math.sin(t * 5)) * (1 - p * 0.3);
                            this.robot.position.y = 0.06 + e * 0.07;
                            setEmissive(this.tabletGlow, 0xfbbf24, 0.45 + e * 0.3);
                        },
                        done: () => this._applyMoodExpression()
                    });
                    break;
                case 'wiggle':
                    this._setExpression('happy');
                    this._startStunt({
                        duration: 0.6,
                        update: (p, t) => {
                            const e = 1 - p;
                            this.robot.rotation.z = Math.sin(t * 14) * 0.08 * e;
                            this.robot.position.x = Math.sin(t * 12) * 0.03 * e;
                        },
                        done: () => { this.robot.rotation.z = 0; this.robot.position.x = 0; }
                    });
                    break;
                case 'look-around':
                    this._setExpression('think');
                    this._setFaceColor(0x22d3ee);
                    this._startStunt({
                        duration: 1.1,
                        update: (p, t) => {
                            this.head.rotation.y = Math.sin(t * 1.6) * 0.28 * easeInOutSine(Math.min(1, p * 1.2));
                            this.head.rotation.x = Math.sin(t * 0.9) * 0.04;
                        },
                        done: () => {
                            this.head.rotation.y = 0;
                            this.head.rotation.x = 0;
                            this._applyMoodExpression();
                        }
                    });
                    break;
                case 'tablet-tap':
                    this._setExpression('work');
                    this._startStunt({
                        duration: 0.55,
                        update: (p, t) => {
                            this.attachment.rotation.z = -0.32 + Math.sin(t * 16) * 0.08;
                            this.attachment.position.y = 0.3 + Math.abs(Math.sin(t * 18)) * 0.02;
                            setEmissive(this.tabletGlow, BRAND, 0.5 + Math.sin(t * 10) * 0.25);
                        },
                        done: () => { this.attachment.position.y = 0.3; }
                    });
                    break;
                case 'shiver':
                    this._setExpression('think');
                    this._setFaceColor(0x64748b);
                    this._startStunt({
                        duration: 0.7,
                        update: (p, t) => {
                            const e = 1 - p * 0.5;
                            this.robot.position.x = Math.sin(t * 22) * 0.015 * e;
                            this.head.rotation.z = Math.sin(t * 18) * 0.05 * e;
                        },
                        done: () => { this._setFaceColor(FACE_CYAN); this._applyMoodExpression(); }
                    });
                    break;
                case 'alert-scan':
                    this._setExpression('work');
                    this._setFaceColor(0x22d3ee);
                    this._startStunt({
                        duration: 0.85,
                        update: (p, t) => {
                            this.head.rotation.y = Math.sin(t * 3.5) * 0.2;
                            this.tabletBars.forEach((b, i) => {
                                b.scale.y = 0.5 + Math.abs(Math.sin(t * 9 + i * 1.2)) * 1.1;
                            });
                            setEmissive(this.tabletGlow, 0x22d3ee, 0.55);
                        },
                        done: () => this._applyMoodExpression()
                    });
                    break;
                case 'shrug':
                    this._setExpression('sad');
                    this._startStunt({
                        duration: 0.55,
                        update: (p, t) => {
                            const e = easeOutCubic(p);
                            this.armL.rotation.z = 0.28 + e * 0.35;
                            this.armR.rotation.z = -0.28 - e * 0.35;
                            this.head.rotation.x = e * 0.05;
                            this.head.rotation.z = Math.sin(t * 6) * 0.04 * e;
                        },
                        done: () => {
                            this.armL.rotation.z = 0.28;
                            this.armR.rotation.z = -0.28;
                            this._applyMoodExpression();
                        }
                    });
                    break;
                case 'stretch':
                    this._setExpression('happy');
                    this._startStunt({
                        duration: 0.8,
                        update: (p) => {
                            const e = easeInOutSine(p < 0.5 ? p * 2 : (1 - p) * 2);
                            this.robot.position.y = 0.06 + e * 0.06;
                            this.armL.rotation.z = 0.28 - e * 0.15;
                            this.armR.rotation.z = -0.28 + e * 0.15;
                        },
                        done: () => { this.robot.position.y = 0.06; }
                    });
                    break;
                default:
                    break;
            }
        }

        _applyMoodExpression() {
            const m = this.mood;
            if (m === 'panic' || m === 'alert') {
                this._setExpression('panic');
                this._setFaceColor(0xf87171);
            } else if (m === 'celebrate') {
                this._setExpression('celebrate');
                this._setFaceColor(0xfbbf24);
            } else if (m === 'think') {
                this._setExpression('think');
                this._setFaceColor(0xa78bfa);
            } else if (m === 'fingerprint' || m === 'fingerprint-decode') {
                this._setExpression(m === 'fingerprint-decode' ? 'think' : 'work');
                this._setFaceColor(0xfbbf24);
            } else if (m === 'identify') {
                this._setExpression('work');
                this._setFaceColor(0x22d3ee);
            } else if (m === 'curious') {
                this._setExpression('think');
                this._setFaceColor(0x22d3ee);
            } else if (m === 'relaxed') {
                this._setExpression('happy');
                this._setFaceColor(0x7dd3fc);
            } else if (m === 'sleepy') {
                this._setExpression('sad');
                this._setFaceColor(0x64748b);
            } else if (m === 'cheerful') {
                this._setExpression('celebrate');
                this._setFaceColor(0xfbbf24);
            } else {
                this._setExpression('happy');
                this._setFaceColor(FACE_CYAN);
            }
        }

        _resize() {
            if (this._dead) return;
            const dims = SIZE[this.sizeKey] || SIZE.fab;
            const rect = this.host.getBoundingClientRect();
            const w = Math.max(48, dims[0], Math.round(rect.width) || 0);
            const h = Math.max(48, dims[1], Math.round(rect.height) || 0);
            this.camera.aspect = w / h;
            this.camera.updateProjectionMatrix();
            this.renderer.setSize(w, h, false);
        }

        setMood(mood) {
            this.mood = mood || 'idle';
            if (!this.stunt) this._applyMoodExpression();
            const m = this.mood;
            if (m === 'identify') setEmissive(this.tabletGlow, 0x22d3ee, 0.5);
            else if (m === 'fingerprint' || m === 'fingerprint-decode') setEmissive(this.tabletGlow, 0xfbbf24, 0.45);
            else if (m === 'panic' || m === 'alert') setEmissive(this.tabletGlow, 0xef4444, 0.45);
            else if (m === 'celebrate' || m === 'cheerful') setEmissive(this.tabletGlow, 0xfbbf24, 0.55);
            else if (m === 'curious') setEmissive(this.tabletGlow, 0x22d3ee, 0.48);
            else if (m === 'sleepy') setEmissive(this.tabletGlow, 0x64748b, 0.28);
            else if (m === 'relaxed') setEmissive(this.tabletGlow, 0x7dd3fc, 0.38);
            else setEmissive(this.tabletGlow, BRAND, 0.4);
            if (this.faceGlow) {
                const glowHex = m === 'panic' || m === 'alert' ? 0xf87171
                    : (m === 'celebrate' || m === 'cheerful' ? 0xfbbf24 : FACE_CYAN);
                this.faceGlow.color.setHex(glowHex);
                this.faceGlow.intensity = m === 'panic' ? 0.55 : 0.35;
            }
        }

        _idleCameraSway(dt) {
            if (this.stunt?.fullRotate) return;
            this.camOrbit += dt * 0.22;
            const ox = Math.sin(this.camOrbit) * 0.1;
            const oy = Math.sin(this.camOrbit * 0.7) * 0.025;
            this.camera.position.x = 0.22 + ox;
            this.camera.position.y = 0.56 + oy;
            this.camera.position.z = 2.65;
            this.camera.lookAt(ox * 0.2, 0.58, 0);
            this.robot.rotation.x += (Math.sin(this.camOrbit * 0.5) * 0.03 - this.robot.rotation.x) * Math.min(1, dt * 2);
        }

        _animateMood(m, t, dt) {
            if (this.stunt) return;

            const taskBusy = m === 'fingerprint' || m === 'fingerprint-decode' || m === 'identify';
            if (!taskBusy) this._idleCameraSway(dt);

            if (!this.stunt) {
                this.robot.rotation.y += (this.baseYaw - this.robot.rotation.y) * Math.min(1, dt * 4);
            }
            const hover = 0.06 + Math.sin(t * 1.4) * 0.01;
            this.robot.position.y = hover;
            if (this.hoverShadow) {
                this.hoverShadow.scale.setScalar(0.92 + Math.sin(t * 1.4) * 0.05);
                this.hoverShadow.material.opacity = 0.14 + Math.sin(t * 1.4) * 0.03;
            }

            this.blinkT += dt;
            const canBlink = this.expression === 'happy' || this.mood === 'relaxed' || this.mood === 'idle';
            if (canBlink && !this._blinkShut && this.blinkT > 3.8) {
                this._blinkShut = true;
                this._drawFace(this.expression, this.faceColor, true);
            } else if (this._blinkShut && this.blinkT > 3.95) {
                this._blinkShut = false;
                this.blinkT = 0;
                this._drawFace(this.expression, this.faceColor, false);
            }

            if (m === 'panic') {
                this.robot.position.y = hover + Math.abs(Math.sin(t * 9)) * 0.025;
                this.robot.position.x = Math.sin(t * 11) * 0.02;
                this.head.rotation.z = Math.sin(t * 8) * 0.06;
                return;
            }
            if (m === 'celebrate' || m === 'cheerful') {
                this.robot.position.y = hover + Math.sin(t * 4.5) * 0.015;
                return;
            }
            if (m === 'curious') {
                this.head.rotation.y = Math.sin(t * 0.85) * 0.11;
                return;
            }
            if (m === 'sleepy') {
                this.head.rotation.x = 0.05 + Math.sin(t * 0.4) * 0.02;
                this.head.rotation.z = Math.sin(t * 0.35) * 0.04;
                return;
            }
            if (m === 'relaxed') {
                this.head.rotation.y = Math.sin(t * 0.55) * 0.05;
                return;
            }

            this.robot.position.x *= 0.88;
            this.head.rotation.x *= 0.9;
            this.head.rotation.z *= 0.9;

            if (m === 'fingerprint' || m === 'fingerprint-decode') {
                this.taskPulse += dt;
                this.attachment.rotation.z = -0.32 + Math.sin(this.taskPulse * 2.8) * 0.05;
                this.tabletBars.forEach((b, i) => {
                    b.scale.y = 0.65 + Math.abs(Math.sin(this.taskPulse * 4.5 + i)) * 0.65;
                });
            } else if (m === 'identify') {
                this.taskPulse += dt;
                this.head.rotation.x = -0.05 + Math.sin(this.taskPulse * 1.8) * 0.03;
                this.robot.rotation.y = this.baseYaw + Math.sin(this.taskPulse * 1.1) * 0.1;
            } else if (m === 'think') {
                this.head.rotation.y = Math.sin(t * 1.1) * 0.06;
            } else {
                this.taskPulse = 0;
                this.attachment.rotation.z += (-0.32 - this.attachment.rotation.z) * Math.min(1, dt * 3);
            }
        }

        _animate() {
            if (this._dead) return;
            const dt = Math.min(0.05, this.clock.getDelta());
            const t = this.clock.elapsedTime;
            const m = this.mood;

            if (this.stunt) {
                this.stunt.elapsed += dt;
                const p = Math.min(1, this.stunt.elapsed / this.stunt.duration);
                this.stunt.update(p, t);
                if (p >= 1) {
                    if (this.stunt.done) this.stunt.done();
                    if (!this.stunt.loop) this.stunt = null;
                    else this.stunt.elapsed = 0;
                }
            } else {
                this._animateMood(m, t, dt);
            }

            try {
                this.renderer.render(this.scene, this.camera);
            } catch (e) {
                console.warn('[AUDIOBoxx] buddy render:', e);
                this._dead = true;
            }
            this.raf = requestAnimationFrame(this._animate);
        }

        dispose() {
            this._dead = true;
            cancelAnimationFrame(this.raf);
            this._resizeObs?.disconnect();
            try { this.renderer.dispose(); } catch { /* ignore */ }
            this.host.classList.remove('abox-3d-ready');
        }
    }

    function parseHost(el) {
        if (!el) return null;
        if (el.classList?.contains('abox-3d-host')) return el;
        return el.querySelector?.('.abox-3d-host');
    }

    function defaultSize(host) {
        if (host.classList.contains('abox-3d-host--sm')) return 'sm';
        if (host.classList.contains('abox-3d-host--head')) return 'head';
        return 'fab';
    }

    function getInstance(wrapper) {
        const host = parseHost(wrapper);
        return host ? instances.get(host) : null;
    }

    function disposeHost(wrapper) {
        const host = parseHost(wrapper);
        if (!host) return;
        const inst = instances.get(host);
        if (inst) {
            inst.dispose();
            instances.delete(host);
        }
    }

    function mountHost(host) {
        if (!host || !getThree()) return null;
        const existing = instances.get(host);
        if (existing && !existing._dead) return existing;
        if (existing) instances.delete(host);
        try {
            const inst = new BuddyRenderer(host, defaultSize(host));
            inst.setMood(host.dataset.mood || 'idle');
            instances.set(host, inst);
            host.classList.remove('abox-3d-failed');
            return inst;
        } catch (err) {
            console.warn('[AUDIOBoxx] 3D buddy mount failed:', err);
            host.classList.add('abox-3d-failed');
            return null;
        }
    }

    global.AboxBuddy3d = {
        ready: false,
        failed: false,
        init() {
            if (getThree()) { this.ready = true; return Promise.resolve(true); }
            this.failed = true;
            return Promise.resolve(false);
        },
        dispose(wrapper) { disposeHost(wrapper); },
        mount(wrapper) { return mountHost(parseHost(wrapper)); },
        mountWhenReady(wrapper) {
            return this.init().then((ok) => {
                if (!ok) return null;
                return new Promise((resolve) => {
                    const attempt = (n) => {
                        const inst = this.mount(wrapper);
                        if (inst) { resolve(inst); return; }
                        if (n < 4) requestAnimationFrame(() => attempt(n + 1));
                        else resolve(null);
                    };
                    requestAnimationFrame(() => attempt(0));
                });
            });
        },
        setMood(wrapper, mood) {
            const host = parseHost(wrapper);
            if (!host) return;
            host.dataset.mood = mood || 'idle';
            const inst = instances.get(host);
            if (inst) inst.setMood(mood);
            else this.mountWhenReady(wrapper);
        },
        playStunt(wrapper, kind) {
            const inst = getInstance(wrapper);
            if (inst) inst.playStunt(kind);
        },
        mountAll(root) {
            const hosts = (root || document).querySelectorAll('.abox-3d-host');
            return this.init().then((ok) => {
                if (!ok) return false;
                hosts.forEach((h) => mountHost(h));
                window.setTimeout(() => hosts.forEach((h) => mountHost(h)), 400);
                return true;
            });
        }
    };

    if (getThree()) global.AboxBuddy3d.ready = true;
})(window);
