import * as THREE from "@intelligentgraphics/three";
import { IGXE } from "@intelligentgraphics/igd.productdata";
import { SceneRenderer, ThreeVector3FromVec3, } from "@intelligentgraphics/ig.gfx.threejs";
import * as Core from "@intelligentgraphics/ig.gfx.core";
import { OrthographicCamera } from "@intelligentgraphics/three";
const ZERO_VECTOR = {
    x: 0,
    y: 0,
    z: 0,
};
const DEFAULT_OPTIONS = {
    lineColor: "black",
    textColor: "black",
    textBoxBackground: "rgb(239, 239, 239)",
    textBoxBorder: "rgb(225, 225, 225)",
    unit: IGXE.EPropertyValueUnit.LENGTH_MM,
    createLabelText: (value, unit) => {
        const unitString = IGXE.Units.getDefaultUnitString(unit);
        return `${value} ${unitString}`;
    },
};
/**
 * Draws are wireframe around each target and
 * renders all provided dimensions as labels on each axis.
 *
 * @export
 * @class LabeledAxis
 * @implements {IMeasurementRenderer}
 */
export class LabeledAxis {
    constructor(options, api, dimensionsProvider, camera, letterGeometryCache) {
        this.api = api;
        this.dimensionsProvider = dimensionsProvider;
        this.camera = camera;
        this.objectLabels = new Map();
        this.options = {
            ...DEFAULT_OPTIONS,
            ...options,
        };
        this.renderScene = SceneRenderer.createSceneRenderer(letterGeometryCache);
        this.textObjectDefaults = {
            type: "text",
            align: "center",
            color: this.options.textColor,
            size: 0.0295,
            font: "",
            background: {
                color: this.options.textBoxBackground,
                padding: 0.015,
                border: {
                    width: 0.003,
                    color: this.options.textBoxBorder,
                },
            },
        };
    }
    /**
     * Updates the internal camera and the orientation of the labels towards the new camera's position
     *
     * @param {THREE.Camera} camera
     */
    setCamera(camera) {
        if (camera === this.camera) {
            return;
        }
        this.camera = camera;
        this.updateLabelLookat();
    }
    /**
     * Updates the orientation of the labels towards the camera's position
     *
     */
    updateLabelLookat() {
        for (const [, { labels, articles, labelContainer, size }] of this
            .objectLabels) {
            const { x, y, z } = this.calculateLabelPositions(size, labelContainer);
            for (let index = 0; index < 3; index++) {
                const labelContainer = labels[index];
                if (!labelContainer.children[0]) {
                    continue;
                }
                let position;
                switch (index) {
                    case 0:
                        position = x;
                        break;
                    case 1:
                        position = y;
                        break;
                    case 2:
                        position = z;
                        break;
                }
                labelContainer.position.set(position.x, position.y, position.z);
                if (this.camera instanceof OrthographicCamera) {
                    const lookAt = ThreeVector3FromVec3(position);
                    lookAt.setY(this.camera.position.y);
                    labelContainer.children[0].lookAt(lookAt);
                }
                else {
                    labelContainer.children[0].lookAt(this.camera.position);
                }
            }
        }
    }
    renderMeasurement(targets) {
        this.objectLabels.clear();
        for (const target of targets) {
            if ("articles" in target) {
                this.renderGroupMeasurement(target.parent, target.articles, target.bounds);
            }
            else {
                this.renderArticleMeasurement(target.parent, target.article, target.bounds);
            }
        }
        this.updateLabelLookat();
    }
    renderGroupMeasurement(parent, articles, bounds) {
        const size = Core.Vector.Sub3(bounds.max, bounds.min);
        const positions = this.calculateLabelPositions(size, parent);
        const objects = [];
        objects.push({
            type: "wirebox",
            bounds: {
                min: ZERO_VECTOR,
                max: size,
            },
            color: this.options.lineColor,
        });
        const { width, height, depth, } = this.dimensionsProvider.getGroupDimensions(articles);
        const labelContainer = {
            type: "group",
            children: [],
        };
        objects.push(labelContainer);
        const x = {
            type: "group",
            children: [],
            position: positions.x,
        };
        const y = {
            type: "group",
            children: [],
            position: positions.y,
        };
        const z = {
            type: "group",
            children: [],
            position: positions.z,
        };
        labelContainer.children.push(x, y, z);
        if (width !== undefined) {
            x.children.push({
                type: "group",
                children: [
                    {
                        ...this.textObjectDefaults,
                        text: this.createLabelText(width),
                        position: ZERO_VECTOR,
                    },
                ],
            });
        }
        if (height !== undefined) {
            y.children.push({
                type: "group",
                children: [
                    {
                        ...this.textObjectDefaults,
                        text: this.createLabelText(height),
                        position: ZERO_VECTOR,
                    },
                ],
            });
        }
        if (depth !== undefined) {
            z.children.push({
                type: "group",
                children: [
                    {
                        ...this.textObjectDefaults,
                        text: this.createLabelText(depth),
                        position: ZERO_VECTOR,
                    },
                ],
            });
        }
        const [, labelContainerObject] = this.renderScene(parent, objects);
        this.objectLabels.set(parent, {
            articles,
            labels: labelContainerObject.children,
            labelContainer: labelContainerObject,
            size,
        });
    }
    renderArticleMeasurement(parent, article, bounds) {
        const localBounds = this.api.GetBounds(article.gfxId, "Local");
        const size = Core.Vector.Sub3(localBounds.max, localBounds.min);
        const positions = this.calculateLabelPositions(size, parent);
        const objects = [];
        objects.push({
            type: "wirebox",
            bounds: {
                min: ZERO_VECTOR,
                max: size,
            },
            color: this.options.lineColor,
        });
        const { width, height, depth, } = this.dimensionsProvider.getArticleDimensions(article);
        const labelContainer = {
            type: "group",
            children: [],
        };
        objects.push(labelContainer);
        const x = {
            type: "group",
            children: [],
            position: positions.x,
        };
        const y = {
            type: "group",
            children: [],
            position: positions.y,
        };
        const z = {
            type: "group",
            children: [],
            position: positions.z,
        };
        labelContainer.children.push(x, y, z);
        if (width !== undefined) {
            x.children.push({
                type: "group",
                children: [
                    {
                        ...this.textObjectDefaults,
                        text: this.createLabelText(width),
                        position: ZERO_VECTOR,
                    },
                ],
            });
        }
        if (height !== undefined) {
            y.children.push({
                type: "group",
                children: [
                    {
                        ...this.textObjectDefaults,
                        text: this.createLabelText(height),
                        position: ZERO_VECTOR,
                    },
                ],
            });
        }
        if (depth !== undefined) {
            z.children.push({
                type: "group",
                children: [
                    {
                        ...this.textObjectDefaults,
                        text: this.createLabelText(depth),
                        position: ZERO_VECTOR,
                    },
                ],
            });
        }
        const [, labelContainerObject] = this.renderScene(parent, objects);
        this.objectLabels.set(parent, {
            articles: [article],
            labels: labelContainerObject.children,
            labelContainer: labelContainerObject,
            size,
        });
    }
    calculateLabelPositions(size, parent) {
        const rotationYRad = Core.MathUtils.degToRad(0);
        const points = [
            [{ ...ZERO_VECTOR }, 0, 0],
            [
                Core.LinearAlgebra.AdaptToRotation(rotationYRad, {
                    ...ZERO_VECTOR,
                    x: size.x,
                }, ZERO_VECTOR),
                180,
                0,
            ],
            [
                Core.LinearAlgebra.AdaptToRotation(rotationYRad, {
                    ...ZERO_VECTOR,
                    x: size.x,
                    z: size.z,
                }, ZERO_VECTOR),
                180,
                180,
            ],
            [
                Core.LinearAlgebra.AdaptToRotation(rotationYRad, {
                    ...ZERO_VECTOR,
                    z: size.z,
                }, ZERO_VECTOR),
                0,
                180,
            ],
        ];
        let smallestDistance;
        let closestPoint;
        for (const entry of points) {
            const relativePosition = new THREE.Vector3(entry[0].x, entry[0].y, entry[0].z);
            const distance = parent
                .localToWorld(relativePosition)
                .distanceTo(this.camera.position);
            if (smallestDistance === undefined || distance < smallestDistance) {
                closestPoint = entry;
                smallestDistance = distance;
            }
        }
        const [displayRoot, displayRootXRotation, displayRootZRotation,] = closestPoint;
        displayRoot.y = size.y;
        let posX = {
            x: size.x / 2,
            y: 0,
            z: 0,
        };
        posX = Core.LinearAlgebra.AdaptToRotation(Core.MathUtils.degToRad(0 + displayRootXRotation), posX, ZERO_VECTOR);
        posX = Core.Vector.Add3(posX, displayRoot);
        posX.y += 0.1;
        const posY = {
            x: displayRoot.x,
            y: size.y / 2,
            z: displayRoot.z,
        };
        let posZ = {
            x: 0,
            y: 0,
            z: size.z / 2,
        };
        posZ = Core.LinearAlgebra.AdaptToRotation(Core.MathUtils.degToRad(0 + displayRootZRotation), posZ, ZERO_VECTOR);
        posZ = Core.Vector.Add3(posZ, displayRoot);
        posZ.y += 0.1;
        return {
            x: posX,
            y: posY,
            z: posZ,
        };
    }
    createLabelText(value) {
        const convertedValue = IGXE.Units.convert(value, IGXE.EPropertyValueUnit.LENGTH_M, this.options.unit);
        return this.options.createLabelText(convertedValue, this.options.unit);
    }
}
