import { serializable } from "../../engine/engine_serialization_decorator.js";
import { FrameEvent } from "../../engine/engine_setup.js";
import { DeviceUtilities, getParam } from "../../engine/engine_utils.js";
import { Behaviour, GameObject } from "../Component.js";
import { EventList } from "../EventList.js";
import { type IPointerEventHandler, PointerEventData } from "./PointerEvents.js";
import { Text } from "./Text.js";
import { tryGetUIComponent } from "./Utils.js";

const debug = getParam("debuginputfield");

/**
 * [InputField](https://engine.needle.tools/docs/api/InputField) is a UI component that allows users to enter and edit text.
 * It provides a text input area where users can type, delete, and modify text.
 * The InputField supports placeholder text, events for value changes, and end edit actions.
 * @summary Text field for user input
 * @category User Interface
 * @group Components
 */
export class InputField extends Behaviour implements IPointerEventHandler {

    get text(): string {
        return this.textComponent?.text ?? "";
    }
    set text(value: string) {
        if (this.textComponent) {
            this.textComponent.text = value;
            if (this.placeholder) {
                if (value.length > 0) this.placeholder.gameObject.visible = false;
                else this.placeholder.gameObject.visible = true;
            }
        }
    }

    get isFocused() {
        return InputField.active === this;
    }

    @serializable(Text)
    private textComponent?: Text;

    @serializable(Text)
    private placeholder?: Text;

    @serializable(EventList)
    onValueChanged?: EventList<any>;

    @serializable(EventList)
    onEndEdit?: EventList<any>;


    private static active: InputField | null = null;
    private static activeTime: number = -1;
    private static htmlField: HTMLInputElement | null = null;
    private static htmlFieldFocused: boolean = false;

    private inputEventFn: any;
    private _iosEventFn: any;

    start() {
        if (debug)
            console.log(this.name, this);
    }

    onEnable() {
        if (!InputField.htmlField) {
            InputField.htmlField = document.createElement("input");
            // we can not use visibility or display because it won't receive input then
            InputField.htmlField.style.width = "0px";
            InputField.htmlField.style.height = "0px";
            InputField.htmlField.style.padding = "0px";
            InputField.htmlField.style.border = "none";
            InputField.htmlField.style.overflow = "hidden";
            InputField.htmlField.style.caretColor = "transparent";
            InputField.htmlField.style.outline = "none";
            InputField.htmlField.classList.add("ar");
            InputField.htmlField.onfocus = () => InputField.htmlFieldFocused = true;
            InputField.htmlField.onblur = () => InputField.htmlFieldFocused = false;
            // TODO: instead of this we should add it to the shadowdom?
            document.body.append(InputField.htmlField);
        }
        if (!this.inputEventFn) {
            this.inputEventFn = this.onInput.bind(this);
        }
        // InputField.htmlField.addEventListener("input", this.mobileInputEventListener);
        InputField.htmlField.addEventListener("keyup", this.inputEventFn);
        // InputField.htmlField.addEventListener("change", this.inputEventFn);
        if (this.placeholder && this.textComponent?.text.length) {
            GameObject.setActive(this.placeholder.gameObject, false);
        }
        if (DeviceUtilities.isiOS()) {
            this._iosEventFn = this.processInputOniOS.bind(this);
            window.addEventListener("click", this._iosEventFn);
        }
    }

    onDisable() {
        // InputField.htmlField?.removeEventListener("input", this.mobileInputEventListener);
        InputField.htmlField?.removeEventListener("keyup", this.inputEventFn);
        // InputField.htmlField?.removeEventListener("change", this.inputEventFn);
        this.onDeselected();
        if (this._iosEventFn) {
            window.removeEventListener("click", this._iosEventFn);
        }
    }

    /** Clear the input field if it's currently active */
    clear() {
        if (InputField.active === this && InputField.htmlField) {
            InputField.htmlField.value = "";
            this.setTextFromInputField();
        }
        else {
            if (this.textComponent) this.textComponent.text = "";
            if (this.placeholder) GameObject.setActive(this.placeholder.gameObject, true);
        }
    }

    /** Select the input field, set it active to receive keyboard input */
    select() {
        this.onSelected();
    }

    /** Deselect the input field, stop receiving keyboard input */
    deselect() {
        this.onDeselected();
    }

    onPointerEnter(_args: PointerEventData) {
        const canSetCursor = _args.event.pointerType === "mouse" && _args.button === 0;
        if (canSetCursor) this.context.input.setCursor("text");
    }
    onPointerExit(_args: PointerEventData) {
        this.context.input.unsetCursor("text")
    }

    onPointerClick(_args) {
        if (debug) console.log("CLICK", _args, InputField.active);
        InputField.activeTime = this.context.time.time;
        if (InputField.active !== this) {
            this.startCoroutine(this.activeLoop(), FrameEvent.LateUpdate);
        }
        this.selectInputField();
    }

    private *activeLoop() {
        this.onSelected();
        while (InputField.active === this) {
            if (this.context.input.getPointerClicked(0)) {
                if (this.context.time.time - InputField.activeTime > 0.2) {
                    break;
                }
            }
            this.setTextFromInputField();
            yield;
        }
        this.onDeselected();
    }

    private onSelected() {
        if (InputField.active === this) return;
        if (debug) console.log("Select", this.name, this, InputField.htmlField, this.context.isInXR, this.context.arOverlayElement, this.textComponent?.text, InputField.htmlField?.value);

        InputField.active?.onDeselected();
        InputField.active = this;

        if (this.placeholder)
            GameObject.setActive(this.placeholder.gameObject, false);

        if (InputField.htmlField) {

            InputField.htmlField.value = this.textComponent?.text || "";
            if (debug)
                console.log("set input field value", InputField.htmlField.value);

            if (this.context.isInXR) {
                const overlay = this.context.arOverlayElement;
                if (overlay) {
                    overlay.append(InputField.htmlField)
                }
            }

            this.selectInputField();
        }
    }

    private onDeselected() {
        if (InputField.active !== this) return;
        InputField.active = null;

        if (debug) console.log("Deselect", this.name, this);
        if (InputField.htmlField) {
            InputField.htmlField.blur();
            document.body.append(InputField.htmlField);
        }
        if (this.placeholder && (!this.textComponent || this.textComponent.text.length <= 0))
            GameObject.setActive(this.placeholder.gameObject, true);

        if (InputField.htmlField)
            this.onEndEdit?.invoke(InputField.htmlField.value);
    }

    // @Marwie, I can provide this fix. But the issue seems to comes from Raycaster+EventSystem
    // As we rollout InputField, and no others elements is behind raycast,
    // ThreeMeshUI.update is not called.
    update() {
        if (InputField.active === this) {
            this.textComponent?.markDirty();
        }
    }

    private onInput(evt: KeyboardEvent) {
        if (InputField.active !== this) return;
        if (debug)
            console.log(evt.code, evt, InputField.htmlField?.value, this.textComponent?.text);
        if (evt.code === "Escape" || evt.code === "Enter") {
            this.onDeselected();
            return;
        }
        if (InputField.htmlField) {
            if (this.textComponent) {
                this.setTextFromInputField();
                if (this.placeholder) {
                    GameObject.setActive(this.placeholder.gameObject, this.textComponent.text.length <= 0);
                }
            }
            this.selectInputField();
        }
        // switch (evt.inputType) {
        //     case "insertCompositionText":
        //         this.appendLetter(evt.data?.charAt(evt.data.length - 1) || null);
        //         break;
        //     case "insertText":
        //         console.log(evt.data);
        //         this.appendLetter(evt.data);
        //         break;
        //     case "deleteContentBackward":
        //         this.deleteLetter();
        //         break;
        // }
    }

    private setTextFromInputField() {
        if (this.textComponent && InputField.htmlField) {
            const oldValue = this.textComponent.text;
            const newValue = InputField.htmlField.value;
            const changed = this.textComponent.text !== InputField.htmlField.value;
            this.textComponent.text = InputField.htmlField.value;

            if (changed) {
                if (debug) console.log("[InputField] value changed:", newValue, oldValue);
                this.onValueChanged?.invoke(newValue, oldValue);
            }
        }
    }

    private selectInputField() {
        if (InputField.htmlField) {
            if (debug) console.log("Focus Inputfield", InputField.htmlFieldFocused, InputField.htmlField);

            InputField.htmlField.setSelectionRange(InputField.htmlField.value.length, InputField.htmlField.value.length);

            if (DeviceUtilities.isiOS()) {
                // in WebXR (iOS AR Safari) display can not be none to focus input it seems
                InputField.htmlField.style.display = "block";
                InputField.htmlField.focus({ preventScroll: true });
            }
            else {
                // on Andoid if we don't focus in a timeout the keyboard will close the second time we click the InputField
                setTimeout(() => InputField.htmlField?.focus(), 1);
            }
        }
    }

    private processInputOniOS() {
        // focus() on safari ios doesnt open the keyboard when not processed from dom event
        // so we try in a touch end event if this is hit
        const hits = this.context.physics.raycast();
        if (!hits.length) return;
        const hit = hits[0];
        const obj = hit.object;
        const component = tryGetUIComponent(obj);
        if (component?.gameObject === this.gameObject || component?.gameObject.parent === this.gameObject)
            this.selectInputField();
    }


    // private static _lastDeletionTime: number = 0;
    // private static _lastKeyInputTime: number = 0;

    // TODO: support modifiers, refactor to not use backspace as string etc
    // private handleKey(key: string | null) {
    //     if (!this.textComponent) return;
    //     if (!key) return;

    //     InputField._lastKey = key || "";

    //     const text = this.textComponent.text;
    //     if (debug)
    //         console.log(key, text);
    //     switch (key) {
    //         case "Backspace":
    //             this.deleteLetter();
    //             break;

    //         default:
    //             this.appendLetter(key);
    //             break;
    //     }
    // }

    // private appendLetter(key: string | null) {
    //     if (this.textComponent && key) {
    //         const timeSinceLastInput = this.context.time.time - InputField._lastKeyInputTime;
    //         if (key.length === 1 && (this.context.input.getKeyDown() === key || timeSinceLastInput > .1)) {
    //             this.textComponent.text += key;
    //             InputField._lastKeyInputTime = this.context.time.time;
    //         }
    //     }
    // }

    // private deleteLetter() {
    //     if (this.textComponent) {
    //         const text = this.textComponent.text;
    //         if (text.length > 0 && this.context.time.time - InputField._lastDeletionTime > 0.05) {
    //             this.textComponent.text = text.slice(0, -1);
    //             InputField._lastDeletionTime = this.context.time.time;
    //         }
    //     }
    // }
}