import { AbstractMesh, Camera } from '@babylonjs/core';
import { ViewerCamera } from '../camera/ViewerCamera';

/**
 * Options for tracking minimum distance to camera.
 */
export interface TrackMeshDistanceToCameraOptions {
    /** The mesh to track and the camera to measure distance from */
    mesh: AbstractMesh;
    /** The camera to measure distance from */
    camera: Camera;
}

/**
 * Options for tracking distance to camera.
 */
export type TrackDistanceToCameraOptions = TrackMeshDistanceToCameraOptions;

/**
 * Tracks distance of objects relative a camera.
 *
 * Call {@link trackDistanceToCamera} for each visible mesh, then read
 * {@link minDistanceToCamera} to get the result.
 * Call {@link endFrame} before each frame.
 */
export class DistanceToCamera {
    private _distanceDivisor = 128.0;
    private _maxNearPlaneDistance = 4.0;
    private _minDistanceToCamera = Number.MAX_VALUE;

    /**
     * Starts tracking distances for a new frame.
     */
    beginFrame(): void {
        this._minDistanceToCamera = Number.MAX_VALUE;
    }

    /**
     * Tracks a mesh's distance to the camera.
     *
     * Updates the internal minimum distance if this mesh is closer than
     * previously tracked meshes.
     *
     * @param options - The tracking options
     * @returns The distance from the mesh to the camera
     */
    trackDistanceToCamera(options: TrackDistanceToCameraOptions): number {
        const { mesh, camera } = options;
        const meshDistanceToCamera = mesh.getDistanceToCamera(camera);
        this._minDistanceToCamera = Math.min(this._minDistanceToCamera, meshDistanceToCamera);
        return meshDistanceToCamera;
    }

    /**
     * The calculated minimum distance from the closest tracked mesh to the camera,
     * divided by the distance divisor and clamped between {@link ViewerCamera.cameraMinZ}
     * and the max near plane distance. Resets when {@link beginFrame} is called.
     */
    get minDistanceToCamera(): number {
        if (this._minDistanceToCamera === Number.MAX_VALUE) {
            return ViewerCamera.cameraMinZ;
        }
        const divided = this._minDistanceToCamera / this._distanceDivisor;
        return Math.min(Math.max(ViewerCamera.cameraMinZ, divided), this._maxNearPlaneDistance);
    }
}
