import * as THREE from "@intelligentgraphics/three";
import { State as ThreeJSState, } from "@intelligentgraphics/ig.gfx.threejs";
import * as Core from "@intelligentgraphics/ig.gfx.core";
const CONTAINER_NAME = "MEASUREMENT_GROUPS_CONTAINER";
export class MeasurementManager {
    constructor(session, plannerManager, api, renderer, provider) {
        this.session = session;
        this.plannerManager = plannerManager;
        this.api = api;
        this.renderer = renderer;
        this.provider = provider;
        this.visible = true;
        this.targets = new Map();
        this.plannerManager.hooks.sceneUpdateDone.tap("MeasurementManager", () => {
            this.forceUpdate();
        });
    }
    /**
     * Retrieves the current list of targets from the provider and renders the measurement again with the current visibility
     *
     */
    forceUpdate() {
        // for (const [identifier, target] of this.targets) {
        // 	target.dirty = true;
        // }
        this.updateTargets();
        this.render();
    }
    /**
     * Changes the visibility of the measurement of all targets.
     *
     * This won't override individually set visibilities, unless force is true.
     *
     * @param {boolean} visible
     */
    setVisibility(visible, force) {
        if (visible === this.visible && !force) {
            return;
        }
        this.visible = visible;
        // for (const [, target] of this.targets) {
        // 	target.dirty = true;
        // }
        if (force) {
            for (const [, target] of this.targets) {
                target.visible = undefined;
            }
        }
        this.render();
    }
    setTargetVisibility(identifier, visible) {
        const target = this.targets.get(identifier);
        if (target === undefined) {
            throw new Error(`Can not set visibility of unknown target "${identifier}"`);
        }
        if (target.visible === visible) {
            return;
        }
        target.visible = visible;
        // target.dirty = true;
        this.render();
    }
    /**
     * Removes all measurement objects from the provided scene.
     *
     * The provided scene can only be an additional scene, not the primary one.
     *
     * @param {THREE.Scene} scene
     */
    removeFromScene(scene) {
        if (scene === ThreeJSState.scene) {
            throw new Error(`The measurement can only be removed from additional scenes, not the primary one, using this method`);
        }
        const container = scene.getObjectByName(CONTAINER_NAME);
        if (container) {
            scene.remove(container);
        }
    }
    updateTargets() {
        const targets = this.provider.getMeasurementTargets();
        const oldTargets = this.targets;
        this.targets = new Map();
        for (const target of targets) {
            const oldTarget = oldTargets.get(target.identifier);
            let articles;
            if ("comId" in target) {
                articles = [this.session.getArticleByComId(target.comId)];
            }
            else if ("comIds" in target) {
                articles = target.comIds.map(comId => this.session.getArticleByComId(comId));
            }
            else if ("scene" in target) {
                articles = this.session.getFilteredArticles(article => article.type === "graphical" &&
                    article.gfxId !== undefined);
            }
            this.targets.set(target.identifier, {
                visible: undefined,
                ...oldTarget,
                // dirty: true,
                ...target,
                containerId: undefined,
                articles,
            });
        }
        for (const [, target] of oldTargets) {
            if (!this.targets.has(target.identifier)) {
                this.deleteTarget(target);
            }
        }
    }
    deleteTarget(target) {
        if (target.containerId === undefined) {
            return;
        }
        const groupsContainer = ThreeJSState.scene.getObjectByName(CONTAINER_NAME);
        if (!groupsContainer) {
            return;
        }
        const targetObject = groupsContainer.getObjectByName(target.containerId);
        if (targetObject) {
            groupsContainer.remove(targetObject);
        }
    }
    render() {
        let groupsContainer = ThreeJSState.scene.getObjectByName(CONTAINER_NAME);
        const targets = [];
        for (const [identifier, target] of this.targets) {
            // if (!target.dirty) {
            // 	continue;
            // }
            if (!groupsContainer) {
                groupsContainer = new THREE.Group();
                groupsContainer.name = CONTAINER_NAME;
                ThreeJSState.scene.add(groupsContainer);
                if (ThreeJSState.depthShadowPlane) {
                    ThreeJSState.depthShadowPlane.setIgnored3DObject(CONTAINER_NAME, groupsContainer);
                }
                Core.setGlobalIgnore3DObjectInPostprocessing(CONTAINER_NAME, groupsContainer, "all");
            }
            let groupContainer = groupsContainer.getObjectByName(identifier);
            if (groupContainer === undefined) {
                groupContainer = new THREE.Group();
                groupContainer.name = identifier;
                groupsContainer.add(groupContainer);
            }
            if (target.visible !== true && this.visible === false) {
                groupContainer.visible = false;
                continue;
            }
            groupContainer.visible = true;
            let position;
            const bounds = this.calculateGroupBounds(target.articles);
            if (target.articles.length === 1) {
                position = this.api.GetPosition(target.articles[0].gfxId);
            }
            else {
                position = this.calculateGroupBounds(target.articles).min;
            }
            groupContainer.position.set(position.x, position.y, position.z);
            let rotationContainer = groupContainer.children[0];
            if (rotationContainer === undefined) {
                rotationContainer = new THREE.Object3D();
                groupContainer.add(rotationContainer);
            }
            if (target.articles.length === 1) {
                rotationContainer.rotation.set(0, Core.MathUtils.degToRad(this.api.GetYRotation(target.articles[0].gfxId)), 0);
            }
            else {
                rotationContainer.rotation.set(0, 0, 0);
            }
            target.containerId = identifier;
            if ("comId" in target) {
                targets.push({
                    article: target.articles[0],
                    parent: rotationContainer,
                    type: "article",
                    bounds,
                });
            }
            else {
                targets.push({
                    articles: target.articles,
                    parent: rotationContainer,
                    type: "group",
                    bounds,
                });
            }
        }
        this.renderer.renderMeasurement(targets);
        this.api.RedrawScene();
    }
    calculateGroupBounds(articles) {
        const bounds = Core.createBounds();
        for (const article of articles) {
            const { min, max } = this.api.GetBounds(article.gfxId, "World");
            Core.extendBounds(bounds, min);
            Core.extendBounds(bounds, max);
        }
        return bounds;
    }
}
