import { Camera, ISize, Mesh, Ray, TargetCamera } from './babylonjs-import';
import { Intersection } from './bim-format-types';
import { BimProductAndMesh } from './BimProductAndMesh';
import { Vertex2 } from '../math';
import { Icon } from '../tools/Icon';
import { PickResult } from './PickResult';

/** Discriminant union selector for {@link VisibleSelectable}. */
export enum VisibleSelectableType {
    /** Corresponds to {@link VisibleMesh}. */
    Mesh,
    /** Corresponds to {@link VisibleIcon}. */
    Icon,
    /** Corresponds to {@link VisibleIfcProductAndMesh}. */
    IfcProductAndMesh
}

/** Represents a visible BabylonJS mesh. */
export interface VisibleMesh {
    /** Discriminant union selector. */
    type: VisibleSelectableType.Mesh;

    /**
     * The {@link Mesh} which is visible. (May contain ifc objects nor not)
     */
    readonly mesh: Mesh;
}

/** Represents a visible {@link Icon}. */
export interface VisibleIcon extends Omit<VisibleMesh, 'type'> {
    /** Discriminant union selector. */
    type: VisibleSelectableType.Icon;
    /** Visible icon */
    icon: Icon;
}

/** Represents a visible {@link BimProductAndMesh}. */
export interface VisibleIfcProductAndMesh extends Omit<VisibleMesh, 'type'> {
    /** Discriminant union selector. */
    type: VisibleSelectableType.IfcProductAndMesh;
    /**
     * Exact {@link BimIfcObject} and corresponding {@link BimProductMesh} which is visible
     * Only available if the visible object actually is a IFC object.
     */
    ifc: BimProductAndMesh;
}

/**
 * Represents something that is visible. Such as a BabylonJS mesh, a {@link Icon} or a
 * {@link BimProductAndMesh}.
 */
export type VisibleSelectable = VisibleMesh | VisibleIcon | VisibleIfcProductAndMesh;

/**
 * Type of pick option.
 */
export enum PickOptionType {
    Canvas,
    Ray,
    Camera,
    Intersection
}

/**
 * Predefined canvas positions.
 */
export enum PredefinedCanvasPosition {
    /**
     * Center of canvas
     */
    Center,
    /**
     * Current mouse coordinates
     */
    Mouse
}

/**
 * Options to use when referencing a coordinate on the canvas.
 */
export type CanvasPosition = Vertex2 | PredefinedCanvasPosition;

/** Flags for {@link PickOption}. */
export interface PickOptionFlags {
    /**
     * Type for use in discriminant union checks.
     */
    type: PickOptionType;

    /**
     * If undefined or `true` then a (computationally expensive) intersection test
     * will be performed against geometry to find out exactly where intersection is.
     */
    isGeometryIntersectionEnabled?: boolean;

    /** If `true`, an intersection test against the AABB is performed. This test also requires that
     * {@link isGeometryIntersectionEnabled} === `false` or that no geometry intersection
     * is found.
     */
    isAABBIntersectionEnabled?: boolean;

    /**
     * If `true`, fall back to use the object center as intersection point if no intersection point
     * is otherwise found.
     */
    isCenterIntersectionFallbackEnabled?: boolean;

    /**
     * If `true` and if no intersection is found, intersections on nearby rendered objects are looked for.
     */
    isNearbyRenderedObjectsIntersectionFallbackEnabled?: boolean;

    /**
     * Texture size of gpu picking texture. (16 if not specified)
     */
    textureSize?: number;
}

/**
 * Options for canvas coordinate pick operation.
 */
export interface PickOptionCanvas extends PickOptionFlags {
    /**
     * Type for use in discriminant union checks.
     */
    type: PickOptionType.Canvas;
    /**
     * Represents a ray from camera that passes through the screen coordinate.
     */
    position: CanvasPosition;
}

/**
 * Options for ray pick operation.
 */
export interface PickOptionRay extends PickOptionFlags {
    /**
     * Type for use in discriminant union checks.
     */
    type: PickOptionType.Ray;
    /**
     * Ray in worldspace
     */
    ray: Ray;
}

/**
 * Options for ray pick operation.
 */
export interface PickOptionCamera extends PickOptionFlags {
    /**
     * Type for use in discriminant union checks.
     */
    type: PickOptionType.Camera;
    /**
     * Camera. Intersection ray goes through camera center
     */
    camera: TargetCamera;
}

/**
 * Options for pick operation using a {@link Intersection}
 */
export interface PickOptionIntersection extends PickOptionFlags {
    /**
     * Type for use in discriminant union checks.
     */
    type: PickOptionType.Intersection;
    /**
     * Camera. Intersection ray goes through camera center
     */
    intersection: Intersection;
}

/**
 * Options for pick operation.
 */
export type PickOption = PickOptionRay | PickOptionCanvas | PickOptionCamera | PickOptionIntersection;

/** Options for attach operation. */
export type AttachOption = Pick<PickOptionFlags, 'isGeometryIntersectionEnabled'> & { applyColor?: boolean };

/**  Represents a selectable render operation.*/
export interface SelectablesRenderOperation {
    /** Called when selectable render operations is completed. */
    readonly done: () => void;
}

export interface GetVisibleInSightOptions {
    camera?: Camera;
    textureSize?: ISize;
    isDistanceCullingEnabled?: boolean;
}

/** Methods and properties related to gpu picking. Basically "given a rendered pixel
 * what object does that pixel represent".
 */
export interface Selectables {
    /** Attaches custom BabylonJS meshes so they can be "picked".  */
    attach(mesh: Mesh | Icon, o?: AttachOption): number;

    /** Detaches a previously attached BabylonJS mesh from "pick" operations.  */
    detach(mesh: Mesh | Icon): boolean;

    /**
     * Get unique "pick" id of mesh or icon. It is only valid as long
     * as the item has been registered with {@link attach}.
     * It will be 0 if not attached.
     * @param meshOrIcon mesh or icon to get id for.
     * @returns 0 if not attached, otherwise unique "pick" id.
     */
    idOf(meshOrIcon: Mesh | Icon): number;

    /**
     * Renders objects and checks which objects are in sight from given camera.
     * @param o Options for getting visible objects in sight.
     */
    getVisiblesInSight(o?: GetVisibleInSightOptions): VisibleSelectable[];
    /**
     * Renders objects and checks which objects are in sight from given camera.
     * @param inSightCamera Camera to represent the view to check for visible objects. If not specified, then current active camera is used.
     * @param textureSizePow2 Resolution of the texture to be used for in sight detection. (The bigger the texture is, the higher precision when picking.)
     * @param isDistanceCullingEnabled Whether to enable distance culling.
     */
    getVisiblesInSight(
        inSightCamera?: Camera,
        textureSizePow2?: number,
        isDistanceCullingEnabled?: boolean
    ): VisibleSelectable[];
    getVisiblesInSight(
        inSightCamera?: Camera | GetVisibleInSightOptions,
        textureSizePow2?: number,
        isDistanceCullingEnabled?: boolean
    ): VisibleSelectable[];

    /**
     * Performs an intersection test in the world (finds objects below a specific screen coordinate or that intersect a ray).
     * @param o Options to use for pick operation.
     * @return Intersection information.
     */
    pick(o: PickOption): PickResult;

    /**
     * Performs an intersection test in the world (find objects below a specific camera or screen coordinate, or that intersect a ray).
     * @param picker {@link TargetCamera} or {@link PickOption}..
     * @param textureSizePow2 texture size (defaults to 16x16) used when rendering the world during pick operation.
     * @param doIntersectionTestOnGeometry `true` to perform exact intersection on intersected object (exact coordinate etc). This
     * is computationally expensive.
     * @param saveRenderedGpuPickingSceneTexture `true` to save the rendering texture of the pick operation. Mostly useful for debugging.
     */
    pick(
        picker: TargetCamera,
        textureSizePow2?: number,
        doIntersectionTestOnGeometry?: boolean,
        saveRenderedGpuPickingSceneTexture?: boolean
    ): PickResult;
}
