var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { Mathf } from "../engine/engine_math.js";
import { serializable } from "../engine/engine_serialization_decorator.js";
import { getParam } from "../engine/engine_utils.js";
import { getObjectAnimated } from "./AnimationUtils.js";
import { AnimatorController } from "./AnimatorController.js";
import { Behaviour } from "./Component.js";
const debug = getParam("debuganimator");
/**
 * Animator plays and manages state-machine based animations on a GameObject.
 * Uses an {@link AnimatorController} for state transitions, blending, and parameters.
 *
 * **State machine animations:**
 * Define animation states and transitions in Unity's Animator window or in [Blender's Animator Controller editor](https://engine.needle.tools/docs/blender/animation.html)
 * Control transitions via parameters (bool, int, float, trigger).
 *
 * ![](https://cloud.needle.tools/-/media/zXQhLgtxr5ZaxLDTDb3MXA.gif)
 *
 * **Creating at runtime:**
 * Use `AnimatorController.createFromClips()` to create controllers from code.
 *
 * **Parameters:**
 * - `setTrigger(name)` - Trigger a one-shot transition
 * - `setBool(name, value)` - Set boolean parameter
 * - `setFloat(name, value)` - Set float parameter
 * - `setInteger(name, value)` - Set integer parameter
 *
 * @example Trigger animation state
 * ```ts
 * const animator = myCharacter.getComponent(Animator);
 * animator.setTrigger("Jump");
 * animator.setFloat("Speed", 5);
 * animator.setBool("IsRunning", true);
 * ```
 *
 * @example Listen to animation events
 * ```ts
 * animator.onLoop(evt => console.log("Animation looped"));
 * animator.onFinished(evt => console.log("Animation finished"));
 * ```
 *
 * @summary Plays and manages animations on a GameObject based on an AnimatorController
 * @category Animation and Sequencing
 * @group Components
 * @see {@link AnimatorController} for state machine configuration
 * @see {@link Animation} for simple clip playback
 * @see {@link PlayableDirector} for timeline-based animation
 *
 * @link https://engine.needle.tools/docs/blender/animation.html
 */
export class Animator extends Behaviour {
    /**
     * Identifies this component as an animation component in the engine
     */
    get isAnimationComponent() {
        return true;
    }
    /**
     * When enabled, animation will affect the root transform position and rotation
     */
    applyRootMotion = false;
    /**
     * Indicates whether this animator contains root motion data
     */
    hasRootMotion = false;
    /**
     * When enabled, the animator will maintain its state when the component is disabled
     */
    keepAnimatorControllerStateOnDisable = false;
    // set from needle animator extension
    /**
     * Sets or replaces the animator controller for this component.
     * Handles binding the controller to this animator instance and ensures
     * proper initialization when the controller changes.
     * @param val The animator controller model or instance to use
     */
    set runtimeAnimatorController(val) {
        if (this._animatorController && this._animatorController.model === val) {
            return;
        }
        if (val) {
            if (!(val instanceof AnimatorController)) {
                if (debug)
                    console.log("Assign animator controller", val, this);
                this._animatorController = new AnimatorController(val);
                if (this.__didAwake)
                    this._animatorController.bind(this);
            }
            else {
                if (val.animator && val.animator !== this) {
                    console.warn("AnimatorController can not be bound to multiple animators", val.model?.name);
                    if (!val.model) {
                        console.error("AnimatorController has no model");
                    }
                    val = new AnimatorController(val.model);
                }
                this._animatorController = val;
                this._animatorController.bind(this);
            }
        }
        else
            this._animatorController = null;
    }
    /**
     * Gets the current animator controller instance
     * @returns The current animator controller or null if none is assigned
     */
    get runtimeAnimatorController() {
        return this._animatorController;
    }
    /**
     * Retrieves information about the current animation state
     * @returns The current state information, or undefined if no state is playing
     */
    getCurrentStateInfo() {
        return this.runtimeAnimatorController?.getCurrentStateInfo();
    }
    /**
     * The currently playing animation action that can be used to modify animation properties
     * @returns The current animation action, or null if no animation is playing
     */
    get currentAction() {
        return this.runtimeAnimatorController?.currentAction || null;
    }
    /**
     * Indicates whether animation parameters have been modified since the last update
     * @returns True if parameters have been changed
     */
    get parametersAreDirty() { return this._parametersAreDirty; }
    _parametersAreDirty = false;
    /**
     * Indicates whether the animator state has changed since the last update
     * @returns True if the animator has been changed
     */
    get isDirty() { return this._isDirty; }
    _isDirty = false;
    /**@deprecated use play() */
    Play(name, layer = -1, normalizedTime = Number.NEGATIVE_INFINITY, transitionDurationInSec = 0) { this.play(name, layer, normalizedTime, transitionDurationInSec); }
    /**
     * Plays an animation on the animator
     * @param name The name or hash of the animation to play
     * @param layer The layer to play the animation on (-1 for default layer)
     * @param normalizedTime The time position to start playing (0-1 range, NEGATIVE_INFINITY for current position)
     * @param transitionDurationInSec The duration of the blend transition in seconds
     */
    play(name, layer = -1, normalizedTime = Number.NEGATIVE_INFINITY, transitionDurationInSec = 0) {
        this.runtimeAnimatorController?.play(name, layer, normalizedTime, transitionDurationInSec);
        this._isDirty = true;
    }
    /**@deprecated use reset */
    Reset() { this.reset(); }
    /**
     * Resets the animator controller to its initial state
     */
    reset() {
        this._animatorController?.reset();
        this._isDirty = true;
    }
    /**@deprecated use setBool */
    SetBool(name, val) { this.setBool(name, val); }
    /**
     * Sets a boolean parameter in the animator
     * @param name The name or hash of the parameter
     * @param value The boolean value to set
     */
    setBool(name, value) {
        if (debug)
            console.log("setBool", name, value);
        if (this.runtimeAnimatorController?.getBool(name) !== value)
            this._parametersAreDirty = true;
        this.runtimeAnimatorController?.setBool(name, value);
    }
    /**@deprecated use getBool */
    GetBool(name) { return this.getBool(name); }
    /**
     * Gets a boolean parameter from the animator
     * @param name The name or hash of the parameter
     * @returns The value of the boolean parameter, or false if not found
     */
    getBool(name) {
        const res = this.runtimeAnimatorController?.getBool(name) ?? false;
        if (debug)
            console.log("getBool", name, res);
        return res;
    }
    /**
     * Toggles a boolean parameter between true and false
     * @param name The name or hash of the parameter
     */
    toggleBool(name) {
        this.setBool(name, !this.getBool(name));
    }
    /**@deprecated use setFloat */
    SetFloat(name, val) { this.setFloat(name, val); }
    /**
     * Sets a float parameter in the animator
     * @param name The name or hash of the parameter
     * @param val The float value to set
     */
    setFloat(name, val) {
        if (this.runtimeAnimatorController?.getFloat(name) !== val)
            this._parametersAreDirty = true;
        if (debug)
            console.log("setFloat", name, val);
        this.runtimeAnimatorController?.setFloat(name, val);
    }
    /**@deprecated use getFloat */
    GetFloat(name) { return this.getFloat(name); }
    /**
     * Gets a float parameter from the animator
     * @param name The name or hash of the parameter
     * @returns The value of the float parameter, or -1 if not found
     */
    getFloat(name) {
        const res = this.runtimeAnimatorController?.getFloat(name) ?? -1;
        if (debug)
            console.log("getFloat", name, res);
        return res;
    }
    /**@deprecated use setInteger */
    SetInteger(name, val) { this.setInteger(name, val); }
    /**
     * Sets an integer parameter in the animator
     * @param name The name or hash of the parameter
     * @param val The integer value to set
     */
    setInteger(name, val) {
        if (this.runtimeAnimatorController?.getInteger(name) !== val)
            this._parametersAreDirty = true;
        if (debug)
            console.log("setInteger", name, val);
        this.runtimeAnimatorController?.setInteger(name, val);
    }
    /**@deprecated use getInteger */
    GetInteger(name) { return this.getInteger(name); }
    /**
     * Gets an integer parameter from the animator
     * @param name The name or hash of the parameter
     * @returns The value of the integer parameter, or -1 if not found
     */
    getInteger(name) {
        const res = this.runtimeAnimatorController?.getInteger(name) ?? -1;
        if (debug)
            console.log("getInteger", name, res);
        return res;
    }
    /**@deprecated use setTrigger */
    SetTrigger(name) { this.setTrigger(name); }
    /**
     * Activates a trigger parameter in the animator
     * @param name The name or hash of the trigger parameter
     */
    setTrigger(name) {
        this._parametersAreDirty = true;
        if (debug)
            console.log("setTrigger", name);
        this.runtimeAnimatorController?.setTrigger(name);
    }
    /**@deprecated use resetTrigger */
    ResetTrigger(name) { this.resetTrigger(name); }
    /**
     * Resets a trigger parameter in the animator
     * @param name The name or hash of the trigger parameter
     */
    resetTrigger(name) {
        this._parametersAreDirty = true;
        if (debug)
            console.log("resetTrigger", name);
        this.runtimeAnimatorController?.resetTrigger(name);
    }
    /**@deprecated use getTrigger */
    GetTrigger(name) { this.getTrigger(name); }
    /**
     * Gets the state of a trigger parameter from the animator
     * @param name The name or hash of the trigger parameter
     * @returns The state of the trigger parameter
     */
    getTrigger(name) {
        const res = this.runtimeAnimatorController?.getTrigger(name);
        if (debug)
            console.log("getTrigger", name, res);
        return res;
    }
    /**@deprecated use isInTransition */
    IsInTransition() { return this.isInTransition(); }
    /**
     * Checks if the animator is currently in a transition between states
     * @returns True if the animator is currently blending between animations
     */
    isInTransition() {
        return this.runtimeAnimatorController?.isInTransition() ?? false;
    }
    /**@deprecated use setSpeed */
    SetSpeed(speed) { return this.setSpeed(speed); }
    /**
     * Sets the playback speed of the animator
     * @param speed The new playback speed multiplier
     */
    setSpeed(speed) {
        if (speed === this._speed)
            return;
        if (debug)
            console.log("setSpeed", speed);
        this._speed = speed;
        if (this._animatorController?.animator == this)
            this._animatorController.setSpeed(speed);
    }
    /**
     * Sets a random playback speed between the min and max values
     * @param minMax Object with x (minimum) and y (maximum) speed values
     */
    set minMaxSpeed(minMax) {
        this._speed = Mathf.lerp(minMax.x, minMax.y, Math.random());
        if (this._animatorController?.animator == this)
            this._animatorController.setSpeed(this._speed);
    }
    /**
     * Sets a random normalized time offset for animations between min (x) and max (y) values
     * @param minMax Object with x (min) and y (max) values for the offset range
     */
    set minMaxOffsetNormalized(minMax) {
        this._normalizedStartOffset = Mathf.lerp(minMax.x, minMax.y, Math.random());
        if (this.runtimeAnimatorController?.animator == this)
            this.runtimeAnimatorController.normalizedStartOffset = this._normalizedStartOffset;
    }
    _speed = 1;
    _normalizedStartOffset = 0;
    _animatorController = null;
    awake() {
        if (debug)
            console.log("ANIMATOR", this.name, this);
        if (!this.gameObject)
            return;
        this.initializeRuntimeAnimatorController();
    }
    // Why do we jump through hoops like this? It's because of the PlayableDirector and animation tracks
    // they NEED to use the same mixer when binding/creating the animation clips
    // so when the playable director runs it takes over updating the mixer for blending and then calls the runtimeAnimatorController.update
    // so they effectively share the same mixer. There might be cases still where not the same mixer is being used but then the animation track prints an error in dev
    _initializeWithRuntimeAnimatorController;
    initializeRuntimeAnimatorController(force = false) {
        const shouldRun = (force || this.runtimeAnimatorController !== this._initializeWithRuntimeAnimatorController);
        if (this.runtimeAnimatorController && shouldRun) {
            const clone = this.runtimeAnimatorController.clone();
            this._initializeWithRuntimeAnimatorController = clone;
            if (clone) {
                console.assert(this.runtimeAnimatorController !== clone);
                this.runtimeAnimatorController = clone;
                console.assert(this.runtimeAnimatorController === clone);
                this.runtimeAnimatorController.bind(this);
                this.runtimeAnimatorController.setSpeed(this._speed);
                this.runtimeAnimatorController.normalizedStartOffset = this._normalizedStartOffset;
            }
            else
                console.warn("Could not clone animator controller", this.runtimeAnimatorController);
        }
    }
    onDisable() {
        if (!this.keepAnimatorControllerStateOnDisable)
            this._animatorController?.reset();
    }
    onBeforeRender() {
        this._isDirty = false;
        this._parametersAreDirty = false;
        const isAnimatedExternally = getObjectAnimated(this.gameObject);
        if (isAnimatedExternally)
            return;
        if (this._animatorController) {
            this._animatorController.update(1);
        }
    }
}
__decorate([
    serializable()
], Animator.prototype, "applyRootMotion", void 0);
__decorate([
    serializable()
], Animator.prototype, "hasRootMotion", void 0);
__decorate([
    serializable()
], Animator.prototype, "keepAnimatorControllerStateOnDisable", void 0);
__decorate([
    serializable()
], Animator.prototype, "runtimeAnimatorController", null);
//# sourceMappingURL=Animator.js.map