import { showBalloonMessage } from "../../engine/debug/index.js";
import { Gizmos } from "../../engine/engine_gizmos.js";
import { PointerType } from "../../engine/engine_input.js";
import { serializable } from "../../engine/engine_serialization_decorator.js";
import { getParam } from "../../engine/engine_utils.js";
import { RGBAColor } from "../../engine/js-extensions/index.js";
import { Animator } from "../Animator.js";
import { Behaviour, GameObject } from "../Component.js";
import { EventList } from "../EventList.js";
import { Image } from "./Image.js";
import type { IPointerClickHandler, IPointerEnterHandler, IPointerEventHandler, IPointerExitHandler, PointerEventData } from "./PointerEvents.js";
import { GraphicRaycaster, ObjectRaycaster, Raycaster } from "./Raycaster.js";

const debug = getParam("debugbutton");

/// <summary>
///Transition mode for a Selectable.
/// </summary>
export enum Transition {
    /// <summary>
    /// No Transition.
    /// </summary>
    None,
    /// <summary>
    /// Use an color tint transition.
    /// </summary>
    ColorTint,
    /// <summary>
    /// Use a sprite swap transition.
    /// </summary>
    SpriteSwap,
    /// <summary>
    /// Use an animation transition.
    /// </summary>
    Animation
}

class ButtonColors {
    @serializable()
    colorMultiplier!: 1;
    @serializable(RGBAColor)
    disabledColor!: RGBAColor;
    @serializable()
    fadeDuration!: number;
    @serializable(RGBAColor)
    highlightedColor!: RGBAColor;
    @serializable(RGBAColor)
    normalColor!: RGBAColor;
    @serializable(RGBAColor)
    pressedColor!: RGBAColor;
    @serializable(RGBAColor)
    selectedColor!: RGBAColor;
}

class AnimationTriggers {
    disabledTrigger!: string;
    highlightedTrigger!: string;
    normalTrigger!: string;
    pressedTrigger!: string;
    selectedTrigger!: string;
}

/**
 * [Button](https://engine.needle.tools/docs/api/Button) is a UI component that can be clicked to trigger actions.  
 * Supports visual states (normal, highlighted, pressed, disabled) with  
 * color tints or animation transitions.  
 *
 * **Visual transitions:**  
 * - `ColorTint` - Tint the button image with state colors
 * - `Animation` - Trigger animator states for each button state
 * - `SpriteSwap` - Swap sprites for each state (not fully supported)
 *
 * **Requirements:**  
 * - Typically paired with an {@link Image} component for visuals or any 3D object
 *
 * @example Listen to button clicks
 * ```ts
 * const button = myButton.getComponent(Button);
 * button.onClick.addEventListener(() => {
 *   console.log("Button clicked!");
 * });
 * ```
 *
 * @example Programmatically click a button
 * ```ts
 * button.click(); // Triggers onClick event
 * ```
 *
 * @summary UI Button that can be clicked to perform actions
 * @category User Interface
 * @group Components
 * @see {@link EventList} for onClick callback handling
 * @see {@link Image} for button visuals
 * @see {@link GraphicRaycaster} for UI interaction
 * @see {@link Transition} for visual state options
 */
export class Button extends Behaviour implements IPointerEventHandler {

    /**
     * Invokes the onClick event
     */
    click() {
        this.onClick?.invoke();
    }

    @serializable(EventList)
    onClick: EventList<void> = new EventList();

    private _isHovered: number = 0;

    onPointerEnter(evt: PointerEventData) {
        const canSetCursor = evt.event.pointerType === "mouse" && evt.button === 0;
        if (canSetCursor) this._isHovered += 1;
        if (debug)
            console.warn("Button Enter", canSetCursor, this._isHovered, this.animationTriggers?.highlightedTrigger, this.animator);
        if (!this.interactable) return;
        if (this.transition == Transition.Animation && this.animationTriggers && this.animator) {
            this.animator.setTrigger(this.animationTriggers.highlightedTrigger);
        }
        else if (this.transition === Transition.ColorTint && this.colors) {
            this._image?.setState("hovered");
        }
        if (canSetCursor) this.context.input.setCursor("pointer");
    }

    onPointerExit() {
        this._isHovered -= 1;
        if (this._isHovered < 0) this._isHovered = 0;
        if (debug)
            console.log("Button Exit", this._isHovered, this.animationTriggers?.highlightedTrigger, this.animator);
        if (!this.interactable) return;
        if (this._isHovered > 0) return;
        this._isHovered = 0;
        if (this.transition == Transition.Animation && this.animationTriggers && this.animator) {
            this.animator.setTrigger(this.animationTriggers.normalTrigger);
        }
        else if (this.transition === Transition.ColorTint && this.colors) {
            this._image?.setState("normal");
        }
        this.context.input.unsetCursor("pointer");
    }

    onPointerDown(_) {
        if (debug)
            console.log("Button Down", this.animationTriggers?.highlightedTrigger, this.animator);
        if (!this.interactable) return;
        if (this.transition == Transition.Animation && this.animationTriggers && this.animator) {
            this.animator.setTrigger(this.animationTriggers.pressedTrigger);
        }
        else if (this.transition === Transition.ColorTint && this.colors) {
            this._image?.setState("pressed");
        }
    }

    onPointerUp(_) {
        if (debug)
            console.warn("Button Up", this.animationTriggers?.highlightedTrigger, this.animator, this._isHovered);
        if (!this.interactable) return;
        if (this.transition == Transition.Animation && this.animationTriggers && this.animator) {
            this.animator.setTrigger(this._isHovered ? this.animationTriggers.highlightedTrigger : this.animationTriggers.normalTrigger);
        }
        else if (this.transition === Transition.ColorTint && this.colors) {
            this._image?.setState(this._isHovered ? "hovered" : "normal");
        }
    }

    onPointerClick(args: PointerEventData) {
        if (!this.interactable) return;

        if (args.button !== 0 && args.event.pointerType === PointerType.Mouse) return;
        // Button clicks should only run with left mouse button while using mouse
        if (debug) {
            console.warn("Button Click", this.onClick);
            showBalloonMessage("CLICKED button " + this.name + " at " + this.context.time.frameCount);
        }

        // TODO: we can not *always* use the event right now because the hotspot sample is relying on onPointerClick on a parent object
        // and it's not using the button
        if (this.onClick && this.onClick.listenerCount > 0) {
            this.onClick.invoke();
            args.use();

            this.context.accessibility.focus(this);

            // debug clicks for WebXR
            if (debug) {
                const pos = this.gameObject.worldPosition;
                pos.add(this.gameObject.worldUp.multiplyScalar(1 + Math.random() * .5))
                Gizmos.DrawLabel(pos, "CLICK:" + Date.now(), .1, 1 + Math.random() * .5);
            }
        }
    }

    @serializable(ButtonColors)
    colors?: ButtonColors;
    @serializable()
    transition?: Transition;

    @serializable(AnimationTriggers)
    animationTriggers?: AnimationTriggers;

    @serializable(Animator)
    animator?: Animator;

    // @serializable(Image)
    // image? : Image;

    @serializable()
    set interactable(value: boolean) {
        this._interactable = value;
        if (this._image) {
            this._image.setInteractable(value);
            if (value)
                this._image.setState("normal");
            else
                this._image.setState("disabled");
        }
    }
    get interactable(): boolean { return this._interactable; }

    private _interactable: boolean = true;
    private set_interactable(value: boolean) {
        this.interactable = value;
    }

    awake(): void {
        super.awake();
        if (debug)
            console.log(this);
        this._isInit = false;
        this.init();
    }

    start() {
        this._image?.setInteractable(this.interactable);
        if (!this.gameObject.getComponentInParent(Raycaster)) {
            this.gameObject.addComponent(GraphicRaycaster);
        }
    }

    onEnable() {
        super.onEnable();
        this.context.accessibility.updateElement(this, {
            role: "button",
            label: this.gameObject.name + " button",
            hidden: false
        });
    }
    onDisable() {
        super.onDisable();
        this.context.accessibility.updateElement(this, { hidden: true })
    }

    onDestroy(): void {
        this.context.accessibility.removeElement(this);
        if (this._isHovered) this.context.input.unsetCursor("pointer");
    }

    private _requestedAnimatorTrigger?: string;
    private *setAnimatorTriggerAtEndOfFrame(requestedTriggerId: string) {
        this._requestedAnimatorTrigger = requestedTriggerId;
        yield;
        yield;
        if (this._requestedAnimatorTrigger == requestedTriggerId) {
            this.animator?.setTrigger(requestedTriggerId);
        }
    }

    private _isInit: boolean = false;
    private _image?: Image;

    private init() {
        if (this._isInit) return;
        this._isInit = true;
        this._image = GameObject.getComponent(this.gameObject, Image) as Image;
        if (this._image) {
            this.stateSetup(this._image);
            if (this.interactable)
                this._image.setState("normal");
            else
                this._image.setState("disabled");
        }
    }

    private stateSetup(image: Image) {
        image.setInteractable(this.interactable);

        // @marwie :    If this piece of code could be moved to the SimpleStateBehavior instanciation location,
        //              Its setup could be eased :
        // @see https://github.com/felixmariotto/three-mesh-ui/blob/7.1.x/examples/ex__keyboard.js#L407

        const normal = this.getFinalColor(image.color, this.colors?.normalColor);
        const normalState = {
            state: "normal",
            attributes: {
                backgroundColor: normal,
                backgroundOpacity: normal.alpha,
            },
        };
        image.setupState(normalState);

        const hover = this.getFinalColor(image.color, this.colors?.highlightedColor);
        const hoverState = {
            state: "hovered",
            attributes: {
                backgroundColor: hover,
                backgroundOpacity: hover.alpha,
            },
        };
        image.setupState(hoverState);

        const pressed = this.getFinalColor(image.color, this.colors?.pressedColor);
        const pressedState = {
            state: "pressed",
            attributes: {
                backgroundColor: pressed,
                backgroundOpacity: pressed.alpha,
            }
        };
        image.setupState(pressedState);

        const selected = this.getFinalColor(image.color, this.colors?.selectedColor);
        const selectedState = {
            state: "selected",
            attributes: {
                backgroundColor: selected,
                backgroundOpacity: selected.alpha,
            }
        };
        image.setupState(selectedState);

        const disabled = this.getFinalColor(image.color, this.colors?.disabledColor);
        const disabledState = {
            state: "disabled",
            attributes: {
                backgroundColor: disabled,
                // @marwie, this disabled alpha property doesn't seem to have the opacity requested in unity
                backgroundOpacity: disabled.alpha
            }
        };
        image.setupState(disabledState);
    }

    private getFinalColor(col: RGBAColor, col2?: RGBAColor): RGBAColor {
        if (col2) {
            return col.clone().multiply(col2).convertLinearToSRGB() as RGBAColor;
        }
        return col.clone().convertLinearToSRGB() as RGBAColor;
    }
}