import { BimTransformsRepository } from './bim-transforms-repository';
import { BimIfcObject, BimIfcSpace, BimIfcBuildingStorey } from './bim-ifc-object';
import { BimIfcStyle } from './bim-ifc-style';
import { BimIfcClass } from './bim-ifc-class';
import { IfcFileIndexReader } from './IfcFileIndexReader';
import { IfcProductMesh } from './IfcProductMesh';
import { BimTypeObjectRepository } from './BimTypeObjectRepository';
import { BimObjectAttributeReader } from './BimObjectAttributeReader';
import { Fail, isFailure } from '../fail';
import { T8BimIfcLoaderElement } from './T8BimIfcLoaderElement';
/**
 * Represents an IFC file and provides access to its data.
 */
export class BimIfcLoaderElement {
    loader;
    ifcHttpResource;
    materials;
    index;
    _propertySetRepository;
    _geometricFeaturesRepository;
    _vertexDataRepository;
    loadOptions;
    transform;
    _numberOfVertices = 0;
    _numberOfIndices = 0;
    _ifcUrl;
    _objectAttributes;
    /** @inheritDoc */
    transformsRepository;
    /** @inheritDoc */
    typeObjectRepository;
    /** @inheritDoc */
    project;
    /** @inheritDoc */
    spaces = [];
    /** @inheritDoc */
    floors = [];
    /** @inheritDoc */
    products = [];
    /** @inheritDoc */
    meshCount;
    /** @inheritDoc */
    discipline;
    /** @inheritDoc */
    styles = [];
    /** @inheritDoc */
    classes = [];
    /** @inheritDoc */
    ifcName;
    _propertySetLoadResult;
    _geometricFeaturesPromise;
    constructor(loader, ifcHttpResource, materials, index, _propertySetRepository, _geometricFeaturesRepository, _vertexDataRepository, loadOptions, transform) {
        this.loader = loader;
        this.ifcHttpResource = ifcHttpResource;
        this.materials = materials;
        this.index = index;
        this._propertySetRepository = _propertySetRepository;
        this._geometricFeaturesRepository = _geometricFeaturesRepository;
        this._vertexDataRepository = _vertexDataRepository;
        this.loadOptions = loadOptions;
        this.transform = transform;
        this.discipline = ifcHttpResource.discipline;
        this.ifcName = ifcHttpResource.name;
        this.styles = index.styles.map((s) => new BimIfcStyle(s));
        this.classes = index.classes.map((classId) => BimIfcClass.getOrAdd(classId));
        this.transformsRepository = new BimTransformsRepository(this);
        this._ifcUrl = new URL(ifcHttpResource.url);
        this._objectAttributes = new BimObjectAttributeReader().read(this.index);
        this.typeObjectRepository = new BimTypeObjectRepository(this.index, this._objectAttributes);
        this.project = BimIfcObject.create(this);
        const referencedMeshDescriptors = new Set();
        const referencedTransforms = new Set();
        const leafNodesWithNoGeometry = new Set();
        let meshCount = 0;
        for (const o of this.project.entries()) {
            let mD;
            const len = o.productMeshes?.length ?? 0;
            for (let idx = 0; idx < len; ++idx) {
                const m = IfcProductMesh.from(o, o.productMeshes[idx]);
                o.productMeshes[idx] = m;
                mD = o.meshDescriptor(m);
                // Keep track of all meshdescriptors and transforms that actually are referenced by
                // meshes. We use this to trim away all meshdescriptors and transforms that have no references
                // later on. This is especialy useful in cases where we forcefully set some ifc objects
                // as not having a geometry. That means that we can clean out all geometries that referenced those
                // objects and save RAM.
                referencedMeshDescriptors.add(m.mi);
                referencedTransforms.add(m.wt);
                this._numberOfIndices += mD.i;
                this._numberOfVertices += mD.p;
            }
            if (loadOptions?.removeLeafIfcObjectsWithNoGeometry && o.childCount === 0 && !o.hasGeometry) {
                leafNodesWithNoGeometry.add(o);
            }
            this.discipline.addReferencedClass(o.class);
            this.discipline.objectCount = this.discipline.objectCount + 1;
            o.class.addReferencedClass(o.discipline);
            o.class.objectCount = o.class.objectCount + 1;
            if (o.hasGeometry) {
                meshCount += o.productMeshes.length;
                o.class.objectsWithGeometryCount = o.class.objectsWithGeometryCount + 1;
                this.discipline.objectsWithGeometryCount = this.discipline.objectsWithGeometryCount + 1;
                if (o.enclosingFloor) {
                    o.enclosingFloor.objectsWithGeometryCount =
                        o.enclosingFloor.objectsWithGeometryCount + 1;
                }
            }
            this.products.push(o);
            if (o instanceof BimIfcSpace) {
                this.spaces.push(o);
            }
            if (o instanceof BimIfcBuildingStorey) {
                this.floors.push(o);
            }
        }
        this.meshCount = meshCount;
        // Trim away leafnodes that has no geometry and no children
        this.deleteLeafNodesWithNoGeometry(leafNodesWithNoGeometry);
        // Check if we need to remove unreferenced mesh descriptors.
        this.deleteUnreferencedMeshDescriptors(referencedMeshDescriptors);
        this.deleteUnreferencedTransforms(referencedTransforms);
    }
    /**
     * Creates a new instance of the BimIfcLoaderElement class.
     *
     * @param bimApi - The {@link BimCoreApiClient} instance.
     * @param materials - The {@link Materials} instance.
     * @param vertexDataRepository - The {@link BimVertexDataRepository} instance.
     * @param propertySetRepository - The {@link BimPropertySetRepository} instance.
     * @param geometricFeaturesRepository - The {@link BimGeometricFeaturesRepository} instance.
     * @param parent - The {@link BimIfcLoaderElementParent} instance.
     * @param ifcHttpResource - The {@link BimChangeIfc} instance.
     * @param options - Optional. The {@link BimApiIfcObjectsLoadOptions} instance.
     * @param transform - Optional. The `Matrix` instance that will be applied to all transformations in the IFC.
     * @returns A Promise that resolves to a {@link BimIfcLoaderElement} instance or a {@link BimIfcLoaderElementCreateFailure} instance.
     */
    static async create(bimApi, materials, vertexDataRepository, propertySetRepository, geometricFeaturesRepository, parent, ifcHttpResource, options, transform) {
        const { id, version, url, resourceUrl } = ifcHttpResource;
        if (resourceUrl.t8cm) {
            return T8BimIfcLoaderElement.create(bimApi, materials, vertexDataRepository, parent, ifcHttpResource, options, transform);
        }
        let indexResult;
        if (resourceUrl.idx) {
            const indexReader = new IfcFileIndexReader(bimApi);
            indexResult = await indexReader.read(resourceUrl.idx, ifcHttpResource.discipline, transform);
        }
        else {
            return Fail({
                httpStatus: 404,
                reason: 'No resource url.',
                ifc: { id, version, url }
            });
        }
        if (isFailure(indexResult)) {
            return Fail({
                httpStatus: indexResult.httpStatus,
                reason: indexResult.reason,
                ifc: { id, version, url }
            });
        }
        return new BimIfcLoaderElement(parent, ifcHttpResource, materials, indexResult, propertySetRepository, geometricFeaturesRepository, vertexDataRepository, options, transform);
    }
    /** @inheritDoc */
    get ifc() {
        return this.ifcHttpResource;
    }
    /** @inheritDoc */
    get ifcUrl() {
        return this._ifcUrl;
    }
    /** @inheritDoc */
    get numberOfVertices() {
        return this._numberOfVertices;
    }
    /** @inheritDoc */
    get numberOfIndices() {
        return this._numberOfIndices;
    }
    /** @inheritDoc */
    get numberOfTriangles() {
        return this._numberOfIndices / 3;
    }
    /** @inheritDoc */
    get ifcProductsWithGeometryCount() {
        return this.meshCount;
    }
    /** @inheritDoc */
    get indexUrl() {
        return this.ifcHttpResource.resourceUrl.idx;
    }
    /** @inheritDoc */
    get geometryUrl() {
        return this.ifcHttpResource.resourceUrl.t8gd ?? this.ifcHttpResource.resourceUrl.geom;
    }
    /** @inheritDoc */
    get propertiesUrl() {
        return this.ifcHttpResource.resourceUrl.prop;
    }
    /** @inheritDoc */
    get geometricFeaturesRepository() {
        return this._geometricFeaturesRepository;
    }
    /** @inheritDoc */
    get geometricFeaturesUrl() {
        return this.ifcHttpResource.resourceUrl['geom-feat'];
    }
    /** @inheritDoc */
    ensureVertexDataLoaded() {
        return this._vertexDataRepository.load(this);
    }
    /** @inheritDoc */
    getVertexData(productMeshDescriptor) {
        return this._vertexDataRepository.get(productMeshDescriptor);
    }
    get geometryFormat() {
        return this.ifcHttpResource.resourceUrl.t8gd != null ? 't8gd' : 'geom';
    }
    /** @inheritDoc */
    tryCreateOrExtendAABBFromRegions(min, max, o) {
        const options = { onlyMostPopulatedRegions: true, ...(o ?? {}) };
        let isValid = false;
        const tmpMin = min.clone();
        const tmpMax = max.clone();
        for (const region of this.index.regions) {
            if (options.onlyMostPopulatedRegions && !region.isMostPopulated) {
                continue;
            }
            const { bbox } = region;
            if (!this.isValidAABB(bbox)) {
                continue; // Ignore all invalid AABBs
            }
            tmpMin.minimizeInPlaceFromFloats(bbox[0], bbox[1], bbox[2]);
            tmpMax.maximizeInPlaceFromFloats(bbox[3], bbox[4], bbox[5]);
            isValid = true;
        }
        if (isValid) {
            min.copyFrom(tmpMin);
            max.copyFrom(tmpMax);
        }
        return isValid;
    }
    /** @inheritDoc */
    writeVertexData(ifcProductMesh, transform, destination, offset, aabb, options) {
        return this._vertexDataRepository.write(ifcProductMesh, transform, destination, offset, aabb, options);
    }
    /** @inheritDoc */
    get hasBatchSupport() {
        return this._vertexDataRepository.hasBatchSupport;
    }
    /** @inheritDoc */
    get wasmMemoryUsage() {
        return this._vertexDataRepository.wasmMemoryUsage;
    }
    /** @inheritDoc */
    writeBatch(meshes, destination, offset) {
        return this._vertexDataRepository.writeBatch(meshes, destination, offset);
    }
    /** @inheritDoc */
    async loadPropertySets(clearCache = false) {
        try {
            if (!this.propertiesUrl) {
                return false;
            }
            if (this._propertySetLoadResult !== undefined) {
                return this._propertySetLoadResult;
            }
            this._propertySetLoadResult = await this._propertySetRepository.assignPropertySetsToProducts(this.propertiesUrl, this.products, this.typeObjectRepository.types);
            return this._propertySetLoadResult;
        }
        finally {
            if (clearCache) {
                this._propertySetRepository.clear();
            }
        }
    }
    /**
     * @internal
     * @hidden
     */
    loadGeometricFeatures(clearCache = false) {
        // Cache the in-flight promise so concurrent callers share one load. Without this,
        // each caller passes the not-loaded check and races into a duplicate load.
        if (!this._geometricFeaturesPromise) {
            this._geometricFeaturesPromise = this.doLoadGeometricFeatures().catch((err) => {
                // Drop the failed promise so a subsequent call can retry.
                this._geometricFeaturesPromise = undefined;
                throw err;
            });
        }
        if (clearCache) {
            return this._geometricFeaturesPromise.finally(() => this._geometricFeaturesRepository.clear());
        }
        return this._geometricFeaturesPromise;
    }
    async doLoadGeometricFeatures() {
        // t8gd carries its own edge section whose indices match the position-deduplicated
        // vertex array we expose at render time. The standalone `.geom-feat` file is
        // indexed against the welded vertex array, which does NOT match t8gd's layout
        // and produces garbage edges when combined with t8gd positions.
        if (this.geometryFormat === 't8gd') {
            await this.ensureVertexDataLoaded();
            this._geometricFeaturesRepository.populateFromVertexDataRepository(this.products, this._vertexDataRepository);
            return true;
        }
        if (!this.geometricFeaturesUrl) {
            return false;
        }
        return this._geometricFeaturesRepository.assignGeometricFeaturesToProducts(this.geometricFeaturesUrl);
    }
    deleteUnreferencedTransforms(referencedTransforms) {
        const transformsLen = this.index.transforms.length;
        if (transformsLen !== referencedTransforms.size) {
            for (let transformIdx = 0; transformIdx < transformsLen; transformIdx++) {
                if (!referencedTransforms.has(transformIdx)) {
                    // Set element in meshDescriptors to undefined. This allows GC to collect it
                    // it will also ensure that bim-vertex-data-repository.ts does not keep geometry
                    // relatited to it
                    this.index.transforms[transformIdx] = undefined;
                }
            }
        }
    }
    deleteUnreferencedMeshDescriptors(referencedMeshDescriptors) {
        const meshDescriptorLen = this.index.meshDescriptors.length;
        if (meshDescriptorLen !== referencedMeshDescriptors.size) {
            for (let meshDescriptorIdx = 0; meshDescriptorIdx < meshDescriptorLen; meshDescriptorIdx++) {
                if (!referencedMeshDescriptors.has(meshDescriptorIdx)) {
                    // Set element in meshDescriptors to undefined. This allows GC to collect it
                    // it will also ensure that bim-vertex-data-repository.ts does not keep geometry
                    // relatited to it
                    this.index.meshDescriptors[meshDescriptorIdx] = undefined;
                }
            }
        }
    }
    deleteLeafNodesWithNoGeometry(leafNodesWithNoGeometry) {
        while (leafNodesWithNoGeometry.size > 0) {
            for (const o of leafNodesWithNoGeometry) {
                o.remove();
                leafNodesWithNoGeometry.delete(o);
                if (o.parent && o.parent.childCount === 0 && !o.parent.hasGeometry) {
                    leafNodesWithNoGeometry.add(o.parent);
                }
            }
        }
    }
    isValidAABB(bbox) {
        const deltaX = bbox[3] - bbox[0];
        const deltaY = bbox[4] - bbox[1];
        const deltaZ = bbox[5] - bbox[2];
        const isAtleast2D = [deltaX > 0, deltaY > 0, deltaZ > 0].filter(Boolean).length > 1;
        return bbox.length >= 6 && deltaX >= 0 && deltaY >= 0 && deltaZ >= 0 && isAtleast2D;
    }
}
//# sourceMappingURL=bim-ifc-loader-element.js.map