import { BimCoreApi } from './BimCoreApi';
import { BimChangeType } from './loader/bim-api-client';
import { BimTwinfinityApiClient } from './loader/client/BimTwinfinityApiClient';
import { Http, HttpStatusCode } from './http';
import { telemetry } from './Telemetry';
import { SeverityLevel } from '@microsoft/applicationinsights-web';
import { wrapAuthorizationHeaderProvider } from './loader/client/AuthHelpers';
import { Discipline } from './loader/discipline';
import { Permissions } from './loader/Permission';
import { LayerDefinitions } from './loader/LayerDefinitions';
/**
 * BimApi is the main API class. This a subclass of {@link BimCoreApi} that is extended
 * with information only available from the Twinfinity API backend.
 */
export class BimApi extends BimCoreApi {
    _apiClient;
    _info;
    _containerInfo;
    _bimApiOptions;
    _twinfinitySession;
    /**
     * Creates a new instance of {@link BimApi}.
     * @param _htmlElementOrId ID of the HTML element where the viewer should be created.
     * @param absoluteContainerUrlOrApiClient Absolute URL to the container.
     * @param options Creation options.
     * @example
     * ```typescript
     * // Create an API instance.
     * const api = await BimApi.create('viewer', 'project-url'); // Open project directly.
     * const api = await BimApi.create('viewer', new BimTwinfinityApiClient(url)); // Create API. If a valid container url is given, the API automatically switches to it, otherwise calls api.setContainer() afterwards.
     * ```
     */
    static async create(_htmlElementOrId, absoluteContainerUrlOrApiClient, options) {
        if (options?.applicationName) {
            telemetry.role = options.applicationName;
        }
        if (BimApi.isTwinfinityApiClient(absoluteContainerUrlOrApiClient)) {
            return BimCoreApi.createApi(_htmlElementOrId, (canvas) => new BimApi(canvas, absoluteContainerUrlOrApiClient, options));
        }
        // absoluteContainerUrlOrApiClient was either a URL or a string representing a URL
        const absoluteUrl = new URL(decodeURI(absoluteContainerUrlOrApiClient.toString()).toLowerCase());
        const session = options && 'session' in options ? options.session : undefined;
        const apiClient = new BimTwinfinityApiClient(absoluteUrl, {
            session,
            applicationName: options?.applicationName
        });
        const api = await BimCoreApi.createApi(_htmlElementOrId, (canvas) => new BimApi(canvas, apiClient, options));
        try {
            await api.setContainer(absoluteUrl);
        }
        catch (err) {
            // Means that the passed url did not point to a valid container. In that case we just return the API
            // the user will have to call api.setContainer() later with a valid project. This can happen if API is created like this
            // created('canvasId', "https://bim.demo.projektstruktur.se") which is a valid use case.
            telemetry.trackTrace({
                message: `Failed to setContainer(${absoluteUrl.href}).`,
                severityLevel: SeverityLevel.Error
            });
        }
        return api;
    }
    /** Constructor.
     * @param canvas Canvas element where 3D content will be rendered.
     * @param _apiClient Methods for communicating with the backend.
     */
    constructor(canvas, _apiClient, bimApiOptions) {
        super(canvas, _apiClient, {
            wasmLoaderOptions: bimApiOptions?.wasmLoaderOptions,
            twinInputResolver: (input) => BimApi.resolveTwinInput(_apiClient, input)
        });
        this._apiClient = _apiClient;
        const givenOptions = bimApiOptions ?? { isPSAuthenticationRedirectEnabled: false };
        this._bimApiOptions = {
            isPSAuthenticationRedirectEnabled: givenOptions.isPSAuthenticationRedirectEnabled
        };
        if (bimApiOptions?.isPSAuthenticationRedirectEnabled !== true) {
            this._twinfinitySession = bimApiOptions?.session;
        }
    }
    /** General Twinfinity information such as customer portal name, Twinfinity API version etc. */
    info() {
        return this._info;
    }
    /** Direct access to the methods for communicating the backend. */
    get backend() {
        return this._apiClient;
    }
    /**
     * Gives information on currently selected container such as title, language
     * and also details of the current user.
     */
    get currentContainerInfo() {
        return this._containerInfo;
    }
    /**
     * Sets the container of the current BimApi instance.
     * @param containerOrUrl Container object or absolute URL Example: https://bim.foo.com/sites/portal/projects/\{project name\}
     * @param options Loading options
     */
    async setContainer(containerOrUrl, options) {
        const url = 'url' in containerOrUrl ? new URL(containerOrUrl.url) : containerOrUrl;
        const containerInfo = await this.backend.getContainerInfo(url);
        if (!containerInfo.ok) {
            throw new Error(`${url.href} does not exist.`);
        }
        this._containerInfo = await containerInfo.value;
        return await super.setContainer(containerOrUrl, options);
    }
    static async resolveTwinInput(apiClient, input) {
        const { twinId, version } = input;
        const twinClient = apiClient.twins;
        const twin = await twinClient.getTwin(version !== undefined ? { id: twinId, version } : twinId);
        const twinContent = version !== undefined
            ? await twinClient.getTwinVersionContent(twinId, version)
            : await twinClient.getTwinContent(twinId);
        if (!twinContent.content.find((c) => c.format === 't8cm')) {
            throw new Error(`Twin ${twinId} does not have a client model (t8cm)`);
        }
        const resolvedVersion = twinContent.twinVersion;
        const baseUrl = BimApi.getBaseUrl(apiClient).toString().replace(/\/$/, '');
        const contentsUrl = `${baseUrl}/_ps/twins/${twinId}/versions/${resolvedVersion}/contents`;
        const contentByFormat = new Map(twinContent.content.map((c) => [c.format, new URL(`${contentsUrl}/${c.id}/content`)]));
        const resourceUrl = {
            t8cm: contentByFormat.get('t8cm'),
            t8gd: contentByFormat.get('t8gd'),
            t8gi: contentByFormat.get('t8gi')
        };
        return {
            type: BimChangeType.Ifc,
            ownerSystem: 'twinfinity',
            id: twin.id,
            version: resolvedVersion,
            name: twin.name,
            url: `${baseUrl}/_ps/twins/${twin.id}`,
            path: `/_ps/twins/${twin.id}`,
            containerId: twin.relatedContainerIds?.[0] ?? '',
            blobId: `twin_${twin.id}_v${resolvedVersion}`,
            format: 'ifc',
            discipline: Discipline.getOrAdd('NA'),
            productCount: -1,
            etag: twin.etag ?? '',
            metadata: {
                ifc: {
                    hasParseError: false,
                    status: 'Processed',
                    url: {
                        t8gd: resourceUrl.t8gd?.toString()
                    }
                },
                serverRelativeUrl: `/_ps/twins/${twin.id}`
            },
            floors: [],
            classes: [],
            permissionsString: '',
            permissions: new Permissions('view'),
            apiUrl: `${baseUrl}/_ps/api/`,
            resourceUrl: resourceUrl,
            availableLayerDefinitions: new LayerDefinitions(),
            transform: input.transform
        };
    }
    static getBaseUrl(apiClient) {
        if ('baseUrl' in apiClient && apiClient.baseUrl instanceof URL) {
            return apiClient.baseUrl;
        }
        return new URL(`https://${apiClient.id}`);
    }
    async initialize() {
        await super.initialize();
        if (this._twinfinitySession != null) {
            const session = this._twinfinitySession;
            Http.setAuthorizationHeaderProvider(wrapAuthorizationHeaderProvider(session));
            delete this._twinfinitySession;
        }
        const rootContainerInfoResponse = await this.backend.getContainerInfo(); // No initial url get top most container info
        if (this._bimApiOptions?.isPSAuthenticationRedirectEnabled &&
            rootContainerInfoResponse.status === HttpStatusCode.Unauthorized) {
            telemetry.trackTrace({
                severityLevel: SeverityLevel.Warning,
                message: 'Authentication by third party cookies has been deprecated. See https://help.twinfinity.com/space/HCFD/125370380/KB%3A+Changes+in+Authentication+Method for further information.'
            });
            // Redirect to projektstruktur login page and force user to login. Projekstruktur will
            // then redirect us back to the page where this code is hosted. Hence we will start executing
            // the this.initialize() method again. However this time container info will report
            // that we are logged in.
            const returnUrl = encodeURIComponent(window.parent.location.href);
            const psUrl = BimApi.getPsUrl(rootContainerInfoResponse.url);
            window.parent.location.href = `${psUrl.protocol}//${psUrl.hostname}/sites/portal/_layouts/PS_Core/Twinfinity/Login.aspx?TFUrl=${returnUrl}`;
        }
        else if (rootContainerInfoResponse.status === HttpStatusCode.NotFound) {
            throw new Error(`Backend API does not exist at ${rootContainerInfoResponse.url}`);
        }
        else if (!rootContainerInfoResponse.ok) {
            throw new Error(`Failed to connect to ${rootContainerInfoResponse.url}. Http status ${rootContainerInfoResponse.status}, ${rootContainerInfoResponse.statusText}`);
        }
        this._containerInfo = await rootContainerInfoResponse.value;
        telemetry.setAuthenticatedUserContext(this._containerInfo.user.name, telemetry.accountId);
        // Call system endpoints to get system info
        this._info = await this.backend.getInfo();
    }
    static getPsUrl(url) {
        const psUrl = new URL(url);
        psUrl.hostname = psUrl.hostname.replace(/^bim\./i, '');
        return psUrl;
    }
    static isTwinfinityApiClient(arg) {
        return typeof arg.getContainers === 'function';
    }
}
//# sourceMappingURL=BimApi.js.map