import { Object3D, Vector3 } from "three";
import { isDevEnvironment } from "../engine/debug/index.js";
import { addComponent, destroyComponentInstance, findObjectOfType, findObjectsOfType, getComponent, getComponentInChildren, getComponentInParent, getComponents, getComponentsInChildren, getComponentsInParent, getOrAddComponent, removeComponent } from "../engine/engine_components.js";
import { activeInHierarchyFieldName } from "../engine/engine_constants.js";
import { destroy, findByGuid, foreachComponent, instantiate, isActiveInHierarchy, isActiveSelf, isDestroyed, isUsingInstancing, markAsInstancedRendered, setActive } from "../engine/engine_gameobject.js";
import { isHotReloadEnabled, registerHotReloadType, unregisterHotReloadType } from "../engine/engine_hot_reload.js";
import * as main from "../engine/engine_mainloop_utils.js";
import { syncDestroy, syncInstantiate } from "../engine/engine_networking_instantiate.js";
import { Context, FrameEvent } from "../engine/engine_setup.js";
import * as threeutils from "../engine/engine_three_utils.js";
import { $componentName } from "../engine/engine_types.js";
import { TypeStore } from "../engine/engine_typestore.js";
// export interface ISerializationCallbackReceiver {
//     onBeforeSerialize?(): object | void;
//     onAfterSerialize?();
//     onBeforeDeserialize?(data?: any);
//     onAfterDeserialize?();
//     onDeserialize?(key: string, value: any): any | void;
// }
/**
 * Base class for objects in Needle Engine. Extends {@link Object3D} from three.js.
 * GameObjects can have components attached to them, which can be used to add functionality to the object.
 * They manage their components and provide methods to add, remove and get components.
 *
 * All {@link Object3D} types loaded in Needle Engine have methods like {@link addComponent}.
 * These methods are available directly on the GameObject instance:
 * ```typescript
 * target.addComponent(MyComponent);
 * ```
 *
 * And can be called statically on the GameObject class as well:
 * ```typescript
 * GameObject.setActive(target, true);
 * ```
 */
export class GameObject extends Object3D {
    /**
     * Unique identifier for this GameObject
     */
    guid;
    /**
     * Checks if a GameObject has been destroyed
     * @param go The GameObject to check
     * @returns True if the GameObject has been destroyed
     */
    static isDestroyed(go) {
        return isDestroyed(go);
    }
    /**
     * Sets the active state of a GameObject
     * @param go The GameObject to modify
     * @param active Whether the GameObject should be active
     * @param processStart Whether to process the start callbacks if being activated
     */
    static setActive(go, active, processStart = true) {
        if (!go)
            return;
        setActive(go, active);
        // TODO: do we still need this?:
        main.updateIsActive(go);
        if (active && processStart)
            main.processStart(Context.Current, go);
    }
    /**
     * Checks if the GameObject itself is active (same as go.visible)
     * @param go The GameObject to check
     * @returns True if the GameObject is active
     */
    static isActiveSelf(go) {
        return isActiveSelf(go);
    }
    /**
     * Checks if the GameObject is active in the hierarchy (e.g. if any parent is invisible or not in the scene it will be false)
     * @param go The GameObject to check
     * @returns True if the GameObject is active in the hierarchy
     */
    static isActiveInHierarchy(go) {
        return isActiveInHierarchy(go);
    }
    /**
     * Marks a GameObject to be rendered using instancing
     * @param go The GameObject to mark
     * @param instanced Whether the GameObject should use instanced rendering
     */
    static markAsInstancedRendered(go, instanced) {
        markAsInstancedRendered(go, instanced);
    }
    /**
     * Checks if a GameObject is using instanced rendering
     * @param instance The GameObject to check
     * @returns True if the GameObject is using instanced rendering
     */
    static isUsingInstancing(instance) { return isUsingInstancing(instance); }
    /**
     * Executes a callback for all components of the provided type on the provided object and its children
     * @param instance Object to run the method on
     * @param cb Callback to run on each component, "return undefined;" to continue and "return <anything>;" to break the loop
     * @param recursive If true, the method will be run on all children as well
     * @returns The last return value of the callback
     */
    static foreachComponent(instance, cb, recursive = true) {
        return foreachComponent(instance, cb, recursive);
    }
    /**
     * Creates a new instance of the provided object that will be replicated to all connected clients
     * @param instance Object to instantiate
     * @param opts Options for the instantiation
     * @returns The newly created instance or null if creation failed
     */
    static instantiateSynced(instance, opts) {
        if (!instance)
            return null;
        return syncInstantiate(instance, opts);
    }
    static instantiate(instance, opts = null) {
        if ('isAssetReference' in instance) {
            return instantiate(instance, opts);
        }
        return instantiate(instance, opts);
    }
    /**
     * Destroys an object on all connected clients (if in a networked session)
     * @param instance Object to destroy
     * @param context Optional context to use
     * @param recursive If true, all children will be destroyed as well
     */
    static destroySynced(instance, context, recursive = true) {
        if (!instance)
            return;
        const go = instance;
        context = context ?? Context.Current;
        syncDestroy(go, context.connection, recursive);
    }
    /**
     * Destroys an object
     * @param instance Object to destroy
     * @param recursive If true, all children will be destroyed as well. Default: true
     */
    static destroy(instance, recursive = true) {
        return destroy(instance, recursive);
    }
    /**
     * Adds an object to parent and ensures all components are properly registered
     * @param instance Object to add
     * @param parent Parent to add the object to
     * @param context Optional context to use
     */
    static add(instance, parent, context) {
        if (!instance || !parent)
            return;
        if (instance === parent) {
            console.warn("Can not add object to self", instance);
            return;
        }
        if (!context) {
            context = Context.Current;
        }
        parent.add(instance);
        setActive(instance, true);
        main.updateIsActive(instance);
        if (context) {
            GameObject.foreachComponent(instance, (comp) => {
                main.addScriptToArrays(comp, context);
                if (comp.__internalDidAwakeAndStart)
                    return;
                if (context.new_script_start.includes(comp) === false) {
                    context.new_script_start.push(comp);
                }
            }, true);
        }
        else {
            console.warn("Missing context");
        }
    }
    /**
     * Removes the object from its parent and deactivates all of its components
     * @param instance Object to remove
     */
    static remove(instance) {
        if (!instance)
            return;
        instance.parent?.remove(instance);
        setActive(instance, false);
        main.updateIsActive(instance);
        GameObject.foreachComponent(instance, (comp) => {
            main.processRemoveFromScene(comp);
        }, true);
    }
    /**
     * Invokes a method on all components including children (if a method with that name exists)
     * @param go GameObject to invoke the method on
     * @param functionName Name of the method to invoke
     * @param args Arguments to pass to the method
     */
    static invokeOnChildren(go, functionName, ...args) {
        this.invoke(go, functionName, true, args);
    }
    /**
     * Invokes a method on all components that have a method matching the provided name
     * @param go GameObject to invoke the method on
     * @param functionName Name of the method to invoke
     * @param children Whether to invoke on children as well
     * @param args Arguments to pass to the method
     */
    static invoke(go, functionName, children = false, ...args) {
        if (!go)
            return;
        this.foreachComponent(go, c => {
            const fn = c[functionName];
            if (fn && typeof fn === "function") {
                fn?.call(c, ...args);
            }
        }, children);
    }
    /** @deprecated use `addComponent` */
    static addNewComponent(go, type, init, callAwake = true) {
        return addComponent(go, type, init, { callAwake });
    }
    /**
     * Adds a new component (or moves an existing component) to the provided object
     * @param go Object to add the component to
     * @param instanceOrType If an instance is provided it will be moved to the new object, if a type is provided a new instance will be created
     * @param init Optional init object to initialize the component with
     * @param opts Optional options for adding the component
     * @returns The added or moved component
     */
    static addComponent(go, instanceOrType, init, opts) {
        return addComponent(go, instanceOrType, init, opts);
    }
    /**
     * Moves a component to a new object
     * @param go GameObject to move the component to
     * @param instance Component to move
     * @returns The moved component
     */
    static moveComponent(go, instance) {
        return addComponent(go, instance);
    }
    /**
     * Removes a component from its object
     * @param instance Component to remove
     * @returns The removed component
     */
    static removeComponent(instance) {
        removeComponent(instance.gameObject, instance);
        return instance;
    }
    /**
     * Gets or adds a component of the specified type
     * @param go GameObject to get or add the component to
     * @param typeName Constructor of the component type
     * @returns The existing or newly added component
     */
    static getOrAddComponent(go, typeName) {
        return getOrAddComponent(go, typeName);
    }
    /**
     * Gets a component on the provided object
     * @param go GameObject to get the component from
     * @param typeName Constructor of the component type
     * @returns The component if found, otherwise null
     */
    static getComponent(go, typeName) {
        if (go === null)
            return null;
        // if names are minified we could also use the type store and work with strings everywhere
        // not ideal, but I dont know a good/sane way to do this otherwise
        // const res = TypeStore.get(typeName);
        // if(res) typeName = res;
        return getComponent(go, typeName);
    }
    /**
     * Gets all components of the specified type on the provided object
     * @param go GameObject to get the components from
     * @param typeName Constructor of the component type
     * @param arr Optional array to populate with the components
     * @returns Array of components
     */
    static getComponents(go, typeName, arr = null) {
        if (go === null)
            return arr ?? [];
        return getComponents(go, typeName, arr);
    }
    /**
     * Finds an object or component by its unique identifier
     * @param guid Unique identifier to search for
     * @param hierarchy Root object to search in
     * @returns The found GameObject or Component, or null/undefined if not found
     */
    static findByGuid(guid, hierarchy) {
        const res = findByGuid(guid, hierarchy);
        return res;
    }
    /**
     * Finds the first object of the specified component type in the scene
     * @param typeName Constructor of the component type
     * @param context Context or root object to search in
     * @param includeInactive Whether to include inactive objects in the search
     * @returns The first matching component if found, otherwise null
     */
    static findObjectOfType(typeName, context, includeInactive = true) {
        return findObjectOfType(typeName, context ?? Context.Current, includeInactive);
    }
    /**
     * Finds all objects of the specified component type in the scene
     * @param typeName Constructor of the component type
     * @param context Context or root object to search in
     * @returns Array of matching components
     */
    static findObjectsOfType(typeName, context) {
        const arr = [];
        findObjectsOfType(typeName, arr, context);
        return arr;
    }
    /**
     * Gets a component of the specified type in the gameObject's children hierarchy
     * @param go GameObject to search in
     * @param typeName Constructor of the component type
     * @param includeInactive Whether to include inactive objects in the search
     * @returns The first matching component if found, otherwise null
     */
    static getComponentInChildren(go, typeName, includeInactive = false) {
        return getComponentInChildren(go, typeName, includeInactive);
    }
    /**
     * Gets all components of the specified type in the gameObject's children hierarchy
     * @param go GameObject to search in
     * @param typeName Constructor of the component type
     * @param arr Optional array to populate with the components
     * @returns Array of components
     */
    static getComponentsInChildren(go, typeName, arr = null) {
        return getComponentsInChildren(go, typeName, arr ?? undefined);
    }
    /**
     * Gets a component of the specified type in the gameObject's parent hierarchy
     * @param go GameObject to search in
     * @param typeName Constructor of the component type
     * @returns The first matching component if found, otherwise null
     */
    static getComponentInParent(go, typeName) {
        return getComponentInParent(go, typeName);
    }
    /**
     * Gets all components of the specified type in the gameObject's parent hierarchy
     * @param go GameObject to search in
     * @param typeName Constructor of the component type
     * @param arr Optional array to populate with the components
     * @returns Array of components
     */
    static getComponentsInParent(go, typeName, arr = null) {
        return getComponentsInParent(go, typeName, arr);
    }
    /**
     * Gets all components on the gameObject
     * @param go GameObject to get components from
     * @returns Array of all components
     */
    static getAllComponents(go) {
        const componentsList = go.userData?.components;
        if (!componentsList)
            return [];
        const newList = [...componentsList];
        return newList;
    }
    /**
     * Iterates through all components on the gameObject
     * @param go GameObject to iterate components on
     * @returns Generator yielding each component
     */
    static *iterateComponents(go) {
        const list = go?.userData?.components;
        if (list && Array.isArray(list)) {
            for (let i = 0; i < list.length; i++) {
                yield list[i];
            }
        }
    }
}
/**
 * Needle Engine component's are the main building blocks of the Needle Engine.
 * Derive from {@link Behaviour} to implement your own using the provided lifecycle methods.
 * Components can be added to any {@link Object3D} using {@link addComponent} or {@link GameObject.addComponent}.
 *
 * **Component lifecycle event methods:**
 * {@link awake}, {@link start}, {@link onEnable}, {@link onDisable}, {@link onDestroy}, {@link earlyUpdate}, {@link update}, {@link lateUpdate}, {@link onBeforeRender}, {@link onAfterRender}.
 *
 * **XR event methods:**
 * {@link onEnterXR}, {@link onLeaveXR}, {@link onUpdateXR}, {@link onXRControllerAdded} and {@link onXRControllerRemoved}.
 *
 * **Input event methods:**
 * {@link onPointerDown}, {@link onPointerUp}, {@link onPointerEnter}, {@link onPointerExit} and {@link onPointerMove}.
 *
 * @example
 * ```typescript
 * import { Behaviour } from "@needle-tools/engine";
 * export class MyComponent extends Behaviour {
 *  start() {
 *     console.log("Hello World", this.gameObject.name);
 *  }
 *  update() {
 *    console.log("Frame", this.context.time.frame);
 *  }
 * }
 * ```
 *
 * @group Components
 */
export class Component {
    /**
     * Indicates whether this object is a component
     * @internal
     */
    get isComponent() { return true; }
    /**
     * Get the original component type name before minification (available if the component is registered in the TypeStore)
     */
    get [$componentName]() { return TypeStore.getKey(this.constructor) || undefined; }
    __context;
    /**
     * The context this component belongs to, providing access to the runtime environment
     * including physics, timing utilities, camera, and scene
     */
    get context() {
        return this.__context ?? Context.Current;
    }
    set context(context) {
        this.__context = context;
    }
    /**
     * Shorthand accessor for the current scene from the context
     * @returns The scene this component belongs to
     */
    get scene() { return this.context.scene; }
    /**
     * The layer value of the GameObject this component is attached to
     * Used for visibility and physics filtering
     */
    get layer() {
        return this.gameObject?.userData?.layer;
    }
    /**
     * The name of the GameObject this component is attached to
     * Used for debugging and finding objects
     */
    get name() {
        if (this.gameObject?.name) {
            return this.gameObject.name;
        }
        return this.gameObject?.userData.name;
    }
    __name;
    set name(str) {
        if (this.gameObject) {
            if (!this.gameObject.userData)
                this.gameObject.userData = {};
            this.gameObject.userData.name = str;
            this.__name = str;
        }
        else {
            this.__name = str;
        }
    }
    /**
     * The tag of the GameObject this component is attached to
     * Used for categorizing objects and efficient lookup
     */
    get tag() {
        return this.gameObject?.userData.tag;
    }
    set tag(str) {
        if (this.gameObject) {
            if (!this.gameObject.userData)
                this.gameObject.userData = {};
            this.gameObject.userData.tag = str;
        }
    }
    /**
     * Indicates whether the GameObject is marked as static
     * Static objects typically don't move and can be optimized by the engine
     */
    get static() {
        return this.gameObject?.userData.static;
    }
    set static(value) {
        if (this.gameObject) {
            if (!this.gameObject.userData)
                this.gameObject.userData = {};
            this.gameObject.userData.static = value;
        }
    }
    // get hideFlags(): HideFlags {
    //     return this.gameObject?.hideFlags;
    // }
    /**
     * Checks if this component is currently active (enabled and part of an active GameObject hierarchy)
     * Components that are inactive won't receive lifecycle method calls
     * @returns True if the component is enabled and all parent GameObjects are active
     */
    get activeAndEnabled() {
        if (this.destroyed)
            return false;
        if (this.__isEnabled === false)
            return false;
        if (!this.__isActiveInHierarchy)
            return false;
        // let go = this.gameObject;
        // do {
        //     // console.log(go.name, go.visible)
        //     if (!go.visible) return false;
        //     go = go.parent as GameObject;
        // }
        // while (go);
        return true;
    }
    get __isActive() {
        return this.gameObject.visible;
    }
    get __isActiveInHierarchy() {
        if (!this.gameObject)
            return false;
        const res = this.gameObject[activeInHierarchyFieldName];
        if (res === undefined)
            return true;
        return res;
    }
    set __isActiveInHierarchy(val) {
        if (!this.gameObject)
            return;
        this.gameObject[activeInHierarchyFieldName] = val;
    }
    /**
     * Reference to the GameObject this component is attached to
     * This is a three.js Object3D with additional GameObject functionality
     */
    gameObject;
    /**
     * Unique identifier for this component instance,
     * used for finding and tracking components
     */
    guid = "invalid";
    /**
     * Identifier for the source asset that created this component.
     * For example, URL to the glTF file this component was loaded from
     */
    sourceId;
    /**
     * Called once when the component becomes active for the first time.
     * This is the first lifecycle callback to be invoked
     */
    awake() { }
    /**
     * Called every time the component becomes enabled or active in the hierarchy.
     * Invoked after {@link awake} and before {@link start}.
     */
    onEnable() { }
    /**
     * Called every time the component becomes disabled or inactive in the hierarchy.
     * Invoked when the component or any parent GameObject becomes invisible
     */
    onDisable() { }
    /**
     * Called when the component is destroyed.
     * Use for cleanup operations like removing event listeners
     */
    onDestroy() {
        this.__destroyed = true;
    }
    /**
     * Starts a coroutine that can yield to wait for events.
     * Coroutines allow for time-based sequencing of operations without blocking.
     * Coroutines are based on generator functions, a JavaScript language feature.
     *
     * @param routine Generator function to start
     * @param evt Event to register the coroutine for (default: FrameEvent.Update)
     * @returns The generator function that can be used to stop the coroutine
     * @example
     * Time-based sequencing of operations
     * ```ts
     * *myCoroutine() {
     *   yield WaitForSeconds(1); // wait for 1 second
     *   yield WaitForFrames(10); // wait for 10 frames
     *   yield new Promise(resolve => setTimeout(resolve, 1000)); // wait for a promise to resolve
     * }
     * ```
     * @example
     * Coroutine that logs a message every 5 frames
     * ```ts
     * onEnable() {
     *   this.startCoroutine(this.myCoroutine());
     * }
     * private *myCoroutine() {
     *   while(this.activeAndEnabled) {
     *     console.log("Hello World", this.context.time.frame);
     *     // wait for 5 frames
     *     for(let i = 0; i < 5; i++) yield;
     *   }
     * }
     * ```
     */
    startCoroutine(routine, evt = FrameEvent.Update) {
        return this.context.registerCoroutineUpdate(this, routine, evt);
    }
    /**
     * Stops a coroutine that was previously started with startCoroutine
     * @param routine The routine to be stopped
     * @param evt The frame event the routine was registered with
     */
    stopCoroutine(routine, evt = FrameEvent.Update) {
        this.context.unregisterCoroutineUpdate(routine, evt);
    }
    /**
     * Checks if this component has been destroyed
     * @returns True if the component or its GameObject has been destroyed
     */
    get destroyed() {
        return this.__destroyed;
    }
    /**
     * Destroys this component and removes it from its GameObject
     * After destruction, the component will no longer receive lifecycle callbacks
     */
    destroy() {
        if (this.__destroyed)
            return;
        this.__internalDestroy();
    }
    /** @internal */
    __didAwake = false;
    /** @internal */
    __didStart = false;
    /** @internal */
    __didEnable = false;
    /** @internal */
    __isEnabled = undefined;
    /** @internal */
    __destroyed = false;
    /** @internal */
    get __internalDidAwakeAndStart() { return this.__didAwake && this.__didStart; }
    /** @internal */
    constructor(init) {
        this.__didAwake = false;
        this.__didStart = false;
        this.__didEnable = false;
        this.__isEnabled = undefined;
        this.__destroyed = false;
        this._internalInit(init);
        if (isHotReloadEnabled())
            registerHotReloadType(this);
    }
    /** @internal */
    __internalNewInstanceCreated(init) {
        this.__didAwake = false;
        this.__didStart = false;
        this.__didEnable = false;
        this.__isEnabled = undefined;
        this.__destroyed = false;
        this._internalInit(init);
        return this;
    }
    /**
     * Initializes component properties from an initialization object
     * @param init Object with properties to copy to this component
     * @internal
     */
    _internalInit(init) {
        if (typeof init === "object") {
            for (const key of Object.keys(init)) {
                const value = init[key];
                // we don't want to allow overriding functions via init
                if (typeof value === "function")
                    continue;
                this[key] = value;
            }
        }
    }
    /** @internal */
    __internalAwake() {
        if (this.__didAwake)
            return;
        this.__didAwake = true;
        this.awake();
    }
    /** @internal */
    __internalStart() {
        if (this.__didStart)
            return;
        this.__didStart = true;
        if (this.start)
            this.start();
    }
    /** @internal */
    __internalEnable(isAddingToScene) {
        if (this.__destroyed) {
            if (isDevEnvironment()) {
                console.warn("[Needle Engine Dev] Trying to enable destroyed component");
            }
            return false;
        }
        // Don't change enable before awake
        // But a user can change enable during awake
        if (!this.__didAwake)
            return false;
        if (this.__didEnable) {
            // We dont want to change the enable state if we are adding to scene
            // But we want to change the state when e.g. a user changes the enable state during awake
            if (isAddingToScene !== true)
                this.__isEnabled = true;
            return false;
        }
        // console.trace("INTERNAL ENABLE");
        this.__didEnable = true;
        this.__isEnabled = true;
        this.onEnable();
        return true;
    }
    /** @internal */
    __internalDisable(isRemovingFromScene) {
        // Don't change enable before awake
        // But a user can change enable during awake
        if (!this.__didAwake)
            return;
        if (!this.__didEnable) {
            // We dont want to change the enable state if we are removing from a scene
            if (isRemovingFromScene !== true)
                this.__isEnabled = false;
            return;
        }
        this.__didEnable = false;
        this.__isEnabled = false;
        this.onDisable();
    }
    /** @internal */
    __internalDestroy() {
        if (this.__destroyed)
            return;
        this.__destroyed = true;
        if (this.__didAwake) {
            this.onDestroy?.call(this);
            this.dispatchEvent(new CustomEvent("destroyed", { detail: this }));
        }
        destroyComponentInstance(this);
        if (isHotReloadEnabled())
            unregisterHotReloadType(this);
    }
    /**
     * Controls whether this component is enabled
     * Disabled components don't receive lifecycle callbacks
     */
    get enabled() {
        return typeof this.__isEnabled === "boolean" ? this.__isEnabled : true; // if it has no enabled field it is always enabled
    }
    set enabled(val) {
        if (this.__destroyed) {
            if (isDevEnvironment()) {
                console.warn(`[Needle Engine Dev] Trying to ${val ? "enable" : "disable"} destroyed component`);
            }
            return;
        }
        // when called from animationclip we receive numbers
        // due to interpolation they can be anything between 0 and 1
        if (typeof val === "number") {
            if (val >= 0.5)
                val = true;
            else
                val = false;
        }
        // need to check here because codegen is calling this before everything is setup
        if (!this.__didAwake) {
            this.__isEnabled = val;
            return;
        }
        if (val) {
            this.__internalEnable();
        }
        else {
            this.__internalDisable();
        }
    }
    /**
     * Gets the position of this component's GameObject in world space.
     * Note: This is equivalent to calling `this.gameObject.worldPosition`
     */
    get worldPosition() {
        return threeutils.getWorldPosition(this.gameObject);
    }
    /**
     * Sets the position of this component's GameObject in world space
     * @param val The world position vector to set
     */
    set worldPosition(val) {
        threeutils.setWorldPosition(this.gameObject, val);
    }
    /**
     * Sets the position of this component's GameObject in world space using individual coordinates
     * @param x X-coordinate in world space
     * @param y Y-coordinate in world space
     * @param z Z-coordinate in world space
     */
    setWorldPosition(x, y, z) {
        threeutils.setWorldPositionXYZ(this.gameObject, x, y, z);
    }
    /**
     * Gets the rotation of this component's GameObject in world space as a quaternion
     * Note: This is equivalent to calling `this.gameObject.worldQuaternion`
     */
    get worldQuaternion() {
        return threeutils.getWorldQuaternion(this.gameObject);
    }
    /**
     * Sets the rotation of this component's GameObject in world space using a quaternion
     * @param val The world rotation quaternion to set
     */
    set worldQuaternion(val) {
        threeutils.setWorldQuaternion(this.gameObject, val);
    }
    /**
     * Sets the rotation of this component's GameObject in world space using quaternion components
     * @param x X component of the quaternion
     * @param y Y component of the quaternion
     * @param z Z component of the quaternion
     * @param w W component of the quaternion
     */
    setWorldQuaternion(x, y, z, w) {
        threeutils.setWorldQuaternionXYZW(this.gameObject, x, y, z, w);
    }
    /**
     * Gets the rotation of this component's GameObject in world space as Euler angles (in radians)
     */
    get worldEuler() {
        return threeutils.getWorldEuler(this.gameObject);
    }
    /**
     * Sets the rotation of this component's GameObject in world space using Euler angles (in radians)
     * @param val The world rotation Euler angles to set
     */
    set worldEuler(val) {
        threeutils.setWorldEuler(this.gameObject, val);
    }
    /**
     * Gets the rotation of this component's GameObject in world space as Euler angles (in degrees)
     * Note: This is equivalent to calling `this.gameObject.worldRotation`
     */
    get worldRotation() {
        return this.gameObject.worldRotation;
    }
    /**
     * Sets the rotation of this component's GameObject in world space using Euler angles (in degrees)
     * @param val The world rotation vector to set (in degrees)
     */
    set worldRotation(val) {
        this.setWorldRotation(val.x, val.y, val.z, true);
    }
    /**
     * Sets the rotation of this component's GameObject in world space using individual Euler angles
     * @param x X-axis rotation
     * @param y Y-axis rotation
     * @param z Z-axis rotation
     * @param degrees Whether the values are in degrees (true) or radians (false)
     */
    setWorldRotation(x, y, z, degrees = true) {
        threeutils.setWorldRotationXYZ(this.gameObject, x, y, z, degrees);
    }
    static _forward = new Vector3();
    /**
     * Gets the forward direction vector (0,0,-1) of this component's GameObject in world space
     */
    get forward() {
        return Component._forward.set(0, 0, -1).applyQuaternion(this.worldQuaternion);
    }
    static _right = new Vector3();
    /**
     * Gets the right direction vector (1,0,0) of this component's GameObject in world space
     */
    get right() {
        return Component._right.set(1, 0, 0).applyQuaternion(this.worldQuaternion);
    }
    static _up = new Vector3();
    /**
     * Gets the up direction vector (0,1,0) of this component's GameObject in world space
     */
    get up() {
        return Component._up.set(0, 1, 0).applyQuaternion(this.worldQuaternion);
    }
    // EventTarget implementation:
    /**
     * Storage for event listeners registered to this component
     * @private
     */
    _eventListeners = new Map();
    /**
     * Registers an event listener for the specified event type
     * @param type The event type to listen for
     * @param listener The callback function to execute when the event occurs
     */
    addEventListener(type, listener) {
        this._eventListeners[type] = this._eventListeners[type] || [];
        this._eventListeners[type].push(listener);
    }
    /**
     * Removes a previously registered event listener
     * @param type The event type the listener was registered for
     * @param listener The callback function to remove
     */
    removeEventListener(type, listener) {
        if (!this._eventListeners[type])
            return;
        const index = this._eventListeners[type].indexOf(listener);
        if (index >= 0)
            this._eventListeners[type].splice(index, 1);
    }
    /**
     * Dispatches an event to all registered listeners
     * @param evt The event object to dispatch
     * @returns Always returns false (standard implementation of EventTarget)
     */
    dispatchEvent(evt) {
        if (!evt || !this._eventListeners[evt.type])
            return false;
        const listeners = this._eventListeners[evt.type];
        for (let i = 0; i < listeners.length; i++) {
            listeners[i](evt);
        }
        return false;
    }
}
// For legacy reasons we need to export this as well 
// (and we don't use extend to inherit the component docs)
export { Component as Behaviour };
//# sourceMappingURL=Component.js.map