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;
};
var ViewBox_1;
import { Matrix4, PerspectiveCamera } from "three";
import { isDevEnvironment } from "../../engine/debug/debug.js";
import { Gizmos } from "../../engine/engine_gizmos.js";
import { serializable } from "../../engine/engine_serialization_decorator.js";
import { getTempVector } from "../../engine/engine_three_utils.js";
import { registerType } from "../../engine/engine_typestore.js";
import { getParam } from "../../engine/engine_utils.js";
import { RGBAColor } from "../../engine/js-extensions/RGBAColor.js";
import { Behaviour } from "../Component.js";
const debugParam = getParam("debugviewbox");
const disabledGizmoColor = new RGBAColor(.5, .5, .5, .5);
/**
 * This component can be used to automatically fit a certain box area into the camera view - no matter your screen size or aspect ratio.
 * This is useful for example to frame a character or object in the center of the screen and ensure it is always fully visible. You can also animate or scale the viewbox to create zoom or framing effects.
 *
 * - [Example on needle.run](https://viewbox-demo-z23hmxbz2gkayo-z1nyzm6.needle.run/)
 * - [Scrollytelling Demo using animated Viewbox](https://scrollytelling-bike-z23hmxb2gnu5a.needle.run/)
 * - [Example on Stackblitz](https://stackblitz.com/edit/needle-engine-view-box-example)
 *
 * @example Add a Viewbox component to an object in your scene
 * ```ts
  const viewBox = new Object3D();
  viewBox.scale.set(0, 0, 0);
  viewBox.addComponent(ViewBox, { debug: true });
  scene.add(viewBox);
 * ```
  
 * @category Camera
 * @group Components
 * @component
 */
let ViewBox = ViewBox_1 = class ViewBox extends Behaviour {
    static instances = [];
    /**
     * The reference field of view is used to calculate the box size. This should usually be the same as your camera's fov.
     * @default -1 (meaning it will use the camera fov on the first frame)
     */
    referenceFieldOfView = -1;
    /**
     * Enable debug logs and rendering for this component instance
     */
    debug = false;
    onEnable() {
        if (debugParam || this.debug || isDevEnvironment())
            console.debug("[ViewBox] Using camera fov:", this.referenceFieldOfView);
        // register instance
        ViewBox_1.instances.push(this);
        this.removeUpdateCallback();
        this.context.pre_render_callbacks.push(this.internalUpdate);
    }
    onDisable() {
        if (debugParam || this.debug)
            console.debug("[ViewBox] Disabled");
        // unregister instance
        const idx = ViewBox_1.instances.indexOf(this);
        if (idx !== -1)
            ViewBox_1.instances.splice(idx, 1);
        this._projectedBoxElement?.remove();
        this.removeUpdateCallback();
    }
    removeUpdateCallback() {
        // remove prerender callback
        const cbIdx = this.context.pre_render_callbacks.indexOf(this.internalUpdate);
        if (cbIdx !== -1)
            this.context.pre_render_callbacks.splice(cbIdx, 1);
    }
    static _tempProjectionMatrix = new Matrix4();
    static _tempProjectionMatrixInverse = new Matrix4();
    internalUpdate = () => {
        if (this.context.isInXR)
            return;
        if (this.destroyed || !this.activeAndEnabled)
            return;
        const isActive = ViewBox_1.instances[ViewBox_1.instances.length - 1] === this;
        if (!isActive) {
            if (debugParam || this.debug) {
                Gizmos.DrawWireBox(this.gameObject.worldPosition, this.gameObject.worldScale, disabledGizmoColor);
            }
            return;
        }
        if (debugParam || this.debug)
            Gizmos.DrawWireBox(this.gameObject.worldPosition, this.gameObject.worldScale, 0xdddd00, 0, true, this.gameObject.worldQuaternion);
        // calculate box size to fit the camera frustrum size at the current position (just scale)
        const camera = this.context.mainCamera;
        if (!camera)
            return;
        if (!(camera instanceof PerspectiveCamera)) {
            // TODO: support orthographic camera
            return;
        }
        if (this.referenceFieldOfView === undefined || this.referenceFieldOfView === -1) {
            this.referenceFieldOfView = camera.fov;
            console.debug("[ViewBox] No referenceFieldOfView set, using camera fov:", this.referenceFieldOfView);
        }
        if (this.referenceFieldOfView === undefined || this.referenceFieldOfView <= 0) {
            if (debugParam || this.debug)
                console.warn("[ViewBox] No valid referenceFieldOfView set, cannot adjust box size:", this.referenceFieldOfView);
            return;
        }
        const domWidth = this.context.domWidth;
        const domHeight = this.context.domHeight;
        let rectWidth = domWidth;
        let rectHeight = domHeight;
        let diffWidth = 1;
        let diffHeight = 1;
        // use focus rect if available
        const focusRectSize = this.context.focusRectSize;
        if (focusRectSize) {
            rectWidth = focusRectSize.width;
            rectHeight = focusRectSize.height;
            diffWidth = domWidth / rectWidth;
            diffHeight = domHeight / rectHeight;
        }
        // Copy the projection matrix and restore values so we can reset it later
        ViewBox_1._tempProjectionMatrix.copy(camera.projectionMatrix);
        ViewBox_1._tempProjectionMatrixInverse.copy(camera.projectionMatrixInverse);
        const view = camera.view;
        const zoom = camera.zoom;
        const aspect = camera.aspect;
        const fov = camera.fov;
        // Set values to default so we can calculate the box size correctly
        camera.view = null;
        camera.zoom = 1;
        camera.fov = this.referenceFieldOfView;
        camera.updateProjectionMatrix();
        const boxPosition = this.gameObject.worldPosition;
        const boxScale = this.gameObject.worldScale;
        const cameraPosition = camera.worldPosition;
        const distance = cameraPosition.distanceTo(boxPosition);
        // #region camera fixes
        // If the camera is inside the box, move it out
        const boxSizeMax = Math.max(boxScale.x, boxScale.y, boxScale.z);
        const direction = getTempVector(cameraPosition).sub(boxPosition);
        if (distance < boxSizeMax) {
            // move camera out of bounds
            if (this.debug || debugParam)
                console.warn("[ViewBox] Moving camera out of bounds", distance, "<", boxSizeMax);
            const positionDirection = getTempVector(direction);
            positionDirection.y *= .00000001; // stay on horizontal plane mostly
            positionDirection.normalize();
            const lengthToMove = (boxSizeMax - distance);
            const newPosition = cameraPosition.add(positionDirection.multiplyScalar(lengthToMove));
            camera.worldPosition = newPosition.lerp(cameraPosition, 1 - this.context.time.deltaTime);
        }
        // Ensure the camera looks at the ViewBox
        // TOOD: smooth lookat over multiple frames if we have multiple viewboxes
        // const dot = direction.normalize().dot(camera.worldForward);
        // if (dot < .9) {
        //     console.log(dot);
        //     const targetRotation = direction;
        //     const rotation = getTempQuaternion();
        //     rotation.setFromUnitVectors(camera.worldForward.multiplyScalar(-1), targetRotation);
        //     camera.worldQuaternion = rotation;
        //     camera.updateMatrixWorld();
        // }
        const boxPositionInCameraSpace = getTempVector(boxPosition);
        camera.worldToLocal(boxPositionInCameraSpace);
        camera.lookAt(boxPosition);
        camera.updateMatrixWorld();
        // #region calculate fit
        const vFOV = this.referenceFieldOfView * Math.PI / 180; // convert vertical fov to radians
        const height = 2 * Math.tan(vFOV / 2) * distance; // visible height
        const width = height * camera.aspect; // visible width
        const projectedBox = this.projectBoxIntoCamera(camera, 1);
        // return
        const boxWidth = (projectedBox.maxX - projectedBox.minX);
        const boxHeight = (projectedBox.maxY - projectedBox.minY);
        const scale = this.fit(boxWidth * camera.aspect, boxHeight, width / diffWidth, height / diffHeight);
        // console.log({ scale, width, height, boxWidth: boxWidth * camera.aspect, boxHeight, diffWidth, diffHeight, aspect: camera.aspect, distance })
        // this.context.focusRectSettings.zoom = 1.39;
        // if (!this.context.focusRect) this.context.setCameraFocusRect(this.context.domElement);
        // return
        const vec = getTempVector(boxPosition);
        vec.project(camera);
        this.context.focusRectSettings.offsetX = vec.x;
        this.context.focusRectSettings.offsetY = vec.y;
        this.context.focusRectSettings.zoom = scale / (height * .5);
        // if we don't have a focus rect yet, set it to the dom element
        if (!this.context.focusRect)
            this.context.setCameraFocusRect(this.context.domElement);
        // Reset values
        camera.view = view;
        camera.zoom = zoom;
        camera.aspect = aspect;
        camera.fov = fov;
        camera.projectionMatrix.copy(ViewBox_1._tempProjectionMatrix);
        camera.projectionMatrixInverse.copy(ViewBox_1._tempProjectionMatrixInverse);
        // BACKLOG: some code for box scale of an object (different component)
        // this.gameObject.worldScale = getTempVector(width, height, worldscale.z);
        // this.gameObject.scale.multiplyScalar(.98)
        // const minscale = Math.min(width, height);
        // console.log(width, height);
        // this.gameObject.worldScale = getTempVector(scale, scale, scale);
    };
    /**
     * Cover fit
     */
    fit(width1, height1, width2, height2) {
        const scaleX = width2 / width1;
        const scaleY = height2 / height1;
        return Math.min(scaleX, scaleY);
    }
    projectBoxIntoCamera(camera, _factor) {
        const factor = .5 * _factor;
        const corners = [
            getTempVector(-factor, -factor, -factor),
            getTempVector(factor, -factor, -factor),
            getTempVector(-factor, factor, -factor),
            getTempVector(factor, factor, -factor),
            getTempVector(-factor, -factor, factor),
            getTempVector(factor, -factor, factor),
            getTempVector(-factor, factor, factor),
            getTempVector(factor, factor, factor),
        ];
        let minX = Number.POSITIVE_INFINITY;
        let maxX = Number.NEGATIVE_INFINITY;
        let minY = Number.POSITIVE_INFINITY;
        let maxY = Number.NEGATIVE_INFINITY;
        for (let i = 0; i < corners.length; i++) {
            const c = corners[i];
            c.applyMatrix4(this.gameObject.matrixWorld);
            c.project(camera);
            if (c.x < minX)
                minX = c.x;
            if (c.x > maxX)
                maxX = c.x;
            if (c.y < minY)
                minY = c.y;
            if (c.y > maxY)
                maxY = c.y;
        }
        if (debugParam) {
            if (!this._projectedBoxElement) {
                this._projectedBoxElement = document.createElement("div");
            }
            if (this._projectedBoxElement.parentElement !== this.context.domElement)
                this.context.domElement.appendChild(this._projectedBoxElement);
            this._projectedBoxElement.style.position = "fixed";
            // dotted but with larger gaps
            this._projectedBoxElement.style.outline = "2px dashed rgba(255,0,0,.5)";
            this._projectedBoxElement.style.left = ((minX * .5 + .5) * this.context.domWidth) + "px";
            this._projectedBoxElement.style.top = ((-maxY * .5 + .5) * this.context.domHeight) + "px";
            this._projectedBoxElement.style.width = ((maxX - minX) * .5 * this.context.domWidth) + "px";
            this._projectedBoxElement.style.height = ((maxY - minY) * .5 * this.context.domHeight) + "px";
            this._projectedBoxElement.style.pointerEvents = "none";
            this._projectedBoxElement.style.zIndex = "1000";
        }
        return { minX, maxX, minY, maxY };
    }
    _projectedBoxElement = null;
};
__decorate([
    serializable()
], ViewBox.prototype, "referenceFieldOfView", void 0);
__decorate([
    serializable()
], ViewBox.prototype, "debug", void 0);
ViewBox = ViewBox_1 = __decorate([
    registerType
], ViewBox);
export { ViewBox };
//# sourceMappingURL=ViewBox.js.map