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 { Object3D, Quaternion, Vector3 } from "three";
import { isDevEnvironment, showBalloonWarning } from "../engine/debug/index.js";
import { WaitForSeconds } from "../engine/engine_coroutine.js";
import { InstantiateOptions } from "../engine/engine_gameobject.js";
import { InstantiateIdProvider } from "../engine/engine_networking_instantiate.js";
import { serializable } from "../engine/engine_serialization_decorator.js";
import { Behaviour, GameObject } from "./Component.js";
import { DragControls, DragMode } from "./DragControls.js";
import { SyncedTransform } from "./SyncedTransform.js";
import { ObjectRaycaster } from "./ui/Raycaster.js";
/**
 * The [Duplicatable](https://engine.needle.tools/docs/api/Duplicatable) component creates clones of a GameObject when clicked/tapped/dragged.
 * Perfect for spawning objects, creating drag-and-drop inventories, or multiplayer object creation.
 *
 * ![](https://cloud.needle.tools/-/media/J_ij9vxhh1zhS8h2ftGBXQ.gif)
 *
 * **How it works:**
 * - When the user clicks on this object, it creates a clone of the assigned `object`
 * - The clone is automatically set up with {@link DragControls} so users can drag it
 * - If networking is enabled, clones are synced via {@link SyncedTransform}
 * - Rate limiting prevents spam (controlled by `limitCount`)
 *
 * **Setup tips:**
 * - Assign `object` to a template object (it will be hidden and used as source)
 * - If `object` is not assigned, the component's own GameObject is used as template
 * - Add an {@link ObjectRaycaster} to enable pointer detection (added automatically if missing)
 *
 * @example Basic duplicatable button
 * ```ts
 * const duplicatable = spawnButton.addComponent(Duplicatable);
 * duplicatable.object = templateObject; // Object to clone
 * duplicatable.parent = spawnContainer;  // Where to place clones
 * duplicatable.limitCount = 10;          // Max 10 per second
 * ```
 *
 * @summary Duplicates a GameObject on pointer events
 * @category Interactivity
 * @group Components
 * @see {@link DragControls} for dragging the duplicated objects
 * @see {@link SyncedTransform} for networking support
 * @see {@link GameObject.instantiateSynced} for the underlying instantiation
 * @link https://engine.needle.tools/samples/collaborative-sandbox/
 */
export class Duplicatable extends Behaviour {
    /**
     * Parent object for spawned duplicates.
     * If not set, duplicates are parented to this GameObject's parent.
     */
    parent = null;
    /**
     * Template object to duplicate. This object will be hidden and used as the source for clones.
     * If not assigned, this GameObject itself is used as the template.
     */
    object = null;
    /**
     * Maximum duplications allowed per second to prevent spam.
     * The counter decreases by 1 each second.
     * @default 60
     */
    limitCount = 60;
    _currentCount = 0;
    _startPosition = null;
    _startQuaternion = null;
    start() {
        this._currentCount = 0;
        this._startPosition = null;
        this._startQuaternion = null;
        if (!this.object) {
            this.object = this.gameObject;
        }
        if (this.object) {
            if (this.object === this.gameObject) {
                // console.error("Can not duplicate self");
                // return;
                const instanceIdProvider = new InstantiateIdProvider(this.guid);
                this.object = GameObject.instantiate(this.object, { idProvider: instanceIdProvider, keepWorldPosition: false, });
                const duplicatable = GameObject.getComponent(this.object, Duplicatable);
                duplicatable?.destroy();
                let dragControls = this.object.getComponentInChildren(DragControls);
                if (!dragControls) {
                    dragControls = this.object.addComponent(DragControls, {
                        dragMode: DragMode.SnapToSurfaces
                    });
                    dragControls.guid = instanceIdProvider.generateUUID();
                }
                let syncedTransfrom = GameObject.getComponent(dragControls.gameObject, SyncedTransform);
                if (!syncedTransfrom) {
                    syncedTransfrom = dragControls.gameObject.addComponent(SyncedTransform);
                    syncedTransfrom.guid = instanceIdProvider.generateUUID();
                }
            }
            this.object.visible = false;
            // legacy – DragControls was required for duplication and so often the component is still there; we work around that by disabling it here
            const dragControls = this.gameObject.getComponent(DragControls);
            if (dragControls) {
                // if (isDevEnvironment()) console.warn(`Please remove DragControls from \"${dragControls.name}\": it's not needed anymore when the object also has a Duplicatable component`);
                dragControls.enabled = false;
            }
            // when this is in a moveable parent in multiuser scenario somehow the object position gets an offset and might stay that way
            // this is just a workaround to set the object position before duplicating
            this._startPosition = this.object.position?.clone() ?? new Vector3(0, 0, 0);
            this._startQuaternion = this.object.quaternion?.clone() ?? new Quaternion(0, 0, 0, 1);
        }
        if (!this.gameObject.getComponentInParent(ObjectRaycaster))
            this.gameObject.addComponent(ObjectRaycaster);
    }
    onEnable() {
        this.startCoroutine(this.cloneLimitIntervalFn());
    }
    _forwardPointerEvents = new Map();
    onPointerEnter(args) {
        if (args.used)
            return;
        if (!this.object)
            return;
        if (!this.context.connection.allowEditing)
            return;
        if (args.button !== 0)
            return;
        this.context.input.setCursor("pointer");
    }
    onPointerExit(args) {
        if (args.used)
            return;
        if (!this.object)
            return;
        if (!this.context.connection.allowEditing)
            return;
        if (args.button !== 0)
            return;
        this.context.input.unsetCursor("pointer");
    }
    /** @internal */
    onPointerDown(args) {
        if (args.used)
            return;
        if (!this.object)
            return;
        if (!this.context.connection.allowEditing)
            return;
        if (args.button !== 0)
            return;
        const res = this.handleDuplication();
        if (res) {
            const dragControls = GameObject.getComponent(res, DragControls);
            if (!dragControls) {
                if (isDevEnvironment())
                    console.warn(`Duplicated object (${res.name}) does not have DragControls`);
            }
            else {
                dragControls.onPointerDown(args);
                this._forwardPointerEvents.set(args.event.space, dragControls);
            }
        }
        else {
            if (this._currentCount >= this.limitCount) {
                console.warn(`[Duplicatable] Limit of ${this.limitCount} objects created within a few seconds reached. Please wait a moment before creating more objects.`);
            }
            else {
                console.warn(`[Duplicatable] Could not duplicate object.`);
            }
        }
    }
    /** @internal */
    onPointerUp(args) {
        if (args.used)
            return;
        const dragControls = this._forwardPointerEvents.get(args.event.space);
        if (dragControls) {
            dragControls.onPointerUp(args);
            this._forwardPointerEvents.delete(args.event.space);
        }
    }
    *cloneLimitIntervalFn() {
        while (this.activeAndEnabled && !this.destroyed) {
            if (this._currentCount > 0) {
                this._currentCount -= 1;
            }
            else if (this._currentCount < 0) {
                this._currentCount = 0;
            }
            yield WaitForSeconds(1);
        }
    }
    handleDuplication() {
        if (!this.object)
            return null;
        if (this.limitCount > 0 && this._currentCount >= this.limitCount)
            return null;
        if (this.object === this.gameObject)
            return null;
        if (GameObject.isDestroyed(this.object)) {
            this.object = null;
            return null;
        }
        if (this.object.matrixAutoUpdate === false) {
            this.object.updateMatrix();
            if (isDevEnvironment()) {
                console.warn(`Object "${this.object.name}" has matrixAutoUpdate disabled. This can cause duplicated objects to have incorrect position/rotation/scale. Consider enabling matrixAutoUpdate or calling updateMatrix() before duplication.`);
                showBalloonWarning("Duplicatable: Object has matrixAutoUpdate disabled");
            }
        }
        this.object.visible = true;
        if (this._startPosition)
            this.object.position.copy(this._startPosition);
        if (this._startQuaternion)
            this.object.quaternion.copy(this._startQuaternion);
        const opts = new InstantiateOptions();
        if (!this.parent)
            this.parent = this.gameObject.parent;
        if (this.parent) {
            opts.parent = this.parent.guid ?? this.parent.userData?.guid;
            opts.keepWorldPosition = true;
        }
        opts.position = this.worldPosition;
        opts.rotation = this.worldQuaternion;
        opts.context = this.context;
        this._currentCount += 1;
        const newInstance = GameObject.instantiateSynced(this.object, opts);
        console.assert(newInstance !== this.object, "Duplicated object is original");
        this.object.visible = false;
        // see if this fixes object being offset when duplicated and dragged - it looks like three clone has shared position/quaternion objects?
        if (this._startPosition)
            this.object.position.clone().copy(this._startPosition);
        if (this._startQuaternion)
            this.object.quaternion.clone().copy(this._startQuaternion);
        return newInstance;
    }
}
__decorate([
    serializable(Object3D)
], Duplicatable.prototype, "parent", void 0);
__decorate([
    serializable(Object3D)
], Duplicatable.prototype, "object", void 0);
__decorate([
    serializable()
], Duplicatable.prototype, "limitCount", void 0);
//# sourceMappingURL=Duplicatable.js.map