import { ProductionRuntimeRenderer, createContactShadowPass, createProductionEnvironmentLightingResources, createProductionPbrHdrPipelineFromRadiance, resolveProductionRuntimeRendererBackend, summarizeProductionProductionProof } from "../../rendering/index.js";
import { createGLTFSceneAnimationRuntime, createProductionGLTFRenderMetadata, loadProductionGLTFRenderPipeline } from "../../assets/index.js";
import { AnimationAction, AnimationLayer, AnimationMixer } from "../../animation/index.js";
import { PhysicsWorld, Shape } from "../../physics/index.js";
import { Geometry, Material, PBRMaterial, TextureBinding, computePerspectiveCameraFrame, createDefaultShaderLibrary } from "../../rendering/index.js";
import { DirectionalLight, composeMat4 } from "../../scene/index.js";
export * as productionRendering from "../../rendering/index.js";
export * as productionWorkflows from "../../workflows/index.js";
export const productionAssets = {
    createProductionGLTFRenderMetadata,
    loadProductionGLTFRenderPipeline
};
export const AURA3D_ENGINE_PRODUCTION_PRODUCT_SURFACE = "a3d-renderer-production-runtime-sdk";
export const A3D_THREEJS_EXAMPLE_PARITY_TARGETS = {
    keyframes: "webgl_animation_keyframes",
    skinningBlending: "webgl_animation_skinning_blending",
    additiveBlending: "webgl_animation_skinning_additive_blending",
    skinningIk: "webgl_animation_skinning_ik",
    skinningMorph: "webgl_animation_skinning_morph",
    multipleAnimations: "webgl_animation_multiple",
    walkCycle: "webgl_animation_walk",
    decals: "webgl_decals",
    parallaxBarrier: "webgl_effects_parallaxbarrier",
    stereo: "webgl_effects_stereo"
};
export class A3DRenderer {
    renderer;
    viewport;
    backend;
    backendSelection;
    constructor(renderer, viewport) {
        this.renderer = renderer;
        this.viewport = viewport;
        this.backend = renderer.backend;
        this.backendSelection = renderer.backendSelection;
    }
    static async create(options) {
        const backendSelection = resolveProductionRuntimeRendererBackend(options);
        return new A3DRenderer(await ProductionRuntimeRenderer.create({ ...options, backend: backendSelection.requestedBackend }), { width: options.width, height: options.height });
    }
    captureProof(input) {
        if (this.backend === "webgpu") {
            throw new Error("A3DRenderer Production WebGPU proof capture uses captureProofAsync() so native texture-to-buffer readback can be awaited.");
        }
        const viewport = input.viewport ?? this.defaultViewport();
        const rendererInput = input.scene.createRendererInput({
            viewport,
            environment: input.environment,
            environmentLighting: input.environmentLighting,
            renderItems: input.renderItems,
            collectedLights: input.collectedLights,
            shadow: input.shadow,
            postprocess: input.postprocess
        });
        const proof = this.renderer.captureProof({
            source: rendererInput.source,
            camera: input.camera ?? rendererInput.camera,
            metadata: this.metadataForRender(input.scene, input.environment)
        });
        return { proof, summary: summarizeProductionProductionProof(proof) };
    }
    render(input) {
        return this.captureProof(input);
    }
    renderInteractiveFrame(input) {
        if (this.backend === "webgpu") {
            throw new Error("A3DRenderer Production WebGPU interactive rendering uses renderInteractiveFrameAsync() so native render submission can be awaited.");
        }
        const viewport = input.viewport ?? this.defaultViewport();
        const rendererInput = input.scene.createRendererInput({
            viewport,
            environment: input.environment,
            environmentLighting: input.environmentLighting,
            renderItems: input.renderItems,
            collectedLights: input.collectedLights,
            shadow: input.shadow,
            postprocess: input.postprocess
        });
        const result = this.renderer.renderInteractiveFrame({
            source: rendererInput.source,
            camera: input.camera ?? rendererInput.camera,
            metadata: this.metadataForRender(input.scene, input.environment)
        });
        return {
            backend: result.backend,
            diagnostics: result.diagnostics,
            features: result.features,
            ...(result.timing ? { timing: result.timing } : {})
        };
    }
    renderFrame(input) {
        return this.renderInteractiveFrame(input);
    }
    async captureProofAsync(input) {
        const viewport = input.viewport ?? this.defaultViewport();
        const rendererInput = input.scene.createRendererInput({
            viewport,
            environment: input.environment,
            environmentLighting: input.environmentLighting,
            renderItems: input.renderItems,
            collectedLights: input.collectedLights,
            shadow: input.shadow,
            postprocess: input.postprocess
        });
        const proof = await this.renderer.captureProofAsync({
            source: rendererInput.source,
            camera: input.camera ?? rendererInput.camera,
            metadata: this.metadataForRender(input.scene, input.environment)
        });
        return { proof, summary: summarizeProductionProductionProof(proof) };
    }
    async renderAsync(input) {
        // ProductionRuntimeRenderer keeps renderImportedAssetAsync as a backwards-compatible alias for captureProofAsync.
        return this.captureProofAsync(input);
    }
    async renderInteractiveFrameAsync(input) {
        const viewport = input.viewport ?? this.defaultViewport();
        const rendererInput = input.scene.createRendererInput({
            viewport,
            environment: input.environment,
            environmentLighting: input.environmentLighting,
            renderItems: input.renderItems,
            collectedLights: input.collectedLights,
            shadow: input.shadow,
            postprocess: input.postprocess
        });
        const result = await this.renderer.renderInteractiveFrameAsync({
            source: rendererInput.source,
            camera: input.camera ?? rendererInput.camera,
            metadata: this.metadataForRender(input.scene, input.environment)
        });
        return {
            backend: result.backend,
            diagnostics: result.diagnostics,
            features: result.features,
            ...(result.timing ? { timing: result.timing } : {})
        };
    }
    async renderFrameAsync(input) {
        return this.renderInteractiveFrameAsync(input);
    }
    getDiagnostics() {
        return this.renderer.getDiagnostics();
    }
    dispose() {
        this.renderer.dispose();
    }
    defaultViewport() {
        return this.viewport;
    }
    metadataForRender(scene, environment) {
        return {
            assetId: scene.metadata.assetId,
            assetName: scene.metadata.assetName,
            assetUri: scene.metadata.assetUri,
            meshCount: scene.metadata.meshCount,
            primitiveCount: scene.metadata.primitiveCount,
            materialCount: scene.metadata.materialCount,
            textureCount: scene.metadata.textureCount,
            imageCount: scene.metadata.imageCount,
            animationCount: scene.metadata.animationCount,
            skinCount: scene.metadata.skinCount,
            morphTargetCount: scene.metadata.morphTargetCount,
            extensionsUsed: scene.metadata.extensionsUsed,
            ...(environment ? {
                environmentId: environment.id,
                hdrEnvironmentUri: environment.url
            } : {})
        };
    }
}
export function createDirectionalLight(options = {}) {
    const source = new DirectionalLight(options.name ?? "a3d-production-runtime-directional-light");
    const color = options.color ?? [1, 1, 1];
    source.color = [color[0], color[1], color[2]];
    source.intensity = clamp(options.intensity ?? 1, 0, 64);
    source.castsShadow = options.castsShadow ?? false;
    return collectedDirectionalLight(source, options.direction ?? [0, -1, 0], source.castsShadow);
}
export function createStudioLighting(options = {}) {
    const scale = clamp(options.intensityScale ?? 1, 0, 16);
    const shadows = options.shadows ?? true;
    switch (options.preset ?? "product") {
        case "inspection":
            return [
                createDirectionalLight({ name: "a3d-production-runtime-inspection-key", direction: [-0.35, -0.72, -0.46], color: [1, 0.98, 0.92], intensity: 2.1 * scale, castsShadow: shadows }),
                createDirectionalLight({ name: "a3d-production-runtime-inspection-fill", direction: [0.55, -0.48, -0.34], color: [0.62, 0.74, 1], intensity: 0.72 * scale }),
                createDirectionalLight({ name: "a3d-production-runtime-inspection-rim", direction: [0.14, -0.34, 0.93], color: [1, 0.82, 0.62], intensity: 1.16 * scale })
            ];
        case "softbox":
            return [
                createDirectionalLight({ name: "a3d-production-runtime-softbox-key", direction: [-0.2, -0.9, -0.32], color: [1, 0.97, 0.91], intensity: 1.75 * scale, castsShadow: shadows }),
                createDirectionalLight({ name: "a3d-production-runtime-softbox-fill", direction: [0.44, -0.52, -0.42], color: [0.74, 0.82, 1], intensity: 1.04 * scale })
            ];
        case "product":
        default:
            return [
                createDirectionalLight({ name: "a3d-production-runtime-product-key-shadow", direction: [-0.42, -0.82, -0.38], color: [1, 0.95, 0.86], intensity: 2.75 * scale, castsShadow: shadows }),
                createDirectionalLight({ name: "a3d-production-runtime-product-fill", direction: [0.62, -0.42, -0.34], color: [0.55, 0.68, 1], intensity: 0.48 * scale }),
                createDirectionalLight({ name: "a3d-production-runtime-product-rim", direction: [0.18, -0.34, 0.92], color: [1, 0.82, 0.55], intensity: 1.05 * scale })
            ];
    }
}
export function createGroundedStage(bounds, options = {}) {
    const labelPrefix = options.labelPrefix ?? "a3d-production-runtime-grounded-stage";
    const width = Math.max(3.8, (bounds.max[0] - bounds.min[0]) * 2.4);
    const height = Math.max(2.6, (bounds.max[1] - bounds.min[1]) * 2.35);
    const depth = Math.max(3.2, (bounds.max[2] - bounds.min[2]) * 3.2 + 1.35);
    const centerX = (bounds.min[0] + bounds.max[0]) / 2;
    const centerY = (bounds.min[1] + bounds.max[1]) / 2;
    const centerZ = (bounds.min[2] + bounds.max[2]) / 2;
    const floorY = bounds.min[1] - 0.05;
    const backZ = bounds.min[2] - depth * 0.42;
    const stageGeometry = Geometry.litCube(1);
    const floorMaterial = new PBRMaterial({
        name: `${labelPrefix}-floor-material`,
        baseColor: options.floorColor ?? [0.022, 0.024, 0.029, 1],
        metallic: clamp(options.floorMetallic ?? 0, 0, 1),
        roughness: clamp(options.floorRoughness ?? 0.46, 0.02, 1),
        environmentIntensity: 0.82
    });
    const backdropMaterial = new PBRMaterial({
        name: `${labelPrefix}-backdrop-material`,
        baseColor: options.backdropColor ?? [0.006, 0.008, 0.012, 1],
        metallic: 0,
        roughness: clamp(options.backdropRoughness ?? 0.58, 0.02, 1),
        environmentIntensity: 0.54
    });
    const contactShadow = options.contactShadows === false ? undefined : createContactShadowPass({
        bounds,
        floorY,
        labelPrefix: `${labelPrefix}-contact-shadow`,
        opacity: 1,
        lightDirection: options.shadowLightDirection ?? [-0.42, -0.82, -0.38],
        softness: 0.82
    });
    const groundingItems = [
        {
            label: `${labelPrefix}-floor`,
            geometry: stageGeometry,
            material: floorMaterial,
            modelMatrix: composeMat4([centerX, floorY, centerZ + depth * 0.12], [0, 0, 0, 1], [width, 0.035, depth])
        },
        ...(contactShadow?.renderItems ?? [])
    ];
    const backgroundItems = options.background === false ? [] : [
        {
            label: `${labelPrefix}-backdrop`,
            geometry: stageGeometry,
            material: backdropMaterial,
            modelMatrix: composeMat4([centerX, centerY + height * 0.9, backZ], [0, 0, 0, 1], [width * 1.35, height * 2.8, 0.05])
        }
    ];
    const diagnostics = {
        labelPrefix,
        floorY,
        floorItemCount: groundingItems.length,
        backgroundItemCount: backgroundItems.length,
        ...(contactShadow ? { contactShadow: contactShadow.diagnostics } : {})
    };
    return {
        groundingItems,
        backgroundItems,
        floorY,
        diagnostics,
        update(settings = {}) {
            const blur = clamp(settings.backgroundBlur ?? 0.08, 0, 1);
            const visible = settings.backgroundVisible === false ? 0 : 1;
            backdropMaterial.roughness = clamp((options.backdropRoughness ?? 0.52) + blur * 0.32, 0.02, 1);
            backdropMaterial.environmentIntensity = visible * (0.58 - blur * 0.18);
        },
        renderItems(renderOptions = {}) {
            return [
                ...(renderOptions.shadows === false ? [] : groundingItems),
                ...(renderOptions.backgroundVisible === false ? [] : backgroundItems)
            ];
        },
        dispose() {
            stageGeometry.dispose();
            contactShadow?.dispose();
        }
    };
}
export function createCameraFrame(options) {
    const preset = options.preset ?? "product-hero";
    const base = productViewerCameraPreset(preset);
    const targetOffset = productViewerTargetOffset(options.bounds, options.target ?? [0, 0, 0], preset);
    const framedBounds = offsetBounds(options.bounds, targetOffset);
    const yawRadians = base.yawRadians + (options.yawRadians ?? 0);
    const pitchRadians = clamp(base.pitchRadians + (options.pitchRadians ?? 0), -1.2, 1.2);
    const zoom = clamp(options.zoom ?? 1, 0.25, 4);
    const paddingRatio = clamp(options.paddingRatio ?? base.paddingRatio * zoom, 0.02, 1.2);
    const frame = computePerspectiveCameraFrame(framedBounds, options.viewport, {
        ...base,
        yawRadians,
        pitchRadians,
        paddingRatio,
        minDistance: base.minDistance * zoom
    });
    return {
        camera: {
            viewProjectionMatrix: frame.viewProjectionMatrix,
            viewMatrix: frame.viewMatrix,
            projectionMatrix: frame.projectionMatrix
        },
        diagnostics: {
            preset,
            yawRadians,
            pitchRadians,
            paddingRatio,
            cameraPosition: frame.cameraPosition,
            targetOffset,
            zoom
        }
    };
}
export function createProductionRenderOptions(options) {
    const extraRenderItems = options.extraRenderItems ? [...options.extraRenderItems] : [];
    const stageItems = options.stage?.renderItems({
        shadows: options.shadows !== false,
        backgroundVisible: options.backgroundVisible ?? true
    });
    const renderItems = [
        ...extraRenderItems,
        ...(stageItems ? [...stageItems] : [])
    ];
    return {
        scene: options.scene,
        ...(options.environment ? { environment: options.environment } : {}),
        ...(options.environmentLighting ? { environmentLighting: options.environmentLighting } : {}),
        ...(renderItems.length > 0 ? { renderItems } : {}),
        collectedLights: options.lights ?? createStudioLighting({ preset: "product", shadows: options.shadows !== false }),
        shadow: options.shadows ?? true,
        camera: options.camera ?? (options.cameraFrame ? createCameraFrame(options.cameraFrame).camera : undefined),
        viewport: options.viewport,
        ...(options.postprocess !== undefined ? { postprocess: options.postprocess } : {})
    };
}
export function createImportedAnimationRuntime(scene) {
    const runtime = createGLTFSceneAnimationRuntime({
        scene: scene.resources.scene,
        clips: scene.asset.animations,
        asset: scene.asset
    });
    return {
        scene,
        runtime,
        applyClip(name, time) {
            return runtime.applyClipByName(name, time);
        },
        blendClips(samples) {
            return runtime.applyClips(samples);
        },
        solveTwoBoneIK(options) {
            return runtime.solveImportedSkeletonTwoBoneIK(options);
        },
        snapshot() {
            return runtime.snapshot();
        }
    };
}
export function createAnimationController(options = {}) {
    const mixer = new AnimationMixer(options.target, {
        applyRootMotion: options.applyRootMotion,
        rootMotionTrack: options.rootMotionTrack,
        rootMotionScale: options.rootMotionScale
    });
    const clips = new Map();
    const actions = new Map();
    const layers = new Map();
    let updateCount = 0;
    let crossFadeCount = 0;
    let lastEvents = [];
    for (const clip of options.clips ?? [])
        clips.set(clip.name, clip);
    const controller = {
        mixer,
        registerClip(clip) {
            clips.set(clip.name, clip);
            return clip;
        },
        play(clipInput, actionOptions = {}) {
            const clip = typeof clipInput === "string" ? resolveAnimationClip(clips, clipInput) : controller.registerClip(clipInput);
            let action = actions.get(clip.name);
            if (!action) {
                action = mixer.play(clip);
                actions.set(clip.name, action);
            }
            else {
                action.play();
            }
            if (actionOptions.weight !== undefined)
                action.setWeight(actionOptions.weight);
            if (actionOptions.timeScale !== undefined)
                action.timeScale = actionOptions.timeScale;
            if (actionOptions.loop !== undefined)
                action.loopMode = actionOptions.loop;
            if (actionOptions.layer || actionOptions.additive || (actionOptions.mask && actionOptions.mask.length > 0)) {
                const layerName = actionOptions.layer ?? (actionOptions.additive ? "additive" : "masked");
                let layer = layers.get(layerName);
                if (!layer) {
                    layer = new AnimationLayer(layerName, {
                        additive: actionOptions.additive ?? false,
                        mask: actionOptions.mask ?? []
                    });
                    layers.set(layerName, layer);
                    mixer.addLayer(layer);
                }
                layer.add(action);
            }
            return action;
        },
        crossFade(fromInput, toInput, duration) {
            const from = typeof fromInput === "string" ? resolveAnimationAction(actions, fromInput) : fromInput;
            const to = toInput instanceof AnimationAction ? toInput : controller.play(toInput, { weight: 0 });
            mixer.crossFade(from, to, duration);
            actions.set(to.clip.name, to);
            crossFadeCount += 1;
        },
        update(deltaSeconds) {
            lastEvents = mixer.update(deltaSeconds);
            updateCount += 1;
            return lastEvents;
        },
        snapshot() {
            const mixerSnapshot = mixer.snapshot();
            return {
                mixer: mixerSnapshot,
                registeredClips: [...clips.keys()],
                updateCount,
                crossFadeCount,
                lastEventCount: lastEvents.length,
                parityTargets: A3D_THREEJS_EXAMPLE_PARITY_TARGETS,
                capabilities: {
                    keyframes: true,
                    crossFade: true,
                    additiveLayers: mixerSnapshot.layers.some((layer) => layer.additive),
                    layerMasks: mixerSnapshot.layers.some((layer) => layer.mask.length > 0),
                    rootMotion: options.applyRootMotion === true,
                    importedGltfAnimationRuntime: true,
                    importedSkinningPaletteRefresh: true,
                    importedMorphWeightAnimation: true
                }
            };
        },
        dispose() {
            mixer.dispose();
            clips.clear();
            actions.clear();
            layers.clear();
            lastEvents = [];
        }
    };
    return controller;
}
export function createPhysicsScene(options = {}) {
    const world = new PhysicsWorld(options);
    const scene = {
        world,
        createBody(bodyOptions = {}) {
            const body = world.createRigidBody({
                type: bodyOptions.type,
                position: bodyOptions.position,
                velocity: bodyOptions.velocity,
                mass: bodyOptions.mass,
                restitution: bodyOptions.restitution,
                friction: bodyOptions.friction
            });
            if (bodyOptions.shape) {
                world.createCollider(body, {
                    shape: resolvePhysicsShape(bodyOptions.shape),
                    sensor: bodyOptions.sensor
                });
            }
            return body;
        },
        createGroundPlane(constant = 0) {
            const body = world.createRigidBody({ type: "static" });
            world.createCollider(body, { shape: Shape.plane([0, 1, 0], constant) });
            return body;
        },
        addConstraint(constraintOptions) {
            return world.createConstraint({
                type: constraintOptions.type,
                bodyA: constraintOptions.bodyA,
                bodyB: constraintOptions.bodyB,
                localAnchorA: constraintOptions.localAnchorA,
                localAnchorB: constraintOptions.localAnchorB,
                restLength: constraintOptions.restLength,
                stiffness: constraintOptions.stiffness,
                axis: constraintOptions.axis
            });
        },
        step(stepOptions = {}) {
            const resolved = typeof stepOptions === "number" ? { dt: stepOptions, steps: 1 } : stepOptions;
            const steps = resolved.steps ?? 1;
            const dt = resolved.dt ?? world.fixedDelta;
            let events = [];
            for (let i = 0; i < steps; i += 1)
                events = world.step(dt);
            return events;
        },
        raycast(origin, direction, maxDistance) {
            return world.raycast(origin, direction, maxDistance === undefined ? {} : { maxDistance });
        },
        sphereCast(origin, radius, direction, maxDistance) {
            return world.sphereCast(origin, radius, direction, maxDistance === undefined ? {} : { maxDistance });
        },
        snapshot() {
            return {
                world: world.snapshot(),
                parityTargets: { walkCycle: A3D_THREEJS_EXAMPLE_PARITY_TARGETS.walkCycle },
                capabilities: {
                    dynamicRigidBodies: true,
                    staticColliders: true,
                    constraints: true,
                    contacts: true,
                    raycast: true,
                    sphereCast: true
                }
            };
        }
    };
    return scene;
}
export class A3DGltfScene {
    pipeline;
    constructor(pipeline) {
        this.pipeline = pipeline;
    }
    get asset() {
        return this.pipeline.asset;
    }
    get resources() {
        return this.pipeline.resources;
    }
    get metadata() {
        return this.pipeline.metadata;
    }
    createRendererInput(options) {
        const environmentLighting = options.environmentLighting ?? options.environment?.lighting.lighting;
        const input = this.pipeline.resources.toRendererInput(options.viewport, {
            qualityPreset: environmentLighting ? "hdr-studio-preview" : "studio-preview",
            cameraPolicy: "require",
            ...(environmentLighting ? { environmentLighting } : {}),
            ...(options.renderItems ? { renderItems: options.renderItems } : {}),
            ...(options.collectedLights ? { collectedLights: options.collectedLights } : {}),
            ...(options.shadow !== undefined ? { shadow: options.shadow } : {}),
            ...(options.postprocess !== undefined ? { postprocess: options.postprocess } : {})
        });
        return {
            source: input.source,
            camera: input.camera,
            bounds: input.bounds
        };
    }
    dispose() {
        this.pipeline.dispose();
    }
}
export async function loadGltfScene(input) {
    const options = typeof input === "string" ? { url: input } : input;
    const viewport = options.viewport ?? { width: 1024, height: 1024 };
    const assetId = options.assetId ?? assetIdFromUrl(options.url);
    const pipeline = await loadProductionGLTFRenderPipeline({
        url: options.url,
        assetId,
        assetName: options.assetName ?? assetId,
        width: viewport.width,
        height: viewport.height,
        ...(options.materialVariant !== undefined ? { materialVariant: options.materialVariant } : {}),
        ...(options.materialRenderStateOverrides ? { materialRenderStateOverrides: options.materialRenderStateOverrides } : {}),
        ...(options.sceneIndex !== undefined ? { sceneIndex: options.sceneIndex } : {}),
        ...(options.sceneName !== undefined ? { sceneName: options.sceneName } : {}),
        ...(options.rendererInput ? { rendererInput: options.rendererInput } : {})
    });
    return new A3DGltfScene(pipeline);
}
export class A3DHdrEnvironment {
    id;
    label;
    url;
    pipeline;
    lighting;
    constructor(options) {
        this.id = options.id;
        this.label = options.label;
        this.url = options.url;
        this.pipeline = options.pipeline;
        this.lighting = options.lighting;
    }
    get environmentLighting() {
        return this.lighting.lighting;
    }
    dispose() {
        this.lighting.dispose();
    }
}
export async function loadHdrEnvironment(input) {
    const options = typeof input === "string" ? { url: input } : input;
    const data = options.data ?? await fetchArrayBuffer(options.url);
    const id = options.id ?? assetIdFromUrl(options.url);
    const pipeline = createProductionPbrHdrPipelineFromRadiance(data, {
        id,
        label: options.label ?? id,
        intensity: options.intensity ?? 1,
        backgroundIntensity: options.backgroundIntensity ?? 0.85,
        rotation: options.rotation ?? 0,
        ...(options.toneMapping ? { toneMapping: options.toneMapping } : {})
    });
    return new A3DHdrEnvironment({
        id,
        label: options.label ?? id,
        url: options.url,
        pipeline,
        lighting: createProductionEnvironmentLightingResources(pipeline)
    });
}
export function createOrbitControls(options = {}) {
    const initial = {
        target: options.target ?? [0, 0, 0],
        position: options.position ?? [0, 0, 5],
        rotation: [0, 0],
        zoom: 1,
        enabled: options.enabled ?? true
    };
    let state = cloneOrbitSnapshot(initial);
    const flags = {
        enablePan: options.enablePan ?? true,
        enableZoom: options.enableZoom ?? true,
        enableRotate: options.enableRotate ?? true
    };
    return {
        rotate(deltaX, deltaY) {
            if (state.enabled && flags.enableRotate) {
                state = { ...state, rotation: [state.rotation[0] + deltaY, state.rotation[1] + deltaX] };
            }
            return cloneOrbitSnapshot(state);
        },
        pan(deltaX, deltaY) {
            if (state.enabled && flags.enablePan) {
                state = { ...state, target: [state.target[0] + deltaX, state.target[1] + deltaY, state.target[2]] };
            }
            return cloneOrbitSnapshot(state);
        },
        dolly(scale) {
            if (state.enabled && flags.enableZoom) {
                state = { ...state, zoom: state.zoom * scale, position: [state.position[0], state.position[1], state.position[2] * scale] };
            }
            return cloneOrbitSnapshot(state);
        },
        reset() {
            state = cloneOrbitSnapshot(initial);
            return cloneOrbitSnapshot(state);
        },
        snapshot() {
            return cloneOrbitSnapshot(state);
        }
    };
}
export function createFirstPersonControls(options = {}) {
    const initial = createNavigationSnapshot(options);
    let state = cloneNavigationSnapshot(initial);
    return {
        moveForward(distance) {
            if (state.enabled)
                state = { ...state, position: [state.position[0], state.position[1], state.position[2] - distance * state.movementSpeed] };
            return cloneNavigationSnapshot(state);
        },
        strafe(distance) {
            if (state.enabled)
                state = { ...state, position: [state.position[0] + distance * state.movementSpeed, state.position[1], state.position[2]] };
            return cloneNavigationSnapshot(state);
        },
        look(deltaX, deltaY) {
            if (state.enabled)
                state = { ...state, rotation: [state.rotation[0] + deltaY, state.rotation[1] + deltaX, state.rotation[2]] };
            return cloneNavigationSnapshot(state);
        },
        reset() {
            state = cloneNavigationSnapshot(initial);
            return cloneNavigationSnapshot(state);
        },
        snapshot() {
            return cloneNavigationSnapshot(state);
        }
    };
}
export function createMapControls(options = {}) {
    const initial = {
        target: options.target ?? [0, 0, 0],
        position: options.position ?? [0, 0, 5],
        rotation: [0, 0],
        zoom: 1,
        enabled: options.enabled ?? true
    };
    let state = cloneOrbitSnapshot(initial);
    const flags = {
        enablePan: options.enablePan ?? true,
        enableZoom: options.enableZoom ?? true,
        enableRotate: options.enableRotate ?? true
    };
    return {
        rotate(deltaX, deltaY) {
            if (state.enabled && flags.enableRotate) {
                state = { ...state, rotation: [state.rotation[0] + deltaY, state.rotation[1] + deltaX] };
            }
            return cloneOrbitSnapshot(state);
        },
        pan(deltaX, deltaY) {
            if (state.enabled && flags.enablePan) {
                state = { ...state, target: [state.target[0] + deltaX, state.target[1] + deltaY, state.target[2]] };
            }
            return cloneOrbitSnapshot(state);
        },
        dolly(scale) {
            if (state.enabled && flags.enableZoom) {
                state = { ...state, zoom: state.zoom * scale, position: [state.position[0], state.position[1], state.position[2] * scale] };
            }
            return cloneOrbitSnapshot(state);
        },
        truck(deltaX, deltaZ) {
            if (state.enabled && flags.enablePan) {
                state = { ...state, target: [state.target[0] + deltaX, state.target[1], state.target[2] + deltaZ] };
            }
            return cloneOrbitSnapshot(state);
        },
        reset() {
            state = cloneOrbitSnapshot(initial);
            return cloneOrbitSnapshot(state);
        },
        snapshot() {
            return cloneOrbitSnapshot(state);
        }
    };
}
export function createTrackballControls(options = {}) {
    const orbit = createOrbitControls(options);
    let rollRadians = 0;
    return {
        rotate(deltaX, deltaY) {
            return orbit.rotate(deltaX, deltaY);
        },
        pan(deltaX, deltaY) {
            return orbit.pan(deltaX, deltaY);
        },
        dolly(scale) {
            return orbit.dolly(scale);
        },
        roll(delta) {
            rollRadians += delta;
            const current = orbit.snapshot();
            return {
                ...current,
                rotation: [current.rotation[0], current.rotation[1] + rollRadians]
            };
        },
        reset() {
            rollRadians = 0;
            return orbit.reset();
        },
        snapshot() {
            const current = orbit.snapshot();
            return {
                ...current,
                rotation: [current.rotation[0], current.rotation[1] + rollRadians]
            };
        }
    };
}
export function createPointerLockControls(options = {}) {
    const initial = createNavigationSnapshot({ ...options, locked: options.locked ?? false });
    let state = cloneNavigationSnapshot(initial);
    return {
        lock() {
            state = { ...state, locked: true };
            return cloneNavigationSnapshot(state);
        },
        unlock() {
            state = { ...state, locked: false };
            return cloneNavigationSnapshot(state);
        },
        look(deltaX, deltaY) {
            if (state.enabled && state.locked) {
                state = { ...state, rotation: [state.rotation[0] + deltaY, state.rotation[1] + deltaX, state.rotation[2]] };
            }
            return cloneNavigationSnapshot(state);
        },
        reset() {
            state = cloneNavigationSnapshot(initial);
            return cloneNavigationSnapshot(state);
        },
        snapshot() {
            return cloneNavigationSnapshot(state);
        }
    };
}
export async function createProductViewer(options) {
    const width = options.width ?? options.canvas.width;
    const height = options.height ?? options.canvas.height;
    const renderer = await A3DRenderer.create({
        backend: options.backend,
        canvas: options.canvas,
        width,
        height,
        preserveDrawingBuffer: true,
        clearColor: [0.012, 0.015, 0.02, 1],
        shaderLibrary: createProductViewerShaderLibrary()
    });
    const controls = createOrbitControls({
        enabled: options.camera?.orbit ?? true
    });
    const viewport = { width, height };
    let currentEnvironment = options.environment;
    let settings = {
        exposure: options.postprocess?.exposure ?? 0.9,
        iblIntensity: currentEnvironment.environmentLighting.environmentMapIntensity ?? currentEnvironment.pipeline.intensity,
        specularIntensity: Math.max(currentEnvironment.environmentLighting.environmentMapSpecularIntensity ?? 0, currentEnvironment.pipeline.intensity * 1.08),
        environmentRotation: currentEnvironment.environmentLighting.environmentMapRotation ?? currentEnvironment.pipeline.rotation,
        backgroundVisible: true,
        backgroundBlur: 0.025,
        shadows: options.lighting?.shadows ?? true,
        toneMapping: options.postprocess?.toneMapping ?? "filmic",
        bloom: options.postprocess?.bloom !== false,
        ssao: options.postprocess?.ssao !== false,
        fxaa: options.postprocess?.fxaa !== false,
        colorGrade: options.postprocess?.colorGrade !== false
    };
    let lastCamera;
    const stage = createGroundedStage(options.asset.resources.bounds, {
        labelPrefix: "a3d-production-product-viewer",
        shadowLightDirection: [-0.42, -0.82, -0.38]
    });
    const skyboxGeometry = Geometry.uvSphere(1, 128, 64);
    const skyboxMaterial = new A3DVisibleHdrSkyboxMaterial({
        texture: currentEnvironment.environmentLighting.environmentMapTexture ?? new TextureBinding({ name: "u_environmentMapTexture", required: false }),
        rotation: settings.environmentRotation,
        exposure: settings.exposure * currentEnvironment.pipeline.backgroundIntensity
    });
    return {
        renderer,
        asset: options.asset,
        get environment() {
            return currentEnvironment;
        },
        controls,
        render() {
            const controlsState = controls.snapshot();
            const camera = createCameraFrame({
                bounds: options.asset.resources.bounds,
                viewport,
                target: controlsState.target,
                yawRadians: controlsState.rotation[1],
                pitchRadians: controlsState.rotation[0],
                zoom: controlsState.zoom,
                preset: options.camera?.preset ?? "product-hero"
            });
            lastCamera = camera.diagnostics;
            stage.update({
                backgroundBlur: settings.backgroundBlur,
                backgroundVisible: settings.backgroundVisible
            });
            const skyboxItems = settings.backgroundVisible
                ? [createProductViewerSkyboxItem(skyboxGeometry, skyboxMaterial, camera.diagnostics.cameraPosition, options.asset.resources.bounds, currentEnvironment, settings)]
                : [];
            return renderer.render(createProductionRenderOptions({
                scene: options.asset,
                ...(options.lighting?.ibl === false ? {} : {
                    environment: currentEnvironment,
                    environmentLighting: createProductViewerEnvironmentLighting(currentEnvironment, settings)
                }),
                stage,
                backgroundVisible: false,
                extraRenderItems: skyboxItems,
                lights: createStudioLighting({ preset: "product", shadows: settings.shadows }),
                shadows: createProductViewerShadowOptions(settings),
                camera: camera.camera,
                viewport,
                postprocess: createProductViewerPostprocess(settings)
            }));
        },
        async renderAsync() {
            const controlsState = controls.snapshot();
            const camera = createCameraFrame({
                bounds: options.asset.resources.bounds,
                viewport,
                target: controlsState.target,
                yawRadians: controlsState.rotation[1],
                pitchRadians: controlsState.rotation[0],
                zoom: controlsState.zoom,
                preset: options.camera?.preset ?? "product-hero"
            });
            lastCamera = camera.diagnostics;
            stage.update({
                backgroundBlur: settings.backgroundBlur,
                backgroundVisible: settings.backgroundVisible
            });
            const skyboxItems = settings.backgroundVisible
                ? [createProductViewerSkyboxItem(skyboxGeometry, skyboxMaterial, camera.diagnostics.cameraPosition, options.asset.resources.bounds, currentEnvironment, settings)]
                : [];
            return renderer.renderAsync(createProductionRenderOptions({
                scene: options.asset,
                ...(options.lighting?.ibl === false ? {} : {
                    environment: currentEnvironment,
                    environmentLighting: createProductViewerEnvironmentLighting(currentEnvironment, settings)
                }),
                stage,
                backgroundVisible: false,
                extraRenderItems: skyboxItems,
                lights: createStudioLighting({ preset: "product", shadows: settings.shadows }),
                shadows: createProductViewerShadowOptions(settings),
                camera: camera.camera,
                viewport,
                postprocess: createProductViewerPostprocess(settings)
            }));
        },
        setSettings(next) {
            settings = sanitizeProductViewerSettings({ ...settings, ...next });
            return settings;
        },
        setEnvironment(environment) {
            currentEnvironment = environment;
            settings = sanitizeProductViewerSettings({
                ...settings,
                iblIntensity: environment.environmentLighting.environmentMapIntensity ?? environment.pipeline.intensity,
                specularIntensity: Math.max(environment.environmentLighting.environmentMapSpecularIntensity ?? 0, environment.pipeline.intensity * 1.08),
                environmentRotation: environment.environmentLighting.environmentMapRotation ?? environment.pipeline.rotation
            });
            return currentEnvironment;
        },
        getSettings() {
            return { ...settings };
        },
        captureScreenshot(type = "image/png", quality) {
            if ("toDataURL" in options.canvas && typeof options.canvas.toDataURL === "function") {
                return options.canvas.toDataURL(type, quality);
            }
            return undefined;
        },
        diagnostics() {
            return {
                asset: options.asset.metadata,
                environment: {
                    id: currentEnvironment.id,
                    url: currentEnvironment.url,
                    realRadianceHdr: currentEnvironment.pipeline.diagnostics.realRadianceHdr,
                    specularPrefilter: currentEnvironment.pipeline.diagnostics.specularPrefilter,
                    cubemapPMREM: currentEnvironment.pipeline.diagnostics.cubemapPMREM,
                    cubemapPMREMShaderSampling: currentEnvironment.pipeline.diagnostics.cubemapPMREMShaderSampling,
                    cubemapFaceSize: currentEnvironment.pipeline.diagnostics.cubemapFaceSize,
                    cubemapMipCount: currentEnvironment.pipeline.diagnostics.cubemapMipCount,
                    brdfLut: currentEnvironment.pipeline.diagnostics.brdfLut
                },
                controls: controls.snapshot(),
                settings,
                ...(lastCamera ? { camera: lastCamera } : {}),
                stage: {
                    enabled: settings.shadows,
                    itemCount: settings.shadows ? stage.groundingItems.length : 0,
                    floorY: stage.floorY,
                    directionalShadowMap: settings.shadows,
                    softFiltering: settings.shadows ? "pcf-16" : "off",
                    depthAwareAmbientOcclusion: settings.ssao,
                    contactShadow: stage.diagnostics.contactShadow
                },
                background: {
                    enabled: settings.backgroundVisible,
                    itemCount: settings.backgroundVisible ? 1 : 0,
                    mode: "visible-hdr-studio-skybox",
                    blur: settings.backgroundBlur,
                    environmentRotation: settings.environmentRotation
                }
            };
        },
        dispose() {
            renderer.dispose();
            skyboxGeometry.dispose();
            stage.dispose();
        }
    };
}
function createProductViewerPostprocess(options) {
    return {
        targetFormat: "rgba16f",
        toneMapping: {
            operator: options.toneMapping,
            exposure: options.exposure,
            whitePoint: 1.25,
            inputColorSpace: "linear",
            outputColorSpace: "srgb"
        },
        bloom: options.bloom ? { threshold: 0.86, intensity: 0.13, radius: 3 } : false,
        ssao: options.ssao ? { radius: 4, intensity: 0.68, bias: 0.008 } : false,
        fxaa: options.fxaa ? { edgeThreshold: 0.09, subpixelBlend: 0.24 } : false,
        colorGrade: options.colorGrade ? {
            contrast: 1.16,
            saturation: 1.1,
            vibrance: 0.2,
            vignette: 0.16,
            sharpening: 0.86
        } : false
    };
}
const A3D_VISIBLE_HDR_SKYBOX_SHADER_NAME = "a3d-production-runtime/visible-hdr-studio-skybox";
const A3D_VISIBLE_HDR_SKYBOX_SHADER_MARKER = "@aura3d-production-runtime-shader:visible-hdr-studio-skybox";
function createProductViewerShaderLibrary() {
    const library = createDefaultShaderLibrary();
    library.register({
        name: A3D_VISIBLE_HDR_SKYBOX_SHADER_NAME,
        marker: A3D_VISIBLE_HDR_SKYBOX_SHADER_MARKER,
        vertex: `#version 300 es
// ${A3D_VISIBLE_HDR_SKYBOX_SHADER_MARKER}
precision highp float;
layout(location = 0) in vec3 a_position;
uniform mat4 u_modelViewProjection;
out vec3 v_direction;
void main() {
  v_direction = normalize(a_position);
  gl_Position = u_modelViewProjection * vec4(a_position, 1.0);
}
`,
        fragment: `#version 300 es
// ${A3D_VISIBLE_HDR_SKYBOX_SHADER_MARKER}
precision highp float;
uniform sampler2D u_environmentMapTexture;
uniform float u_environmentSkyboxRotation;
uniform float u_environmentSkyboxExposure;
uniform float u_environmentSkyboxBlur;
in vec3 v_direction;
out vec4 outColor;

const float PI = 3.141592653589793;

vec2 a3dProductViewerEquirectUv(vec3 direction, float rotation) {
  vec3 d = normalize(direction);
  float u = atan(d.z, d.x) / (2.0 * PI) + 0.5 + rotation;
  float v = acos(clamp(d.y, -1.0, 1.0)) / PI;
  return vec2(fract(u), clamp(v, 0.0, 1.0));
}

vec3 a3dProductViewerFilmic(vec3 color) {
  color = max(color, vec3(0.0));
  return clamp((color * (2.51 * color + 0.03)) / (color * (2.43 * color + 0.59) + 0.14), vec3(0.0), vec3(1.0));
}

vec3 a3dProductViewerLinearToSrgb(vec3 color) {
  vec3 lo = color * 12.92;
  vec3 hi = 1.055 * pow(max(color, vec3(0.0)), vec3(1.0 / 2.4)) - 0.055;
  return mix(lo, hi, step(vec3(0.0031308), color));
}

void main() {
  vec2 uv = a3dProductViewerEquirectUv(v_direction, u_environmentSkyboxRotation);
  float lod = clamp(u_environmentSkyboxBlur * 6.0, 0.0, 8.0);
  vec3 hdr = textureLod(u_environmentMapTexture, uv, lod).rgb * u_environmentSkyboxExposure;
  vec3 mapped = a3dProductViewerLinearToSrgb(a3dProductViewerFilmic(hdr));
  outColor = vec4(mapped, 1.0);
}
`
    });
    return library;
}
class A3DVisibleHdrSkyboxMaterial extends Material {
    constructor(options) {
        super({
            name: "a3d-production-runtime-visible-hdr-studio-skybox",
            shaderKey: A3D_VISIBLE_HDR_SKYBOX_SHADER_NAME,
            renderState: {
                depthTest: false,
                depthWrite: false,
                cullMode: "front"
            },
            parameters: {
                u_modelViewProjection: identityMatrix(),
                u_environmentMapTexture: options.texture,
                u_environmentSkyboxRotation: options.rotation,
                u_environmentSkyboxExposure: options.exposure,
                u_environmentSkyboxBlur: 0.025
            },
            requiredAttributes: ["a_position"],
            uniformSchema: [
                { name: "u_modelViewProjection", kind: "mat4" },
                { name: "u_environmentMapTexture", kind: "texture2d", required: false },
                { name: "u_environmentSkyboxRotation", kind: "float" },
                { name: "u_environmentSkyboxExposure", kind: "float" },
                { name: "u_environmentSkyboxBlur", kind: "float" }
            ]
        });
    }
}
function createProductViewerSkyboxItem(geometry, material, cameraPosition, bounds, environment, settings) {
    material.setParameter("u_environmentMapTexture", environment.environmentLighting.environmentMapTexture ?? new TextureBinding({ name: "u_environmentMapTexture", required: false }));
    material.setParameter("u_environmentSkyboxRotation", settings.environmentRotation);
    material.setParameter("u_environmentSkyboxExposure", settings.exposure * environment.pipeline.backgroundIntensity);
    material.setParameter("u_environmentSkyboxBlur", settings.backgroundBlur);
    const extent = Math.max(1, bounds.max[0] - bounds.min[0], bounds.max[1] - bounds.min[1], bounds.max[2] - bounds.min[2]);
    const radius = extent * 80;
    return {
        geometry,
        material,
        label: "a3d-production-runtime-visible-hdr-studio-skybox",
        includeInAutoFrame: false,
        modelMatrix: composeMat4([cameraPosition[0], cameraPosition[1], cameraPosition[2]], [0, 0, 0, 1], [radius, radius, radius])
    };
}
function createProductViewerShadowOptions(settings) {
    if (!settings.shadows)
        return false;
    return {
        enabled: true,
        size: 2048,
        strength: 0.62,
        bias: 0.0018,
        slopeBias: 1,
        filter: "pcf",
        pcfRadius: 1.65,
        pcfSamples: 16,
        pcfDistribution: "poisson",
        label: "a3d-production-product-viewer-directional-shadow"
    };
}
function collectedDirectionalLight(source, direction, castsShadow) {
    return {
        kind: "directional",
        color: source.color,
        intensity: source.intensity,
        position: [0, 0, 0],
        direction: normalizeTuple3(direction),
        range: 0,
        spotAngle: 0,
        penumbra: 0,
        castsShadow,
        layerMask: source.layerMask,
        source
    };
}
function createProductViewerEnvironmentLighting(environment, settings) {
    const lighting = environment.environmentLighting;
    return {
        ...lighting,
        proceduralMap: settings.backgroundVisible ? lighting.proceduralMap : undefined,
        environmentMapIntensity: settings.iblIntensity,
        environmentMapSpecularIntensity: settings.specularIntensity,
        environmentMapRotation: settings.environmentRotation
    };
}
function productViewerCameraPreset(preset) {
    switch (preset) {
        case "asset-inspection":
            return { fovYRadians: Math.PI / 3, paddingRatio: 0.2, minDistance: 0.65, nearPadding: 0.12, farPadding: 2.4, yawRadians: -0.28, pitchRadians: -0.1 };
        case "material-inspection":
            return { fovYRadians: 0.82, paddingRatio: 0.12, minDistance: 0.55, nearPadding: 0.1, farPadding: 2.2, yawRadians: -0.34, pitchRadians: -0.08 };
        case "product-hero":
        default:
            return { fovYRadians: 0.45, paddingRatio: 0.024, minDistance: 0.58, nearPadding: 0.1, farPadding: 3.3, yawRadians: -0.34, pitchRadians: -0.08 };
    }
}
function productViewerTargetOffset(bounds, target, preset) {
    const extent = Math.max(0.001, bounds.max[0] - bounds.min[0], bounds.max[1] - bounds.min[1], bounds.max[2] - bounds.min[2]);
    const presetVerticalOffset = preset === "product-hero" ? -0.1 : 0;
    const presetHorizontalOffset = preset === "product-hero" ? 0.16 : 0;
    return [
        target[0] * extent * 0.05 + presetHorizontalOffset * extent,
        target[1] * extent * 0.05 + presetVerticalOffset * extent,
        target[2] * extent * 0.05
    ];
}
function offsetBounds(bounds, offset) {
    return {
        min: [bounds.min[0] + offset[0], bounds.min[1] + offset[1], bounds.min[2] + offset[2]],
        max: [bounds.max[0] + offset[0], bounds.max[1] + offset[1], bounds.max[2] + offset[2]]
    };
}
function sanitizeProductViewerSettings(settings) {
    return {
        ...settings,
        exposure: clamp(settings.exposure, 0, 4),
        iblIntensity: clamp(settings.iblIntensity, 0, 4),
        specularIntensity: clamp(settings.specularIntensity, 0, 4),
        environmentRotation: clamp(settings.environmentRotation, -1, 1),
        backgroundBlur: clamp(settings.backgroundBlur, 0, 1)
    };
}
function resolveAnimationClip(clips, name) {
    const clip = clips.get(name);
    if (!clip) {
        throw new Error(`A3D animation clip "${name}" is not registered.`);
    }
    return clip;
}
function resolveAnimationAction(actions, name) {
    const action = actions.get(name);
    if (!action) {
        throw new Error(`A3D animation action "${name}" has not been played.`);
    }
    return action;
}
function resolvePhysicsShape(shape) {
    if (shape.kind === "box") {
        const halfExtents = shape.halfExtents ?? [0.5, 0.5, 0.5];
        return Shape.box(halfExtents[0], halfExtents[1], halfExtents[2]);
    }
    if (shape.kind === "sphere") {
        return Shape.sphere(shape.radius ?? 0.5);
    }
    return Shape.capsule(shape.radius ?? 0.35, shape.halfHeight ?? 0.75);
}
function clamp(value, min, max) {
    return Math.min(max, Math.max(min, Number.isFinite(value) ? value : min));
}
function identityMatrix() {
    return new Float32Array([
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
    ]);
}
function normalizeTuple3(value) {
    const length = Math.hypot(value[0], value[1], value[2]);
    if (!Number.isFinite(length) || length <= 0)
        return [0, -1, 0];
    return [value[0] / length, value[1] / length, value[2] / length];
}
async function fetchArrayBuffer(url) {
    if (typeof fetch !== "function") {
        throw new Error("loadHdrEnvironment(url) requires fetch; pass { url, data } when loading HDR data outside the browser.");
    }
    const response = await fetch(url);
    if (!response.ok) {
        throw new Error(`Failed to fetch HDR environment ${url}: ${response.status}`);
    }
    return response.arrayBuffer();
}
function assetIdFromUrl(url) {
    const clean = url.split(/[?#]/)[0] ?? url;
    const basename = clean.split("/").filter(Boolean).pop() ?? "asset";
    return basename.replace(/\.[^.]+$/, "").replace(/[^a-zA-Z0-9_-]+/g, "-").toLowerCase();
}
function cloneOrbitSnapshot(snapshot) {
    return {
        target: [...snapshot.target],
        position: [...snapshot.position],
        rotation: [...snapshot.rotation],
        zoom: snapshot.zoom,
        enabled: snapshot.enabled
    };
}
function createNavigationSnapshot(options) {
    return {
        position: options.position ?? [0, 0, 0],
        rotation: options.rotation ?? [0, 0, 0],
        movementSpeed: options.movementSpeed ?? 1,
        enabled: options.enabled ?? true,
        locked: options.locked ?? false
    };
}
function cloneNavigationSnapshot(snapshot) {
    return {
        position: [...snapshot.position],
        rotation: [...snapshot.rotation],
        movementSpeed: snapshot.movementSpeed,
        enabled: snapshot.enabled,
        locked: snapshot.locked
    };
}
//# sourceMappingURL=index.js.map