export class WebXRSessionController {
    mode;
    requiredFeatures;
    optionalFeatures;
    referenceSpaceType;
    xr;
    session = null;
    referenceSpace = null;
    constructor(options = {}) {
        this.xr = options.xr ?? readNavigatorXR();
        this.mode = options.mode ?? "immersive-vr";
        this.requiredFeatures = options.requiredFeatures ?? [];
        this.optionalFeatures = options.optionalFeatures ?? [];
        this.referenceSpaceType = options.referenceSpace ?? (this.mode === "immersive-ar" ? "viewer" : "local-floor");
    }
    get active() {
        return this.session !== null;
    }
    async isSupported() {
        if (!this.xr)
            return false;
        if (!this.xr.isSessionSupported)
            return true;
        return this.xr.isSessionSupported(this.mode);
    }
    async start() {
        if (!this.xr) {
            return {
                mode: this.mode,
                referenceSpace: this.referenceSpaceType,
                supported: false,
                started: false
            };
        }
        const supported = await this.isSupported();
        if (!supported) {
            return {
                mode: this.mode,
                referenceSpace: this.referenceSpaceType,
                supported: false,
                started: false
            };
        }
        this.session = await this.xr.requestSession(this.mode, {
            requiredFeatures: this.requiredFeatures,
            optionalFeatures: this.optionalFeatures
        });
        this.referenceSpace = await this.session.requestReferenceSpace?.(this.referenceSpaceType) ?? { type: this.referenceSpaceType };
        return {
            mode: this.mode,
            referenceSpace: this.referenceSpace?.type ?? this.referenceSpaceType,
            supported: true,
            started: true
        };
    }
    sampleFrame(frame, hitTestSource) {
        const referenceSpace = this.referenceSpace ?? { type: this.referenceSpaceType };
        const controllers = Array.from(this.session?.inputSources ?? []).map((source) => sampleInputSource(source, frame, referenceSpace));
        const hitTests = frame?.getHitTestResults && hitTestSource
            ? frame.getHitTestResults(hitTestSource).map(sampleHitTest)
            : [];
        return {
            mode: this.mode,
            referenceSpace: this.referenceSpace?.type ?? this.referenceSpaceType,
            active: this.active,
            controllerCount: controllers.length,
            controllers,
            hitTestCount: hitTests.length,
            hitTests
        };
    }
    async end() {
        const session = this.session;
        this.session = null;
        this.referenceSpace = null;
        await session?.end?.();
    }
    async pulseHaptics(intensity = 0.5, duration = 35) {
        if (!Number.isFinite(intensity) || intensity < 0 || intensity > 1)
            throw new RangeError("WebXR haptic intensity must be in [0, 1].");
        if (!Number.isFinite(duration) || duration < 0)
            throw new RangeError("WebXR haptic duration must be finite and non-negative.");
        let pulses = 0;
        for (const source of this.session?.inputSources ?? []) {
            for (const actuator of source.gamepad?.hapticActuators ?? []) {
                const accepted = await actuator.pulse?.(intensity, duration);
                if (accepted !== false)
                    pulses += 1;
            }
        }
        return pulses;
    }
}
function sampleInputSource(source, frame, referenceSpace) {
    const buttons = source.gamepad?.buttons ?? [];
    const axes = source.gamepad?.axes ?? [];
    const trigger = buttons[0];
    const squeeze = buttons[1];
    return {
        handedness: source.handedness ?? "none",
        targetRayMode: source.targetRayMode ?? "tracked-pointer",
        profiles: source.profiles ?? [],
        triggerPressed: Boolean(trigger?.pressed),
        squeezePressed: Boolean(squeeze?.pressed),
        primaryValue: finiteOrZero(trigger?.value),
        axes: axes.map(finiteOrZero),
        targetRayMatrix: samplePoseMatrix(frame, source.targetRaySpace, referenceSpace),
        gripMatrix: samplePoseMatrix(frame, source.gripSpace, referenceSpace),
        hapticActuatorCount: source.gamepad?.hapticActuators?.length ?? 0
    };
}
function samplePoseMatrix(frame, space, referenceSpace) {
    if (!frame?.getPose || !space)
        return null;
    return sanitizeMatrix4(frame.getPose(space, referenceSpace)?.transform?.matrix);
}
function sampleHitTest(result) {
    return {
        position: sanitizeVec3(result.position, [0, 0, 0]),
        normal: sanitizeVec3(result.normal, [0, 1, 0])
    };
}
function sanitizeVec3(value, fallback) {
    if (!value || value.length < 3)
        return fallback;
    const x = finiteOrZero(value[0]);
    const y = finiteOrZero(value[1]);
    const z = finiteOrZero(value[2]);
    return [x, y, z];
}
function finiteOrZero(value) {
    return typeof value === "number" && Number.isFinite(value) ? value : 0;
}
function sanitizeMatrix4(value) {
    if (!value || value.length < 16)
        return null;
    const matrix = Array.from(value.slice(0, 16), finiteOrZero);
    return matrix;
}
function readNavigatorXR() {
    return globalThis.navigator?.xr;
}
//# sourceMappingURL=WebXRSessionController.js.map