import { InputSnapshot } from "../input/index.js";
import { FlyControls } from "./FlyControls.js";
import { OrbitControls } from "./OrbitControls.js";
import { Picking } from "./Picking.js";
import { ControlVector3 } from "./NativeControlTypes.js";
export class InteractionControls {
    orbit;
    fly;
    picking;
    enabled = true;
    controlMode;
    rootRef;
    rayProvider;
    pickOptions;
    listeners = new Set();
    hotspotHandlers = new Map();
    hoverHit = null;
    disposed = false;
    rotateButton;
    panButton;
    rotateSpeed;
    panSpeed;
    dollySpeed;
    flyMoveSpeed;
    constructor(options = {}) {
        this.orbit = options.orbit ?? new OrbitControls();
        this.fly = options.fly ?? new FlyControls();
        this.picking = options.picking ?? new Picking();
        this.controlMode = options.mode ?? "orbit";
        this.rootRef = options.root;
        this.rayProvider = options.rayProvider;
        this.pickOptions = options.pickOptions ?? {};
        this.rotateButton = options.rotateButton ?? 0;
        this.panButton = options.panButton ?? 1;
        this.rotateSpeed = options.rotateSpeed ?? 0.005;
        this.panSpeed = options.panSpeed ?? 0.01;
        this.dollySpeed = options.dollySpeed ?? 0.001;
        this.flyMoveSpeed = options.flyMoveSpeed ?? 1;
    }
    get mode() {
        return this.controlMode;
    }
    setMode(mode, snapshot = new InputSnapshot()) {
        if (this.controlMode === mode)
            return;
        this.controlMode = mode;
        this.emit({ type: "mode-change", mode, snapshot });
    }
    setRoot(root) {
        this.rootRef = root;
        this.hoverHit = null;
    }
    setRayProvider(provider) {
        this.rayProvider = provider;
    }
    setPickOptions(options) {
        this.pickOptions = options;
    }
    subscribe(listener) {
        this.listeners.add(listener);
        return () => this.listeners.delete(listener);
    }
    onHotspot(id, handler) {
        const handlers = this.hotspotHandlers.get(id) ?? new Set();
        handlers.add(handler);
        this.hotspotHandlers.set(id, handlers);
        return () => {
            handlers.delete(handler);
            if (handlers.size === 0)
                this.hotspotHandlers.delete(id);
        };
    }
    update(snapshot) {
        if (this.disposed || !this.enabled) {
            return { mode: this.controlMode, hit: this.hoverHit };
        }
        if (this.controlMode === "orbit") {
            this.updateOrbit(snapshot);
        }
        else {
            this.updateFly(snapshot);
        }
        const pickReport = this.updatePicking(snapshot);
        return { mode: this.controlMode, pickReport, hit: pickReport?.hit ?? null };
    }
    pick(snapshot) {
        const root = this.resolveRoot();
        if (!root)
            return undefined;
        const ray = this.resolveRay(snapshot);
        return this.picking.report(root, ray.origin, ray.direction, this.pickOptions);
    }
    dispose() {
        this.disposed = true;
        this.listeners.clear();
        this.hotspotHandlers.clear();
        this.hoverHit = null;
    }
    updateOrbit(snapshot) {
        if (snapshot.button(this.rotateButton).down) {
            this.orbit.rotate(snapshot.pointer.deltaX * this.rotateSpeed, snapshot.pointer.deltaY * this.rotateSpeed);
        }
        if (snapshot.button(this.panButton).down) {
            this.orbit.pan(snapshot.pointer.deltaX * this.panSpeed, -snapshot.pointer.deltaY * this.panSpeed);
        }
        if (snapshot.pointer.wheelY !== 0) {
            this.orbit.dolly(Math.max(0.01, 1 + snapshot.pointer.wheelY * this.dollySpeed));
        }
    }
    updateFly(snapshot) {
        const distance = this.flyMoveSpeed;
        if (snapshot.key("KeyW").down || snapshot.key("ArrowUp").down)
            this.fly.moveForward(distance);
        if (snapshot.key("KeyS").down || snapshot.key("ArrowDown").down)
            this.fly.moveForward(-distance);
        if (snapshot.key("KeyA").down || snapshot.key("ArrowLeft").down)
            this.fly.strafe(-distance);
        if (snapshot.key("KeyD").down || snapshot.key("ArrowRight").down)
            this.fly.strafe(distance);
    }
    updatePicking(snapshot) {
        const pickReport = this.pick(snapshot);
        const hit = pickReport?.hit ?? null;
        const previousHit = this.hoverHit;
        if (previousHit?.object !== hit?.object) {
            if (previousHit)
                this.emit({ type: "hover-exit", mode: this.controlMode, snapshot, previousHit });
            if (hit)
                this.emit({ type: "hover-enter", mode: this.controlMode, snapshot, hit, previousHit: previousHit ?? undefined });
            this.hoverHit = hit;
        }
        if (hit && snapshot.button(0).released) {
            const event = { type: "pick", mode: this.controlMode, snapshot, hit, previousHit: previousHit ?? undefined };
            this.emit(event);
            this.emitHotspot(event);
        }
        return pickReport;
    }
    emitHotspot(event) {
        const id = event.hit.metadata?.id ?? event.hit.object.name;
        if (!id)
            return;
        const hotspotEvent = { ...event, type: "hotspot-click" };
        this.emit(hotspotEvent);
        for (const handler of [...(this.hotspotHandlers.get(id) ?? [])]) {
            handler(hotspotEvent);
        }
    }
    emit(event) {
        for (const listener of [...this.listeners])
            listener(event);
    }
    resolveRoot() {
        return typeof this.rootRef === "function" ? this.rootRef() : this.rootRef;
    }
    resolveRay(snapshot) {
        return this.rayProvider?.(snapshot) ?? {
            origin: new ControlVector3(),
            direction: new ControlVector3(0, 0, -1)
        };
    }
}
//# sourceMappingURL=InteractionControls.js.map