import { AudioListener as ThreeAudioListener } from "three";

import { Application } from "../engine/engine_application.js";
import { Camera } from "./Camera.js";
import { Behaviour, GameObject } from "./Component.js";

/**
 * The [AudioListener](https://engine.needle.tools/docs/api/AudioListener) represents a listener that can hear audio sources in the scene.
 * This component creates and manages a Three.js {@link three#AudioListener}, automatically connecting it
 * to the main camera or a Camera in the parent hierarchy.
 * 
 * @summary Receives audio in the scene and outputs it to speakers
 * @category Multimedia
 * @group Components
 */
export class AudioListener extends Behaviour {

    /**
     * Gets the existing Three.js {@link three#AudioListener} instance or creates a new one if it doesn't exist.
     * This listener is responsible for capturing audio in the 3D scene.
     * @returns The {@link three#AudioListener} instance
     */
    get listener(): ThreeAudioListener {
        if (this._listener == null)
            this._listener = new ThreeAudioListener();
        return this._listener;
    }

    private _listener: ThreeAudioListener | null = null;

    /**
     * Registers for interaction events and initializes the audio listener
     * when this component is enabled.
     * @internal
     */
    onEnable(): void {
        Application.registerWaitForInteraction(this.onInteraction);
        this.addListenerIfItExists();
    }

    /**
     * Cleans up event registrations and removes the audio listener
     * when this component is disabled.
     * @internal
     */
    onDisable(): void {
        Application.unregisterWaitForInteraction(this.onInteraction);
        this.removeListenerIfItExists();
    }

    private onInteraction = () => {
        if (this.destroyed) return;
        const listener = this.listener;
        if (listener == null) return;
        this.addListenerIfItExists();
    }

    private addListenerIfItExists() {
        const listener = this._listener;
        if (!listener) return;
        // if the listener is already parented to some object dont change it
        if (listener?.parent) return;

        const cam = this.context.mainCameraComponent || GameObject.getComponentInParent(this.gameObject, Camera);
        if (cam?.threeCamera) {
            cam.threeCamera.add(listener);
        }
        else {
            this.gameObject.add(listener);
        }

        // connect the listeners audio nodes
        if (!listener.filter) {
            listener.gain.connect(listener.context.destination);
        }
        else {
            listener.gain.connect(listener.filter);
            listener.filter.connect(listener.context.destination);
        }
    }

    private removeListenerIfItExists() {
        const listener = this._listener;
        if (!listener) return;
        listener.removeFromParent();

        // disconnect the listeners audio nodes
        if (listener.filter) {
            listener.filter.disconnect();
        }
        if (listener.gain) {
            listener.gain.disconnect();
        }
    }
}