import { IGXE, IGXA, } from "@intelligentgraphics/igd.productdata";
import { State as ThreeJSState, } from "@intelligentgraphics/ig.gfx.threejs";
import { isValue } from "@intelligentgraphics/ig.utilities";
import * as Planning from "@intelligentgraphics/ig.gfx.planning";
import { Hierarchy, Category, } from "@intelligentgraphics/ig.gfx.core";
import * as THREE from "@intelligentgraphics/three";
import { noop } from "@intelligentgraphics/ig.utilities";
import { createSceneFromBlueprint } from "./BlueprintModelIGXCBuilder";
export class BlueprintRoomContainer {
    constructor({ session, roomCatalogSourceId, api, environment = {
        createFloorArticle: session.createArticleFromArticleNumber,
        createWallArticle: session.createArticleFromArticleNumber,
    }, defaultConfiguration, }) {
        this.wallImportIndex = 0;
        this.handleBPPartImported = async (component, kindIndex, configuration) => {
            const rawCategories = component.userData.categories || "";
            const categories = Array.isArray(rawCategories)
                ? rawCategories
                : rawCategories.split(",");
            const isWall = categories.indexOf(Category.Wall) > -1;
            const isFloor = !isWall && categories.indexOf(Category.Floor) > -1;
            if (isWall) {
                let index = kindIndex;
                if (index === undefined) {
                    index = this.wallImportIndex;
                    this.wallImportIndex++;
                }
                const comId = await this.environment.createWallArticle({
                    source: this.roomCatalogSourceId,
                    articleNumber: {
                        baseArticleNumber: "WALL",
                        itemType: IGXA.EItemType.ArticleGfx,
                    },
                    gfxId: component.name,
                });
                this.api.SetCommercialId(component.name, comId);
                const initialConfiguration = this.getInitialConfig("Wall", index, configuration) || {};
                if (component.userData.wallInfo) {
                    if (component.userData.wallInfo.Height) {
                        initialConfiguration["wall.height"] =
                            component.userData.wallInfo.Height;
                    }
                    if (component.userData.wallInfo.Thickness) {
                        initialConfiguration["wall.thickness"] =
                            component.userData.wallInfo.Thickness;
                    }
                    if (component.userData.wallInfo.WallPaperWidth) {
                        initialConfiguration["wall.length"] =
                            component.userData.wallInfo.WallPaperWidth;
                    }
                }
                await this.session.changeArticleProperties(comId, initialConfiguration);
            }
            if (isFloor) {
                const comId = await this.environment.createFloorArticle({
                    source: this.roomCatalogSourceId,
                    articleNumber: {
                        baseArticleNumber: "FLOOR",
                        itemType: IGXA.EItemType.ArticleGfx,
                    },
                    gfxId: component.name,
                });
                this.api.SetCommercialId(component.name, comId);
                const area = this.calculateFloorComponentArea(component);
                await this.session.changeArticleProperties(comId, {
                    ...this.getInitialConfig("Floor", 0, configuration),
                    "floor.area": area,
                });
                return;
            }
        };
        this.session = session;
        this.roomCatalogSourceId = roomCatalogSourceId;
        this.api = api;
        this.environment = environment;
        this.defaultConfiguration = defaultConfiguration;
        const catalogManager = session.getCatalogManagerForSourceId(roomCatalogSourceId);
        if (catalogManager === undefined) {
            throw new Error(`Could not find a catalogManager for "${roomCatalogSourceId}"`);
        }
        this.catalogManager = catalogManager;
    }
    getItemById(id) {
        return this.catalogManager.getIGXAArticle(id);
    }
    getItems() {
        return this.catalogManager.getIGXAFolder(undefined)
            .SubItems;
    }
    getRoomConfig() {
        const floorComId = this.getFloorComId();
        const articleIds = [floorComId, ...this.getWallComIds()];
        if (articleIds.filter(Boolean).length === 0) {
            return {
                floor: {},
                walls: [],
                wallHeight: 0,
            };
        }
        const [floorConfig, ...wallConfigs] = articleIds.map(comId => {
            if (comId === undefined) {
                return undefined;
            }
            const { configuration, properties, } = this.session.getArticleByComId(comId);
            const clonedConfig = { ...configuration };
            for (const propertyKey in configuration) {
                const prop = properties.find(property => property.Key === propertyKey);
                if (prop && prop.ValueType !== IGXE.EPropertyType.Material) {
                    delete clonedConfig[propertyKey];
                }
            }
            return clonedConfig;
        });
        const config = {
            floor: floorConfig || {},
            walls: wallConfigs.filter(isValue),
            wallHeight: 0,
        };
        return config;
    }
    remove3D() {
        if (Planning.Environment.getInstance().getRoot()) {
            this.api.RemoveComponent(Hierarchy.getSuperRootGfxId(Planning.Environment.getInstance().getRoot()));
            this.api.RedrawScene();
        }
    }
    async apply3D({ wallHeight, wallThickness, model, configuration, wallBackMaterial, wallFrontMaterial, floorMaterial, }) {
        this.remove3D();
        this.wallImportIndex = 0;
        await this.session.deleteArticles([this.getFloorComId(), ...this.getWallComIds()].filter(isValue));
        const igxc = createSceneFromBlueprint({
            model,
            wallHeight,
            wallThickness,
            wallBackMaterial,
            wallFrontMaterial,
            floorMaterial,
        });
        for (const categoryName in igxc.Categories) {
            const materialName = igxc.Categories[categoryName];
            const material = this.api.Materials[materialName];
            if (material !== undefined) {
                igxc.Materials[materialName] = material;
            }
        }
        ThreeJSState.OnBlueprintPartImported = (component, kindIndex) => this.handleBPPartImported(component, kindIndex, configuration);
        const rootId = this.api.CreateSuperRoot();
        await this.api.ProcessIGXC(rootId, igxc);
        this.api.RedrawScene();
        ThreeJSState.OnBlueprintPartImported = noop;
    }
    getFloorComId() {
        const floorArticle = this.session
            .getAllArticles()
            .find(article => article.articleId === "FLOOR");
        if (floorArticle !== undefined) {
            return floorArticle.comId;
        }
        return undefined;
    }
    getWallComIds() {
        return this.session
            .getFilteredArticles(article => article.articleId === "WALL")
            .map(article => article.comId);
    }
    getInitialConfig(category, categoryItemIndex, lastConfig) {
        if (lastConfig !== undefined) {
            if (category === "Floor") {
                return lastConfig.floor;
            }
            if (lastConfig.walls.length > categoryItemIndex) {
                return lastConfig.walls[categoryItemIndex];
            }
            return lastConfig.walls[0];
        }
        if (this.defaultConfiguration !== undefined) {
            if (category === "Floor") {
                return this.defaultConfiguration.floor;
            }
            return this.defaultConfiguration.wall;
        }
        return undefined;
    }
    calculateFloorComponentArea(component) {
        // area of floor
        const getMesh = (parent) => {
            if (parent instanceof THREE.Mesh) {
                return parent;
            }
            let mesh;
            parent.children.some(ch => {
                mesh = getMesh(ch);
                if (mesh !== undefined) {
                    return true;
                }
                return false;
            });
            return mesh;
        };
        const mesh = getMesh(component);
        const geometry = mesh.geometry;
        let area = 0;
        if (geometry["vertices"] && geometry["faces"]) {
            const tV = geometry["vertices"];
            geometry["faces"].forEach((face) => {
                const tAB = new THREE.Vector3().subVectors(tV[face.a], tV[face.b]);
                const tAC = new THREE.Vector3().subVectors(tV[face.a], tV[face.c]);
                const tCross = new THREE.Vector3().crossVectors(tAB, tAC);
                area += tCross.length() / 2;
            });
        }
        return area;
    }
}
