import { Euler, Object3D, Quaternion, Scene, 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, HideFlags, type IInstantiateOptions, instantiate, isActiveInHierarchy, isActiveSelf, isDestroyed, isUsingInstancing, markAsInstancedRendered, setActive } from "../engine/engine_gameobject.js";
import * as main from "../engine/engine_mainloop_utils.js";
import { syncDestroy, syncInstantiate, SyncInstantiateOptions } from "../engine/engine_networking_instantiate.js";
import { Context, FrameEvent } from "../engine/engine_setup.js";
import * as threeutils from "../engine/engine_three_utils.js";
import type { Collision, ComponentInit, Constructor, ConstructorConcrete, GuidsMap, ICollider, IComponent, IGameObject, SourceIdentifier } from "../engine/engine_types.js";
import type { INeedleXRSessionEventReceiver, NeedleXRControllerEventArgs, NeedleXREventArgs } from "../engine/engine_xr.js";
import { type IPointerEventHandler, PointerEventData } from "./ui/PointerEvents.js";


// export interface ISerializationCallbackReceiver {
//     onBeforeSerialize?(): object | void;
//     onAfterSerialize?();
//     onBeforeDeserialize?(data?: any);
//     onAfterDeserialize?();
//     onDeserialize?(key: string, value: any): any | void;
// }

/**
 * All {@type Object3D} types that are loaded in Needle Engine do automatically receive the GameObject extensions like `addComponent` etc.  
 * Many of the GameObject methods can be imported directly via `@needle-tools/engine` as well:
 * ```typescript
 * import { addComponent } from "@needle-tools/engine";
 * ```
 */
export abstract class GameObject extends Object3D implements Object3D, IGameObject {

    // these are implemented via threejs object extensions
    abstract activeSelf: boolean;

    /** @deprecated use `addComponent` */
    // eslint-disable-next-line deprecation/deprecation
    abstract addNewComponent<T extends IComponent>(type: ConstructorConcrete<T>, init?: ComponentInit<T>): T;
    /** creates a new component on this gameObject */
    abstract addComponent<T extends IComponent>(comp: T | ConstructorConcrete<T>, init?: ComponentInit<T>): T;
    abstract removeComponent<T extends IComponent>(comp: T): T;
    abstract getOrAddComponent<T>(typeName: ConstructorConcrete<T> | null): T;
    abstract getComponent<T>(type: Constructor<T>): T | null;
    abstract getComponents<T>(type: Constructor<T>, arr?: T[]): Array<T>;
    abstract getComponentInChildren<T>(type: Constructor<T>): T | null;
    abstract getComponentsInChildren<T>(type: Constructor<T>, arr?: T[]): Array<T>;
    abstract getComponentInParent<T>(type: Constructor<T>): T | null;
    abstract getComponentsInParent<T>(type: Constructor<T>, arr?: T[]): Array<T>;


    abstract get worldPosition(): Vector3
    abstract set worldPosition(val: Vector3);
    abstract set worldQuaternion(val: Quaternion);
    abstract get worldQuaternion(): Quaternion;
    abstract set worldRotation(val: Vector3);
    abstract get worldRotation(): Vector3;
    abstract set worldScale(val: Vector3);
    abstract get worldScale(): Vector3;

    abstract get worldForward(): Vector3;
    abstract get worldRight(): Vector3;
    abstract get worldUp(): Vector3;

    guid: string | undefined;

    // Added to the threejs Object3D prototype
    abstract destroy();




    public static isDestroyed(go: Object3D): boolean {
        return isDestroyed(go);
    }

    public static setActive(go: Object3D, active: boolean, processStart: boolean = true) {
        if (!go) return;
        setActive(go, active);

        // TODO: do we still need this?:
        main.updateIsActive(go);
        if (active && processStart)
            main.processStart(Context.Current, go);
    }

    /** If the object is active (same as go.visible) */
    public static isActiveSelf(go: Object3D): boolean {
        return isActiveSelf(go);
    }

    /** If the object is active in the hierarchy (e.g. if any parent is invisible or not in the scene it will be false) 
     * @param go object to check
    */
    public static isActiveInHierarchy(go: Object3D): boolean {
        return isActiveInHierarchy(go);
    }

    public static markAsInstancedRendered(go: Object3D, instanced: boolean) {
        markAsInstancedRendered(go, instanced);
    }

    public static isUsingInstancing(instance: Object3D): boolean { return isUsingInstancing(instance); }

    /** Run a callback for all components of the provided type on the provided object and its children (if recursive is true)
     * @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
     */
    public static foreachComponent(instance: Object3D, cb: (comp: Component) => any, recursive: boolean = true): any {
        return foreachComponent(instance, cb as (comp: IComponent) => any, recursive);
    }

    /** Creates a new instance of the provided object. The new instance will be created on all connected clients 
     * @param instance object to instantiate
     * @param opts options for the instantiation
     */
    public static instantiateSynced(instance: GameObject | Object3D | null, opts: SyncInstantiateOptions): GameObject | null {
        if (!instance) return null;
        return syncInstantiate(instance as any, opts) as GameObject | null;
    }

    /** Creates a new instance of the provided object (like cloning it including all components and children) 
     * @param instance object to instantiate
     * @param opts options for the instantiation (e.g. with what parent, position, etc.)
    */
    public static instantiate(instance: GameObject | Object3D, opts: IInstantiateOptions | null = null): GameObject {
        return instantiate(instance, opts) as GameObject;
    }

    /** Destroys a object on all connected clients (if you are in a networked session) 
     * @param instance object to destroy
    */
    public static destroySynced(instance: Object3D | Component, context?: Context, recursive: boolean = true) {
        if (!instance) return;
        const go = instance as GameObject;
        context = context ?? Context.Current;
        syncDestroy(go as any, context.connection, recursive);
    }

    /** Destroys a object
     * @param instance object to destroy
     * @param recursive if true, all children will be destroyed as well. true by default
     */
    public static destroy(instance: Object3D | Component, recursive: boolean = true) {
        return destroy(instance, recursive);
    }

    /**
     * Add an object to parent and also ensure all components are being registered
     */
    public static add(instance: Object3D | null | undefined, parent: Object3D, context?: 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: Component) => {
                main.addScriptToArrays(comp, context!);
                if (comp.__internalDidAwakeAndStart) return;
                if (context!.new_script_start.includes(comp) === false) {
                    context!.new_script_start.push(comp as Component);
                }
            }, true);
        }
        else {
            console.warn("Missing context");
        }
    }

    /**
     * Removes the object from its parent and deactivates all of its components
     */
    public static remove(instance: Object3D | null | undefined) {
        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) */
    public static invokeOnChildren(go: Object3D | null | undefined, functionName: string, ...args: any) {
        this.invoke(go, functionName, true, args);
    }

    /** Invokes a method on all components that have a method matching the provided name
     * @param go object to invoke the method on all components
     * @param functionName name of the method to invoke
     */
    public static invoke(go: Object3D | null | undefined, functionName: string, children: boolean = false, ...args: any) {
        if (!go) return;
        this.foreachComponent(go, c => {
            const fn = c[functionName];
            if (fn && typeof fn === "function") {
                fn?.call(c, ...args)
            }
        }, children);
    }

    /** @deprecated use `addComponent` */
    // eslint-disable-next-line deprecation/deprecation
    public static addNewComponent<T extends IComponent>(go: IGameObject | Object3D, type: T | ConstructorConcrete<T>, init?: ComponentInit<T>, callAwake: boolean = true): T {
        return addComponent(go, type, init, { callAwake });
    }

    /**
     * Add a new component (or move 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 and moved to the new object
     * @param init optional init object to initialize the component with
     * @param callAwake if true, the component will be added and awake will be called immediately
     */
    public static addComponent<T extends IComponent>(go: IGameObject | Object3D, instanceOrType: T | ConstructorConcrete<T>, init?: ComponentInit<T>, opts?: { callAwake: boolean }): T {
        return addComponent(go, instanceOrType, init, opts);
    }

    /**
     * Moves a component to a new object  
     * @param go component to move the component to
     * @param instance component to move to the GO
     */
    public static moveComponent<T extends IComponent>(go: IGameObject | Object3D, instance: T | ConstructorConcrete<T>): T {
        return addComponent(go, instance);
    }

    /** Removes a component from its object
     * @param instance component to remove
     */
    public static removeComponent<T extends IComponent>(instance: T): T {
        removeComponent(instance.gameObject, instance as any);
        return instance;
    }

    public static getOrAddComponent<T extends IComponent>(go: IGameObject | Object3D, typeName: ConstructorConcrete<T>): T {
        return getOrAddComponent<any>(go, typeName);
    }

    /** Gets a component on the provided object */
    public static getComponent<T extends IComponent>(go: IGameObject | Object3D | null, typeName: Constructor<T> | null): T | null {
        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 as any);
    }

    public static getComponents<T extends IComponent>(go: IGameObject | Object3D | null, typeName: Constructor<T>, arr: T[] | null = null): T[] {
        if (go === null) return arr ?? [];
        return getComponents(go, typeName, arr);
    }

    public static findByGuid(guid: string, hierarchy: Object3D): GameObject | Component | null | undefined {
        const res = findByGuid(guid, hierarchy);
        return res as GameObject | Component | null | undefined;
    }

    public static findObjectOfType<T extends IComponent>(typeName: Constructor<T>, context?: Context | Object3D, includeInactive: boolean = true): T | null {
        return findObjectOfType(typeName, context ?? Context.Current, includeInactive);
    }

    public static findObjectsOfType<T extends IComponent>(typeName: Constructor<T>, context?: Context | Object3D): Array<T> {
        const arr = [];
        findObjectsOfType(typeName, arr, context);
        return arr;
    }

    public static getComponentInChildren<T extends IComponent>(go: IGameObject | Object3D, typeName: Constructor<T>): T | null {
        return getComponentInChildren(go, typeName);
    }

    public static getComponentsInChildren<T extends IComponent>(go: IGameObject | Object3D, typeName: Constructor<T>, arr: T[] | null = null): Array<T> {
        return getComponentsInChildren<T>(go, typeName, arr ?? undefined) as T[]
    }

    public static getComponentInParent<T extends IComponent>(go: IGameObject | Object3D, typeName: Constructor<T>): T | null {
        return getComponentInParent(go, typeName);
    }

    public static getComponentsInParent<T extends IComponent>(go: IGameObject | Object3D, typeName: Constructor<T>, arr: Array<T> | null = null): Array<T> {
        return getComponentsInParent(go, typeName, arr);
    }

    public static getAllComponents(go: IGameObject | Object3D): Component[] {
        const componentsList = go.userData?.components;
        if (!componentsList) return [];
        const newList = [...componentsList];
        return newList;
    }

    public static *iterateComponents(go: IGameObject | Object3D) {
        const list = go?.userData?.components;
        if (list && Array.isArray(list)) {
            for (let i = 0; i < list.length; i++) {
                yield list[i];
            }
        }
    }
}



/** 
 * Needle Engine component base class. 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}. 
 * 
 * The most common lifecycle methods are {@link update}, {@link awake}, {@link start}, {@link onEnable}, {@link onDisable} and {@link onDestroy}.   
 * 
 * XR specific callbacks include {@link onEnterXR}, {@link onLeaveXR}, {@link onUpdateXR}, {@link onXRControllerAdded} and {@link onXRControllerRemoved}.    
 * 
 * To receive pointer events implement {@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");
 *  }
 *  update() {
 *    console.log("Frame", this.context.time.frame);
 *  }
 * }
 * ```
 * 
 * @group Components
 */
export abstract class Component implements IComponent, EventTarget,
    Partial<INeedleXRSessionEventReceiver>,
    Partial<IPointerEventHandler>
{
    /** @internal */
    get isComponent(): boolean { return true; }

    private __context: Context | undefined;
    /** Use the context to get access to many Needle Engine features and use physics, timing, access the camera or scene */
    get context(): Context {
        return this.__context ?? Context.Current;
    }
    set context(context: Context) {
        this.__context = context;
    }
    /** shorthand for `this.context.scene`
     * @returns the scene of the context  */
    get scene(): Scene { return this.context.scene; }

    /** @returns the layer of the gameObject this component is attached to */
    get layer(): number {
        return this.gameObject?.userData?.layer;
    }

    /** @returns the name of the gameObject this component is attached to */
    get name(): string {
        if (this.gameObject?.name) {
            return this.gameObject.name;
        }
        return this.gameObject?.userData.name;
    }
    private __name?: string;
    set name(str: string) {
        if (this.gameObject) {
            if (!this.gameObject.userData) this.gameObject.userData = {}
            this.gameObject.userData.name = str;
            this.__name = str;
        }
        else {
            this.__name = str;
        }
    }
    /** @returns the tag of the gameObject this component is attached to */
    get tag(): string {
        return this.gameObject?.userData.tag;
    }
    set tag(str: string) {
        if (this.gameObject) {
            if (!this.gameObject.userData) this.gameObject.userData = {}
            this.gameObject.userData.tag = str;
        }
    }
    /** Is the gameObject marked as static */
    get static() {
        return this.gameObject?.userData.static;
    }
    set static(value: boolean) {
        if (this.gameObject) {
            if (!this.gameObject.userData) this.gameObject.userData = {}
            this.gameObject.userData.static = value;
        }
    }
    get hideFlags(): HideFlags {
        return this.gameObject?.userData.hideFlags;
    }

    /** @returns true if the object is enabled and active in the hierarchy */
    get activeAndEnabled(): boolean {
        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;
    }

    private get __isActive(): boolean {
        return this.gameObject.visible;
    }
    private get __isActiveInHierarchy(): boolean {
        if (!this.gameObject) return false;
        const res = this.gameObject[activeInHierarchyFieldName];
        if (res === undefined) return true;
        return res;
    }

    private set __isActiveInHierarchy(val: boolean) {
        if (!this.gameObject) return;
        this.gameObject[activeInHierarchyFieldName] = val;
    }

    /** the object this component is attached to. Note that this is a threejs Object3D with some additional features */
    gameObject!: GameObject;
    /** the unique identifier for this component */
    guid: string = "invalid";
    /** holds the source identifier this object was created with/from (e.g. if it was part of a glTF file the sourceId holds the url to the glTF) */
    sourceId?: SourceIdentifier;
    // transform: Object3D = nullObject;

    /** called on a component with a map of old to new guids (e.g. when instantiate generated new guids and e.g. timeline track bindings needs to remape them) */
    resolveGuids?(guidsMap: GuidsMap): void;

    /** called once when the component becomes active for the first time (once per component)   
     * This is the first callback to be called */
    awake() { }
    /** called every time when the component gets enabled (this is invoked after awake and before start)   
     * or when it becomes active in the hierarchy (e.g. if a parent object or this.gameObject gets set to visible)
    */
    onEnable() { }
    /** called every time the component gets disabled or if a parent object (or this.gameObject) gets set to invisible */
    onDisable() { }
    /** Called when the component gets destroyed */
    onDestroy() {
        this.__destroyed = true;
    }
    /** called when you decorate fields with the @validate() decorator
     * @param prop the name of the field that was changed
     */
    onValidate?(prop?: string): void;

    /** Called for all scripts when the context gets paused or unpaused */
    onPausedChanged?(isPaused: boolean, wasPaused: boolean): void;

    /** called at the beginning of a frame (once per component) */
    start?(): void;
    /** first callback in a frame (called every frame when implemented) */
    earlyUpdate?(): void;
    /** regular callback in a frame (called every frame when implemented) */
    update?(): void;
    /** late callback in a frame (called every frame when implemented) */
    lateUpdate?(): void;
    /** called before the scene gets rendered in the main update loop */
    onBeforeRender?(frame: XRFrame | null): void;
    /** called after the scene was rendered */
    onAfterRender?(): void;

    onCollisionEnter?(col: Collision);
    onCollisionExit?(col: Collision);
    onCollisionStay?(col: Collision);

    onTriggerEnter?(col: ICollider);
    onTriggerStay?(col: ICollider);
    onTriggerExit?(col: ICollider);


    /** Optional callback, you can implement this to only get callbacks for VR or AR sessions if necessary. 
     * @returns true if the mode is supported (if false the mode is not supported by this component and it will not receive XR callbacks for this mode)
    */
    supportsXR?(mode: XRSessionMode): boolean;
    /** Called before the XR session is requested. Use this callback if you want to modify the session init features */
    onBeforeXR?(mode: XRSessionMode, args: XRSessionInit): void;
    /** Callback when this component joins a xr session (or becomes active in a running XR session) */
    onEnterXR?(args: NeedleXREventArgs): void;
    /** Callback when a xr session updates (while it is still active in XR session) */
    onUpdateXR?(args: NeedleXREventArgs): void;
    /** Callback when this component exists a xr session (or when it becomes inactive in a running XR session) */
    onLeaveXR?(args: NeedleXREventArgs): void;
    /** Callback when a controller is connected/added while in a XR session  
     * OR when the component joins a running XR session that has already connected controllers  
     * OR when the component becomes active during a running XR session that has already connected controllers */
    onXRControllerAdded?(args: NeedleXRControllerEventArgs): void;
    /** callback when a controller is removed while in a XR session   
     * OR when the component becomes inactive during a running XR session
    */
    onXRControllerRemoved?(args: NeedleXRControllerEventArgs): void;


    /* IPointerEventReceiver */
    /* @inheritdoc */
    onPointerEnter?(args: PointerEventData);
    onPointerMove?(args: PointerEventData);
    onPointerExit?(args: PointerEventData);
    onPointerDown?(args: PointerEventData);
    onPointerUp?(args: PointerEventData);
    onPointerClick?(args: PointerEventData);


    /** starts a coroutine (javascript generator function)   
     * `yield` will wait for the next frame:  
     * - Use `yield WaitForSeconds(1)` to wait for 1 second.
     * - Use `yield WaitForFrames(10)` to wait for 10 frames.
     * - Use `yield new Promise(...)` to wait for a promise to resolve.  
     * @param routine generator function to start
     * @param evt event to register the coroutine for (default: FrameEvent.Update). Note that all coroutine FrameEvent callbacks are invoked after the matching regular component callbacks. For example `FrameEvent.Update` will be called after regular component `update()` methods)  
     * @returns the generator function (use it to stop the coroutine with `stopCoroutine`)
     * @example 
     * ```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: Generator, evt: FrameEvent = FrameEvent.Update): Generator {
        return this.context.registerCoroutineUpdate(this, routine, evt);
    }
    /**
     * Stop a coroutine that was previously started with `startCoroutine`
     * @param routine the routine to be stopped
     * @param evt the frame event to unregister the routine from (default: FrameEvent.Update)
     */
    stopCoroutine(routine: Generator, evt: FrameEvent = FrameEvent.Update): void {
        this.context.unregisterCoroutineUpdate(routine, evt);
    }

    /** @returns true if this component was destroyed (`this.destroy()`) or the whole object this component was part of */
    public get destroyed(): boolean {
        return this.__destroyed;
    }

    /**
     * Destroys this component (and removes it from the object)
     */
    public destroy() {
        if (this.__destroyed) return;
        this.__internalDestroy();
    }



    /** @internal */
    protected __didAwake: boolean = false;

    /** @internal */
    private __didStart: boolean = false;

    /** @internal */
    protected __didEnable: boolean = false;

    /** @internal */
    protected __isEnabled: boolean | undefined = undefined;

    /** @internal */
    private __destroyed: boolean = false;

    /** @internal */
    get __internalDidAwakeAndStart() { return this.__didAwake && this.__didStart; }


    /** @internal */
    constructor(init?: ComponentInit<Component>) {
        this.__didAwake = false;
        this.__didStart = false;
        this.__didEnable = false;
        this.__isEnabled = undefined;
        this.__destroyed = false;
        this._internalInit(init as ComponentInit<this>);
    }

    /** @internal */
    __internalNewInstanceCreated(init?: ComponentInit<this>): this {
        this.__didAwake = false;
        this.__didStart = false;
        this.__didEnable = false;
        this.__isEnabled = undefined;
        this.__destroyed = false;
        this._internalInit(init);
        return this;
    }

    _internalInit(init?: ComponentInit<this>) {
        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 as any)[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?: boolean): boolean {
        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?: boolean) {
        // 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 as any);
    }


    get enabled(): boolean {
        return typeof this.__isEnabled === "boolean" ? this.__isEnabled : true; // if it has no enabled field it is always enabled
    }
    set enabled(val: boolean) {
        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();
        }
    }

    get worldPosition(): Vector3 {
        return threeutils.getWorldPosition(this.gameObject);
    }

    set worldPosition(val: Vector3) {
        threeutils.setWorldPosition(this.gameObject, val);
    }

    setWorldPosition(x: number, y: number, z: number) {
        threeutils.setWorldPositionXYZ(this.gameObject, x, y, z);
    }


    get worldQuaternion(): Quaternion {
        return threeutils.getWorldQuaternion(this.gameObject);
    }
    set worldQuaternion(val: Quaternion) {
        threeutils.setWorldQuaternion(this.gameObject, val);
    }
    setWorldQuaternion(x: number, y: number, z: number, w: number) {
        threeutils.setWorldQuaternionXYZW(this.gameObject, x, y, z, w);
    }

    // world euler (in radians)
    get worldEuler(): Euler {
        return threeutils.getWorldEuler(this.gameObject);
    }

    // world euler (in radians)
    set worldEuler(val: Euler) {
        threeutils.setWorldEuler(this.gameObject, val);
    }

    // returns rotation in degrees
    get worldRotation(): Vector3 {
        return this.gameObject.worldRotation;;
    }

    set worldRotation(val: Vector3) {
        this.setWorldRotation(val.x, val.y, val.z, true);
    }

    setWorldRotation(x: number, y: number, z: number, degrees: boolean = true) {
        threeutils.setWorldRotationXYZ(this.gameObject, x, y, z, degrees);
    }

    private static _forward: Vector3 = new Vector3();
    /** Forward (0,0,-1) vector in world space */
    public get forward(): Vector3 {
        return Component._forward.set(0, 0, -1).applyQuaternion(this.worldQuaternion);
    }
    private static _right: Vector3 = new Vector3();
    /** Right (1,0,0) vector in world space */
    public get right(): Vector3 {
        return Component._right.set(1, 0, 0).applyQuaternion(this.worldQuaternion);
    }
    private static _up: Vector3 = new Vector3();
    /** Up (0,1,0) vector in world space */
    public get up(): Vector3 {
        return Component._up.set(0, 1, 0).applyQuaternion(this.worldQuaternion);
    }



    // EventTarget implementation:

    private _eventListeners = new Map<string, EventListener[]>();

    addEventListener<T extends Event>(type: string, listener: (evt: T) => any) {
        this._eventListeners[type] = this._eventListeners[type] || [];
        this._eventListeners[type].push(listener);
    }

    removeEventListener<T extends Event>(type: string, listener: (arg: T) => any) {
        if (!this._eventListeners[type]) return;
        const index = this._eventListeners[type].indexOf(listener);
        if (index >= 0) this._eventListeners[type].splice(index, 1);
    }

    dispatchEvent(evt: Event): boolean {
        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 };
