import { BoundingInfo, DeepImmutable, Matrix, Vector3 } from './babylonjs-import';
import { BimIfcObject } from './bim-ifc-object';
import { Writeable } from '../Types';
import { RgbaComponent } from './rgba';
import { BimIfcStyle } from './bim-ifc-style';
import { IBimIfcLoaderElement } from './bim-ifc-loader-element';
import { IfcMaterialHighlightIndex } from './CustomBabylonMaterials/IfcMaterial';
import { FastTransform } from '../math/FastTransform';
import { BimIfcMesh } from './bim-ifc-mesh';
import { BimChangeIfc } from './bim-api-client';
import { BimProductMeshDTO, BimProductMeshDescriptor, BimVertexData } from './bim-format-types';
import type { FlatSegment3, VertexData } from '@twinfinity/geometry';
import { MergedIfcProductMeshesRenderInfo } from './MergedIfcProductMeshesRenderInfo';
import { MergeId } from './MergeId';

/**
 * An IFC product mesh before it has been assigned to a merge group.
 * At this stage the mesh has geometry and material data but no rendering
 * identity.
 *
 * See {@link BimProductMesh} when assigned a render identity.
 */
export interface BimProductMeshPreMerge extends BimProductMeshDTO {
    /**
     * Number of vertices in the mesh.
     */
    readonly vertexCount: number;

    /**
     * Number of indices in the mesh. Number of triangles can be calculated as `indexCount / 3`.
     */
    readonly indexCount: number;

    /**
     * Unique index of mesh. Not persistent and not globally unique like a GUID.
     */
    readonly colorTexturePixelIndex: number;

    /** Parent {@link BimIfcObject} the mesh belongs to. */
    readonly ifcProduct: BimIfcObject;

    /** If `true` then mesh is visible, otherwise `false`.
     */
    visible: boolean;

    /** If `true` then mesh is written to the depth buffer even if it is not visible
     */
    depthWriteIgnoresVisibility: boolean;

    /** Set the different highlight indexes (or indices, both plurals are fine) of the product mesh.
     * If any index but `Empty` is set then the product mesh will be colored to the corresponding highlight color.
     * You can change the colors of the highlights by calling the API method
     */
    highlight: IfcMaterialHighlightIndex;

    /** If `true` then mesh is displayed with an outline, otherwise `false`. For the outline to actually show,
     * the postprocess effect for outlines needs to be created, which is created by calling createOutlinePipeline from PostProcessEffects
     *
     * So for example you could do like this:
     * @example
     * ```typescript`
     *
     * for (let product of this._api.ifc.products()) {
     *   for (let productMesh of product.productMeshes) {
     *     if (Math.random() > 0.95) {
     *       productMesh.outline = true;
     *     }
     *   }
     * }
     *
     *  api.camera.attachBehavior(PostProcessEffects.createOutlinePipeline(true));
     * ``
     * Note that like any other post process, the order you attach them to the camera is the same order the post processes are rendered
     */
    outline: boolean;

    /**
     * Enable/disable the 'ghost outline'. Alias for setting {@link visible} `=false` and
     * {@link depthWriteIgnoresVisibility} `= true`.
     * Requires that lineshading is enabled. Otherwise the object will simply not be visible.
     */
    ghostOutline: boolean;

    /**
     * If set to true, this mesh will ignore clip plane culling, if not it will behave normally (it will be culled by clip planes)
     */
    ignoreClipPlanes: boolean;

    /**
     * If `true` then the mesh has been transferred to the gpu, otherwise `false`.
     */
    readonly isOnGpu: boolean;

    /**
     * Ifc style used by mesh
     */
    readonly style: BimIfcStyle;

    /**
     * Mesh descriptor
     */
    readonly descriptor: BimProductMeshDescriptor;

    /**
     * Parent loader element.
     */
    readonly loaderElement: IBimIfcLoaderElement;

    /**
     * Parent IFC mesh belongs to.
     */
    readonly ifc: BimChangeIfc;

    /**
     * Vertex data.
     */
    readonly vertexData: BimVertexData;

    /** Cached feature edges in world space, populated by {@link getEdges}. */
    readonly edges?: FlatSegment3[];

    /**
     * Lazy-load edges for this mesh. On first call, extracts unique edges from
     * vertex data and caches the result. Subsequent calls return the cached edges
     * immediately.
     *
     * Edges are returned in world space as {@link FlatSegment3} tuples — the
     * format consumed directly by `CreateGreasedLine`.
     */
    getEdges(): Promise<FlatSegment3[]>;

    /**
     * Asynchronously get the vertex data for this mesh.
     */
    getVertexData(): Promise<VertexData>;

    /**
     * Calculate aabb min and max value of mesh.
     * @param min minimum bounding vector3.
     * @param max maximum bounding vector3
     * @param noCache Whether to clear the transform cache after calculation or not. Set to `false` when calculating
     * aabb for many meshes at the same time for better performance. Set to `true` for last mesh to ensure that the cache is cleared.
     * Defaults to `true`.
     */
    aabb(min: Vector3, max: Vector3, noCache?: boolean): void;

    /**
     * Builds the {@link mergeId}. After calling, mesh can be represented by {@link BimProductMesh}.
     * Only for internal use.
     * @internal
     * @hidden
     * @param mergeId - The merge identity to assign.
     * @returns `true` if the merge id was built. Otherwise `false` (it was built already).
     */
    _setMergeId(mergeId: MergeId): boolean;

    /**
     * Clears the {@link mergeId}. This is used when a mesh needs to be re-merged into a different render mesh, for example when the style of the mesh changes.
     * Only for internal use. After calling this method the mesh is back to being a {@link BimProductMeshPreMerge} and will remain so until new call to {@link _setMergeId}.
     * @internal
     * @hidden
     * @returns `true` if the merge id was cleared. Otherwise `false` (it was already cleared).
     */
    _clearMergeId(): boolean;

    /**
     * Calculate aabb `BoundingInfo` of the mesh.
     * @param bI Optional `BoundingInfo` if specified it will be 'extended' with the aabb of this mesh. Makes it simple to calculate
     * a compound aabb for many meshes. Simply use the `BoundingInfo` from the first call to {@link boundingInfo} in all the subsequent calls
     * to {@link boundingInfo}  the other meshes. The end result will be a compound `BoundingInfo` for all the meshes.
     * @param noCache Whether to clear the transform cache after calculation or not. Set to `false` when calculating
     * aabb for many meshes at the same time for better performance. Set to `true` for last mesh to ensure that the cache is cleared.
     * Defaults to `true`.
     * @returns `BoundingInfo` representing the aabb. If `bI` was specifie as argument then that instance is returned.
     */
    boundingInfo(bI?: BoundingInfo, noCache?: boolean): BoundingInfo;

    /**
     * Copy the RGBA color of the mesh into `dst`. Each component is `0..255`.
     * Must be at least 4 elements.
     * @param dst Buffer to write color to. Must have length in multiples of 4.
     * @param dstOffset Offset in `dst`. Must be specified in multiples of 4. Do
     * not set so writes to `dst` end up outside length of `dst`.
     */
    copyColorTo(dst: Writeable<ArrayLike<number>>, dstOffset?: number): void;

    /**
     * Resets back to the default color of the mesh.
     * @returns `true` if colors changes needed to be made.
     */
    defaultColor(): boolean;

    /**
     * Set mesh color.
     * @param color RGBA color. Each component is `0..255`.
     * @returns `true` if color was changed. Otherwise `false`.
     */
    setColor(color: ArrayLike<number>): boolean;

    /**
     * Get specific color component. Output is in range `0..255`.
     * @param colorComponent Color component.
     */
    getColorComponent(colorComponent: RgbaComponent): number;

    /**
     * Calculate `Matrix` transform for mesh. Gives same wordlspace transform as {@link fastTransform} but can be used
     * with methods that do not accept {@link FastTransform}.
     * @param noCache Whether to clear the transform cache after calculation or not. Set to `false` when calculating
     * matrix transform for many meshes at the same time for better performance. Set to `true` for last mesh to ensure that the cache is cleared.
     * or call {@link BimTransformsRepository.clear} on {@link loaderElement} afterwards.
     * Defaults to `true`.
     */
    transform(noCache?: boolean): Matrix;

    /**
     * Calculate {@link FastTransform} for mesh.
     * @param noCache Whether to clear the transform cache after calculation or not. Set to `false` when calculating
     * {@link FastTransform} transform for many meshes at the same time for better performance. Set to `true` for last mesh to ensure that the cache is cleared.
     * or call {@link BimTransformsRepository.clear} on {@link loaderElement} afterwards.
     * Defaults to `true`.
     */
    fastTransform(noCache?: boolean): FastTransform;

    /**
     * Gets a {@link BimIfcMesh} instance that can be used to do intersection testing and other (computationally expensive)
     * operations.
     * @param noCache Whether to clear the transform cache after {@link BimIfcMesh} creation or not. Set to `false` when creating
     * many {@link BimIfcMesh} matrix transform for many meshes at the same time for better performance. Set to `true` for last mesh to ensure that the cache is cleared.
     * or call {@link BimTransformsRepository.clear} on {@link loaderElement} afterwards.
     * Defaults to `true`.
     */
    mesh(noCache?: boolean): BimIfcMesh;
}

/**
 * An IFC product mesh that has been assigned to a merge group and is ready
 * for rendering. Extends {@link BimProductMeshPreMerge} with merge identity
 * and culling properties.
 */
export interface BimProductMesh extends BimProductMeshPreMerge {
    /** Determine which babylonjs mesh this ifc product mesh shall belong to. */
    readonly mergeId: string;

    /** Determine at what distance from the camera this mesh should be hidden.
     * If < `0` then mesh is never hidden. If >= `0` then mesh is hidden when distance to camera is greater than this value.
     */
    readonly cullingDistance: number;

    /** Center of the cube, this mesh will be a part of, in IFC space (not worldspace) */
    readonly centerOfParentIfcCube?: DeepImmutable<Vector3>;

    /**
     * Render property info shared by all meshes with the same {@link BimProductMesh.mergeId | mergeId}.
     * Set by {@link MergedIfcProductMeshesRenderInfo.assign} after the merged geometry has been built.
     * For internal use only.
     * @hidden
     * @internal
     */
    _renderPropertyInfo?: MergedIfcProductMeshesRenderInfo;
}

export interface HasGeometricFeatureEdges {
    readonly edges?: FlatSegment3[];
}
