/** @internal NOTE: Internal APIs. Subject to change. Use of these APIs in production applications is not supported. */ /** */

import { BimIfcPropertiesResponse } from './bim-format-types';
import { BimIfcObject } from './bim-ifc-object';
import { HttpResponseType } from '../http';
import '../MapExtensions';
import { BimCoreApiClient } from './client/BimCoreApiClient';
import { BimPropertySetReader } from './bim-property-set-reader';
import { BimTypeObject } from './BimTypeObject';
import { telemetry } from '../Telemetry';
import type { Writeable } from '../Types';
import type { T8PropertySet, T8QuantitySet, T8Unit } from '@twinfinity/t8-client-model';

function addPropertySet(
    obj: { readonly propertySets: Record<string, T8PropertySet<T8Unit>> },
    name: string,
    set: T8PropertySet<T8Unit>
): void {
    const current = obj.propertySets;
    if (Object.isFrozen(current)) {
        (obj as Writeable<typeof obj>).propertySets = { ...current, [name]: set };
    } else {
        current[name] = set;
    }
}

function addQuantitySet(
    obj: { readonly quantitySets: Record<string, T8QuantitySet<T8Unit>> },
    name: string,
    set: T8QuantitySet<T8Unit>
): void {
    const current = obj.quantitySets;
    if (Object.isFrozen(current)) {
        (obj as Writeable<typeof obj>).quantitySets = { ...current, [name]: set };
    } else {
        current[name] = set;
    }
}

export class BimPropertySetRepository {
    readonly _propertyReader = new BimPropertySetReader();
    readonly _isPropertySetUrlAdded = new Map<string, Promise<boolean>>();

    public constructor(private readonly _bimApi: BimCoreApiClient) {}

    public assignPropertySetsToProducts(
        propertysetUrl: URL,
        ifcObjects: BimIfcObject[],
        ifcTypeObjects: BimTypeObject[]
    ): Promise<boolean> {
        const key = propertysetUrl.href.toLowerCase();
        const loadingPromise = this._isPropertySetUrlAdded.get(key);
        if (loadingPromise) {
            return loadingPromise;
        }
        const p = this.fetchDecodeAndAssignPropertySetsToIfcObjects(propertysetUrl, ifcObjects, ifcTypeObjects);
        this._isPropertySetUrlAdded.set(key, p);
        return p;
    }

    public clear(): void {
        this._isPropertySetUrlAdded.clear();
        this._propertyReader.clear();
    }

    private async fetchDecodeAndAssignPropertySetsToIfcObjects(
        propertysetUrl: URL,
        ifcObjects: BimIfcObject[],
        ifcTypeObjects: BimTypeObject[]
    ): Promise<boolean> {
        const bimIfcPropertiesDataResponse = await this._bimApi.get<BimIfcPropertiesResponse>(
            propertysetUrl,
            HttpResponseType.json
        );
        if (bimIfcPropertiesDataResponse.status !== 200) {
            telemetry.trackTrace(
                { message: 'Failed to load property set data' },
                {
                    httpStatus: bimIfcPropertiesDataResponse.status,
                    reason: bimIfcPropertiesDataResponse.statusText,
                    url: propertysetUrl.href
                }
            );
            return false;
        }

        const bimIfcPropertiesData = await bimIfcPropertiesDataResponse.value;

        const decodedPropertySets = this._propertyReader.decodePropertySets(bimIfcPropertiesData);

        for (const ifcObject of ifcObjects) {
            const propertySetIndicesForProduct = bimIfcPropertiesData.products[ifcObject.entityLabelInIfc] ?? [];
            for (const idx of propertySetIndicesForProduct) {
                const decoded = decodedPropertySets[idx];
                if (decoded.type === 'properties') {
                    let pset = decoded.value as T8PropertySet<T8Unit>;
                    if (decoded.gidPropertyNames) {
                        pset = this._propertyReader.getT8PropertySetForProduct(ifcObject, decoded);
                    }
                    addPropertySet(ifcObject, decoded.name, pset);
                } else {
                    addQuantitySet(ifcObject, decoded.name, decoded.value as T8QuantitySet<T8Unit>);
                }
            }
        }

        if (bimIfcPropertiesData.types != null) {
            for (const ifcTypeObject of ifcTypeObjects) {
                const propertySetIndicesForProduct = bimIfcPropertiesData.types[ifcTypeObject.id] ?? [];
                for (const idx of propertySetIndicesForProduct) {
                    const decoded = decodedPropertySets[idx];
                    if (decoded.type === 'properties') {
                        addPropertySet(ifcTypeObject, decoded.name, decoded.value as T8PropertySet<T8Unit>);
                    } else {
                        addQuantitySet(ifcTypeObject, decoded.name, decoded.value as T8QuantitySet<T8Unit>);
                    }
                }
            }
        }

        return true;
    }
}
