import * as i1 from '@angular/common/http';
import { HttpResponse, HttpClient, HttpHeaders, HttpClientModule } from '@angular/common/http';
import * as i0 from '@angular/core';
import { Injectable, InjectionToken, Optional, Inject, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { tap, finalize, map, catchError, switchMap, retry, flatMap, skipWhile, share, concatMap, filter, withLatestFrom } from 'rxjs/operators';
import { Observable, throwError, BehaviorSubject, of, ReplaySubject, merge } from 'rxjs';
import { TaskType } from '@vcd/bindings/vcloud/api/rest/schema_v1_5';

// tslint:disable:variable-name
// tslint:disable-next-line:no-namespace
var Query;
(function (Query) {
    class Builder {
        constructor() {
            this._format = Format.ID_RECORDS;
            this._links = true;
            this._pageSize = 25;
        }
        static getBuilder() {
            return new Builder();
        }
        static ofType(type) {
            const qb = new Builder();
            qb._type = type;
            return qb;
        }
        format(format) {
            this._format = format;
            return this;
        }
        links(links) {
            this._links = links;
            return this;
        }
        pageSize(pageSize) {
            this._pageSize = pageSize;
            return this;
        }
        fields(...fields) {
            this._fields = fields;
            return this;
        }
        filter(filter) {
            this._filter = filter;
            return this;
        }
        sort(...sort) {
            this._sort = sort;
            return this;
        }
        get() {
            let query = `?type=${this._type}&format=${this._format}&links=${this._links}&pageSize=${this._pageSize}`;
            if (this._fields && this._fields.length > 0) {
                query += `&fields=${this._fields.join(',')}`;
            }
            if (this._filter) {
                query += `&filter=${this._filter}`;
            }
            if (this._sort) {
                this._sort.forEach(s => {
                    query += `&${s.reverse ? 'sortDesc' : 'sortAsc'}=${s.field}`;
                });
            }
            return query;
        }
        getCloudAPI() {
            let query = `?pageSize=${this._pageSize}`;
            if (this._filter) {
                query += `&filter=${this._filter}`;
            }
            if (this._sort) {
                this._sort.forEach(s => {
                    query += `&${s.reverse ? 'sortDesc' : 'sortAsc'}=${s.field}`;
                });
            }
            return query;
        }
    }
    Query.Builder = Builder;
    class Format {
    }
    Format.ID_RECORDS = 'idrecords';
    Format.RECORDS = 'records';
    Format.REFERENCES = 'references';
    Query.Format = Format;
})(Query || (Query = {}));

// tslint:disable-next-line:no-namespace
var Filter;
(function (Filter) {
    class Operators {
    }
    Operators.OR = ',';
    Operators.AND = ';';
    Operators.GT = '=gt=';
    Operators.GE = '=ge=';
    Operators.LT = '=lt=';
    Operators.LE = '=le=';
    Operators.EQ = '==';
    Operators.NEQ = '!=';
    /**
     * Collection of strategies for wilcard string matching.
     */
    class MatchMode {
    }
    /**
     * Match the start of a string.
     */
    MatchMode.START = 'START';
    /**
     * Match the end of a string.
     */
    MatchMode.END = 'END';
    /**
     * Match anywhere in the string.
     */
    MatchMode.ANYWHERE = 'ANYWHERE';
    Filter.MatchMode = MatchMode;
    class BuilderChain {
        constructor(parent) {
            this.result = '';
            this.parent = parent;
        }
        query() {
            return this.buildPartial();
        }
        // tslint:disable-next-line:max-line-length
        and(condition1, condition2, conditionN) {
            if (!condition1) {
                return this.simpleAnd();
            }
            this.result += '(' + condition1.buildPartial() + Operators.AND + condition2.buildPartial();
            if (conditionN && conditionN.length) {
                conditionN.forEach((condition) => {
                    this.result += Operators.AND + condition.buildPartial();
                });
            }
            this.result += ')';
            return this;
        }
        simpleAnd() {
            if (this.currentCompositeOp === Operators.OR || this.parent && this.parent.currentCompositeOp === Operators.OR) {
                if (this.parent) {
                    this.parent.result = '(' + this.parent.result;
                    this.result += ')';
                }
                else {
                    this.wrap();
                }
                this.currentCompositeOp = Operators.AND;
            }
            this.result += Operators.AND;
            return this;
        }
        // tslint:disable-next-line:max-line-length
        or(condition1, condition2, conditionN) {
            if (!condition1) {
                return this.simpleOr();
            }
            this.result += '(' + condition1.buildPartial() + Operators.OR + condition2.buildPartial();
            if (conditionN && conditionN.length) {
                conditionN.forEach((condition) => {
                    this.result += Operators.OR + condition.buildPartial();
                });
            }
            this.result += ')';
            return this;
        }
        simpleOr() {
            if (this.currentCompositeOp === Operators.AND || (this.parent && this.parent.currentCompositeOp === Operators.AND)) {
                if (this.parent) {
                    this.parent.result = '(' + this.parent.result;
                    this.result += ')';
                }
                else {
                    this.wrap();
                }
                this.currentCompositeOp = Operators.OR;
            }
            this.result += Operators.OR;
            return this;
        }
        wrap() {
            this.result = '(' + this.result + ')';
            this.currentCompositeOp = null;
            return this;
        }
        buildPartial() {
            return (this.parent) ? this.parent.buildPartial() + this.result : this.result;
        }
        is(property) {
            const builder = new BuilderChain(this);
            builder.result = property;
            return builder;
        }
        equalTo(value, ...moreValues) {
            return this.condition(Operators.EQ, value, ...moreValues);
        }
        notEqualTo(value) {
            return this.condition(Operators.NEQ, value);
        }
        lessThan(value) {
            return this.condition(Operators.LT, value);
        }
        lessOrEqualTo(value) {
            return this.condition(Operators.LE, value);
        }
        greaterThan(value) {
            return this.condition(Operators.GT, value);
        }
        greaterOrEqualTo(value) {
            return this.condition(Operators.GE, value);
        }
        like(value, mode = MatchMode.START) {
            let wildcardValue;
            switch (mode) {
                case MatchMode.START:
                    wildcardValue = `${value}*`;
                    break;
                case MatchMode.END:
                    wildcardValue = `*${value}`;
                    break;
                case MatchMode.ANYWHERE:
                    wildcardValue = `*${value}*`;
                    break;
                default:
                    wildcardValue = value;
                    break;
            }
            return this.condition(Operators.EQ, wildcardValue);
        }
        condition(operator, value, ...moreValues) {
            const name = this.result;
            this.result += operator + encodeURI(value);
            if (moreValues.length) {
                moreValues.forEach((next) => {
                    this.result += ',' + name + operator + encodeURI(next);
                });
                this.currentCompositeOp = Operators.OR;
            }
            return this;
        }
    }
    /**
     * Builds a FIQL search condition using a fluent interface.
     */
    class Builder {
        /**
         * Create a simple property to be evaulated as a filter condition.
         *
         * @param property the name of the property
         * @returns a Property instance for defining a filter condition
         */
        is(property) {
            return new BuilderChain().is(property);
        }
        /**
         * Create a conjunction (AND) condition from existing conditions.
         *
         * @param condition1 first condition
         * @param condition2 second condition
         * @param conditionN any additional conditions
         * @returns an evaluatable filter condition
         */
        and(condition1, condition2, conditionN) {
            return new BuilderChain().and(condition1, condition2, conditionN);
        }
        /**
         * Create a disjunction (OR) condition from existing conditions.
         *
         * @param condition1 first condition
         * @param condition2 second condition
         * @param conditionN any additional conditions
         * @returns an evaluatable filter condition
         */
        or(condition1, condition2, conditionN) {
            return new BuilderChain().or(condition1, condition2, conditionN);
        }
    }
    Filter.Builder = Builder;
})(Filter || (Filter = {}));

// tslint:disable:variable-name
class ApiResultService {
    constructor() {
        this._results = [];
    }
    get results() {
        return this._results;
    }
    add(result) {
        this._results = [result, ...this._results.slice(0, 99)];
    }
    clear() {
        this._results = [];
    }
}
ApiResultService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: ApiResultService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
ApiResultService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: ApiResultService });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: ApiResultService, decorators: [{
            type: Injectable
        }] });
class ApiResult {
    get message() {
        return this._message;
    }
    get succeeded() {
        return this._succeeded;
    }
    get started() {
        return this._started;
    }
    get finished() {
        return this._finished;
    }
    constructor(message, succeeded, started, finished) {
        this._message = message;
        this._succeeded = succeeded;
        this._started = started;
        this._finished = finished;
    }
}

/**
 * This is the currently supported - albeit very minimal - public SDK.
 */
// Bind straight into the hooks provided by the container.
if (!window.System || !window.System.registry || !window.System.registry.get) {
    throw new Error('SystemJS registry not found');
}
let containerHooks = window.System.registry.get('@vcd/common');
if (!containerHooks) {
    containerHooks = window.System.registry.get('@vcd-ui/common');
}
if (!containerHooks) {
    throw new Error('VCD UI container hooks not present in SystemJS registry');
}
/**
 * Wire in as a string.  Gives the root URL for API access (for example, the load balancer URL
 * or the single-cell URL).
 */
const API_ROOT_URL = containerHooks.API_ROOT_URL;
/**
 * Wire in as a string.  Gives the root URL for the legacy Flex application.
 */
const FLEX_APP_URL = containerHooks.API_ROOT_URL;
/**
 * Wire in as a string.  Gives the current scope of the VCD-UI.  As of current, this will be
 * either 'tenant' for the tenant portal, or 'service-provider' for the service-provider portal.
 */
const SESSION_SCOPE = containerHooks.SESSION_SCOPE;
/**
 * Wire in as a string.  Gives the unique name (not the display name) of the current tenant
 * organization that the VCD-UI is being used for.
 */
const SESSION_ORGANIZATION = containerHooks.SESSION_ORGANIZATION;
/**
 * Wire in as a string.  Gives the UUID identifier of the current tenant
 * organization that the VCD-UI is being used for.
 */
const SESSION_ORG_ID = containerHooks.SESSION_ORG_ID ? containerHooks.SESSION_ORG_ID : '';
/**
 * Wire in as a string.  Gives the full root path for module assets (e.g. images, scripts, text files)
 *
 * ATTENTION!
 * Add || new InjectionToken to workaround the Angular security mechanics which prevent use of injection tokens
 * which potentially are not defiend. The same fix can be applied for the rest tokens if needed.
 */
const EXTENSION_ASSET_URL = containerHooks.EXTENSION_ASSET_URL || new InjectionToken('EXTENSION_ASSET_URL');
/**
 * Wire in as a string.  Gives the Angular 2 route that the module is registered under.
 */
const EXTENSION_ROUTE = containerHooks.EXTENSION_ROUTE;
/**
 * Wire in as a boolean.  True if running under the SDK, false if running in production.
 */
const SDK_MODE = containerHooks.SDK_MODE;
const ExtensionNavRegistrationAction = containerHooks.ExtensionNavRegistrationAction;
const AuthTokenHolderService = containerHooks.AuthTokenHolderService;
/**
 * Every component referenced by an entity action extension point must inherit from this.
 */
// tslint:disable-next-line:class-name
class _EntityActionExtensionComponent {
}
const EntityActionExtensionComponent = containerHooks.EntityActionExtensionComponent;
// tslint:disable-next-line:class-name
class _WizardExtensionComponent {
}
const WizardExtensionComponent = containerHooks.WizardExtensionComponent;
// tslint:disable-next-line:class-name
class _WizardExtensionWithValidationComponent extends _WizardExtensionComponent {
}
// tslint:disable-next-line:max-line-length
const WizardExtensionWithValidationComponent = containerHooks.WizardExtensionWithValidationComponent;
/**
 * Every component-based Extension Point that is renderd in Cloud Director UI Entity Details
 * must extend that class to obtain context about the Entity.
 *
 * See comments of the methods of the abstract class for more information.
 */
// tslint:disable-next-line:class-name
class _WizardExtensionWithContextComponent extends _WizardExtensionComponent {
}
// tslint:disable-next-line:max-line-length
const WizardExtensionWithContextComponent = containerHooks.WizardExtensionWithContextComponent;

class PluginModule {
    constructor(appStore) {
        this.appStore = appStore;
    }
    registerExtension(extension) {
        this.appStore.dispatch(new ExtensionNavRegistrationAction(extension));
    }
}
/**
 * Config object that is passed on VcdSdkModule init
 * using .forRoot() method. It can be used to store
 * configutration properties that are later used by the Services
 * provided.
 */
class VcdSdkConfig {
}
/**
 * Use this token for injecting custom HttpInterceptor implementations inside the HttpClient
 */
const VCD_HTTP_INTERCEPTORS = new InjectionToken("VCD_HTTP_INTERCEPTORS");

// tslint:disable:variable-name
class LoggingInterceptor {
    set enabled(enabled) {
        this._enabled = enabled;
        if (this._enabled && this._outputToConsole) {
            console.warn('API logging enabled but no provider found for ApiResultService.  Results will be output to the console.');
        }
    }
    constructor(apiResultService) {
        this.apiResultService = apiResultService;
        this._enabled = false;
        this._outputToConsole = !this.apiResultService;
    }
    intercept(req, next) {
        if (!this._enabled) {
            return next.handle(req);
        }
        const started = new Date();
        let succeeded;
        return next.handle(req)
            .pipe(tap(event => succeeded = event instanceof HttpResponse ? true : false, error => succeeded = false), finalize(() => {
            if (this._outputToConsole) {
                console.log(`${req.method} ${req.urlWithParams} completed in ${Date.now() - started.getTime()} ms.  Success: ${succeeded}`);
            }
            else {
                this.apiResultService.add(new ApiResult(`${req.method} ${req.urlWithParams}`, succeeded, started, new Date()));
            }
        }));
    }
}
LoggingInterceptor.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: LoggingInterceptor, deps: [{ token: ApiResultService, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
LoggingInterceptor.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: LoggingInterceptor });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: LoggingInterceptor, decorators: [{
            type: Injectable
        }], ctorParameters: function () {
        return [{ type: ApiResultService, decorators: [{
                        type: Optional
                    }] }];
    } });

/**
 * HTTP Headers
 */
const HTTP_HEADERS = Object.freeze({
    Authorization: 'Authorization',
    etag: 'etag',
    // Angular is dealing with case sensitive links despite the specification
    // https://github.com/angular/angular/issues/6142
    link: 'link',
    Link: 'Link',
    x_vcloud_authorization: 'x-vcloud-authorization'
});

// tslint:disable:variable-name
class RequestHeadersInterceptor {
    constructor() {
        this._enabled = true;
        this._version = '';
        this._authenticationHeader = HTTP_HEADERS.Authorization;
    }
    set enabled(_enabled) {
        this._enabled = _enabled;
    }
    set actAs(_actAs) {
        this._actAs = _actAs;
    }
    get version() {
        return this._version;
    }
    set version(_version) {
        this._version = _version;
    }
    set authentication(_authentication) {
        this._authentication = _authentication;
        this._authenticationHeader = (this._authentication && this._authentication.length > 32) ?
            HTTP_HEADERS.Authorization : HTTP_HEADERS.x_vcloud_authorization;
    }
    intercept(req, next) {
        let headers = req.headers;
        if (!headers.has('Accept')) {
            headers = this.setAcceptHeader(headers);
        }
        if (!headers.has('Content-Type')) {
            headers = this.setContentTypeHeader(headers, req.url);
        }
        if (this._authentication && !headers.has(HTTP_HEADERS.Authorization)) {
            headers = headers.set(this._authenticationHeader, this._authentication);
        }
        /**
         * Covers the case where the User set the ActAs token himself
         */
        if (!headers.has('X-VMWARE-VCLOUD-TENANT-CONTEXT') && this._actAs) {
            headers = headers.set('X-VMWARE-VCLOUD-TENANT-CONTEXT', this._actAs);
        }
        const customReq = req.clone({
            headers
        });
        return next.handle(customReq).pipe(map((res) => {
            if (res instanceof HttpResponse) {
                if (!res.body || !res.headers) {
                    return res;
                }
                if (res.headers.has(HTTP_HEADERS.link)) {
                    res.body.link = parseHeaderHateoasLinks(res.headers.get(HTTP_HEADERS.link));
                }
                if (res.headers.has(HTTP_HEADERS.Link)) {
                    res.body.link = parseHeaderHateoasLinks(res.headers.get(HTTP_HEADERS.Link));
                }
                if (res.headers.has(HTTP_HEADERS.etag)) {
                    res.body.etag = res.headers.get(HTTP_HEADERS.etag);
                }
            }
            return res;
        }));
    }
    setAcceptHeader(headers) {
        const value = headers.get('_multisite');
        headers = headers.delete('_multisite');
        return headers.set('Accept', [
            `application/*+json;version=${this._version}${value ? `;multisite=${value}` : ''}`,
            `application/json;version=${this._version}${value ? `;multisite=${value}` : ''}`
        ]);
    }
    setContentTypeHeader(headers, url) {
        if (url.indexOf('cloudapi') > -1) {
            return headers.set('Content-Type', 'application/json');
        }
        else {
            return headers.set('Content-Type', 'application/*+json');
        }
    }
}
RequestHeadersInterceptor.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: RequestHeadersInterceptor, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
RequestHeadersInterceptor.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: RequestHeadersInterceptor });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: RequestHeadersInterceptor, decorators: [{
            type: Injectable
        }] });

// tslint:disable:jsdoc-format
/**
 * An interceptor on the response chain that normalizes differences in
 * JSON payloads between vCloud Director version 9.1 and versions
 * greater than 9.1.
 *
 * In 9.1 (API version 30.0) the server serializes JSON and nests the payload in a value field:
 * ```
  {
    "name" : "{http://www.vmware.com/vcloud/versions}SupportedVersions",
    "declaredType" : "com.vmware.vcloud.api.rest.schema.versioning.SupportedVersionsType",
    "scope" : "javax.xml.bind.JAXBElement$GlobalScope",
    "value" : {
      "versionInfo" : [],
      "any" : [],
      "otherAttributes" : {}
    }
  }
  ```
 *
 * That same request in API versions 31.0 and above is represented as:
 * ```
  {
    "versionInfo" : [],
    "any" : [],
    "otherAttributes" : {}
  }
  ```
 * This interceptor should process responses before any other interceptors that rely
 * on consistent API information.
 */
class ResponseNormalizationInterceptor {
    intercept(req, next) {
        return next.handle(req).pipe(map(response => {
            // While this condition seems awfully specific, the alternative option of examining the 'Content-Type'
            // response header for 'version=30.0' proved to be an unreliable condition in at least one case;
            // returning the same JSON payload as API versions >= 31.0.
            if (response instanceof HttpResponse && response.body && response.body.value && response.body.declaredType && response.body.scope) {
                const body = response.body.value;
                if (response.body.declaredType === ResponseNormalizationInterceptor.QUERY_RESULT_TYPE) {
                    body.record = body.record.map(record => record.value);
                }
                return response.clone({ body });
            }
            return response;
        }));
    }
}
ResponseNormalizationInterceptor.QUERY_RESULT_TYPE = 'com.vmware.vcloud.api.rest.schema_v1_5.QueryResultRecordsType';
ResponseNormalizationInterceptor.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: ResponseNormalizationInterceptor, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
ResponseNormalizationInterceptor.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: ResponseNormalizationInterceptor });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: ResponseNormalizationInterceptor, decorators: [{
            type: Injectable
        }] });

/**
 * Angular's HttpInterceptorHandler is not publicly exposed.  This is a clone of it.
 */
class VcdHttpInterceptorHandler {
    constructor(next, interceptor) {
        this.next = next;
        this.interceptor = interceptor;
    }
    handle(req) {
        return this.interceptor.intercept(req, this.next);
    }
}
/**
 * This is a specialist subclass of HttpClient.  The HttpClient that is defined/imported
 * by HttpClientModule is a singleton from the container, meaning that all extensions would
 * get the same one.  We sub-class it so that each extension gets their own instance.
 * Extension consumers should inject this.
 * @see HttpClient
 */
class VcdHttpClient extends HttpClient {
    /**
     * Create an HttpClient with the logging and header interceptors in the chain.
     * @param httpBackend backend (likely injected from HttpClientModule)
     * @param loggingInterceptor the logging interceptor
     * @param requestHeadersInterceptor the request header interceptor
     * @param responseNormalizationInterceptor
     * @param customInterceptors
     */
    constructor(httpBackend, loggingInterceptor, requestHeadersInterceptor, responseNormalizationInterceptor, customInterceptors) {
        const interceptors = [
            loggingInterceptor,
            requestHeadersInterceptor,
            responseNormalizationInterceptor,
            ...(customInterceptors !== null && customInterceptors !== void 0 ? customInterceptors : []),
        ];
        const chain = interceptors.reduceRight((next, interceptor) => new VcdHttpInterceptorHandler(next, interceptor), httpBackend);
        super(chain);
        this.customInterceptors = customInterceptors;
        this.loggingInterceptor = loggingInterceptor;
        this.requestHeadersInterceptor = requestHeadersInterceptor;
    }
}
VcdHttpClient.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: VcdHttpClient, deps: [{ token: i1.HttpBackend }, { token: LoggingInterceptor }, { token: RequestHeadersInterceptor }, { token: ResponseNormalizationInterceptor }, { token: VCD_HTTP_INTERCEPTORS, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
VcdHttpClient.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: VcdHttpClient });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: VcdHttpClient, decorators: [{
            type: Injectable
        }], ctorParameters: function () {
        return [{ type: i1.HttpBackend }, { type: LoggingInterceptor }, { type: RequestHeadersInterceptor }, { type: ResponseNormalizationInterceptor }, { type: undefined, decorators: [{
                        type: Optional
                    }, {
                        type: Inject,
                        args: [VCD_HTTP_INTERCEPTORS]
                    }] }];
    } });

/**
 * Default chunk size is 50MiB.  This is equal to the chunk size the VCD UI uses for library uploads.
 */
const MAX_CHUNK_SIZE = 50 * 1024 * 1024;
/**
 * How many times to retry a chunk upload.
 */
const MAX_CHUNK_RETRY_COUNT = 5;
/**
 * A special error thrown by the transfer client.  It gives access to the causing error, and the final progress
 * before the error occurred.
 */
class TransferError extends Error {
    constructor(message, originalError, lastProgress) {
        super(message);
        this.originalError = originalError;
        this.lastProgress = lastProgress;
    }
}
/**
 * This is used to upload files to a VCD API transfer URL.  It is not suggested to create this class - instead
 * use the startTransfer method in VcdApiClient.
 */
class VcdTransferClient {
    /**
     * Create a transfer client.
     * @param httpClient the http client to be used
     * @param transferUrl the URL to upload to
     */
    constructor(httpClient, transferUrl, maxChunkSize = MAX_CHUNK_SIZE, maxChunkRetryCount = MAX_CHUNK_RETRY_COUNT) {
        this.httpClient = httpClient;
        this.transferUrl = transferUrl;
        this.maxChunkSize = maxChunkSize;
        this.maxChunkRetryCount = maxChunkRetryCount;
    }
    /**
     * Upload data, optionally listening for progress updates.
     * @param source what to upload.
     * @param progressObserver (optional) this will get progress notifications during the upload
     * @returns fetails of the finished upload.
     * @throws TransferError when a chunk upload fails.
     */
    upload(source, progressObserver) {
        // Cache the client and url so they don't change from under us.
        const { httpClient, transferUrl, maxChunkSize, maxChunkRetryCount } = this;
        // Compute static information used through the upload.
        const filename = source.name || '<blob>';
        const totalChunks = Math.ceil(source.size / maxChunkSize);
        const totalBytes = source.size;
        const startTimeMs = new Date().getTime();
        let retryCount = 0;
        // This helper function creates a TransferProgress object for sending to the progressObserver.
        // It relies on the above static information, hence being nested.
        function createTransferProgress(retryNumber, rtryCount, chunksSent) {
            const chunksRemaining = totalChunks - chunksSent;
            const bytesSent = Math.min(chunksSent * maxChunkSize, totalBytes);
            const bytesRemaining = totalBytes - bytesSent;
            const percent = bytesSent / totalBytes * 100;
            const timeTakenMs = new Date().getTime() - startTimeMs;
            const estimatedTotalTimeMs = (bytesSent / bytesRemaining) * timeTakenMs;
            const estimatedTimeRemainingMs = Math.max(estimatedTotalTimeMs - timeTakenMs, 0);
            return {
                filename, transferUrl, retryNumber, retryCount: rtryCount,
                chunksSent, chunksRemaining, bytesSent, bytesRemaining,
                percent, timeTakenMs, estimatedTimeRemainingMs
            };
        }
        // This is the main chunk upload function
        function transferChunk(chunkIndex) {
            // Calculate chunk details.
            const chunkStart = chunkIndex * maxChunkSize;
            const chunkEnd = Math.min(chunkStart + maxChunkSize, totalBytes);
            const contentRangeHeader = `bytes ${chunkStart}-${chunkEnd - 1}/${totalBytes}`;
            // Dispatch progress
            if (progressObserver) {
                const progress = createTransferProgress(0, retryCount, chunkIndex);
                progressObserver.next(progress);
            }
            // Read in the chunk
            return Observable.create(observer => {
                const chunkSlice = source.slice(chunkStart, chunkEnd);
                const fileReader = new FileReader();
                fileReader.onerror = err => {
                    observer.error(err);
                };
                fileReader.onabort = err => {
                    observer.error(err);
                };
                fileReader.onload = () => {
                };
                fileReader.onloadend = () => {
                    observer.next(fileReader.result);
                    observer.complete();
                };
                return fileReader.readAsArrayBuffer(chunkSlice);
                // Transfer the chunk
            }).pipe(
            // Upon read error, abort the upload process.  No point retrying read failures.
            catchError((e) => {
                // Abandon the upload and propagate a consumable error.
                const progress = createTransferProgress(0, retryCount, chunkIndex);
                return throwError(() => new TransferError('Read error', e, progress));
            }), 
            // Upon successful read, transfer the chunk.
            switchMap((data) => {
                let retryNumber = 0;
                return httpClient.put(transferUrl, data, {
                    headers: new HttpHeaders({
                        'Content-Range': contentRangeHeader
                    }),
                    responseType: 'text'
                }).pipe(
                // This is called upon any chunk upload failure.
                catchError((e) => {
                    // Increase the total retry count.
                    retryCount++;
                    // Increase the current chunk retry number.
                    retryNumber++;
                    // Dispatch progress (the retry information has changed).
                    if (progressObserver) {
                        const progress = createTransferProgress(retryNumber, retryCount, chunkIndex);
                        progressObserver.next(progress);
                    }
                    // Rethrow the error so that the "retry" call handles it.
                    return throwError(() => e);
                }), 
                // Retry the chunk upload up to the limit - this will run the entire chain again.
                retry(maxChunkRetryCount), 
                // This is called when all retries for the chunk are exhausted.
                catchError((e) => {
                    // Abandon the upload and propagate a consumable error.
                    const progress = createTransferProgress(retryNumber, retryCount, chunkIndex);
                    return throwError(() => new TransferError('Transfer error', e, progress));
                }));
            }));
        }
        // This creates the final transfer progress, dispatches it, and returns the transfer result.
        function finishUpload() {
            const transferProgress = createTransferProgress(0, retryCount, totalChunks);
            if (progressObserver) {
                progressObserver.next(transferProgress);
                progressObserver.complete();
            }
            const transferResult = {
                filename: transferProgress.filename,
                transferUrl: transferProgress.transferUrl,
                retryCount: transferProgress.retryCount,
                chunksSent: transferProgress.chunksSent,
                bytesSent: transferProgress.bytesSent,
                timeTakenMs: transferProgress.timeTakenMs
            };
            return transferResult;
        }
        // Upload all of the chunks
        let chain = transferChunk(0);
        for (let currentChunk = 1; currentChunk < totalChunks; currentChunk++) {
            chain = chain.pipe(flatMap(() => transferChunk(currentChunk)));
        }
        // Finish transfer
        return chain.pipe(map(finishUpload));
    }
}

class ClientError extends Error {
    constructor(message, type) {
        super(message);
        this.message = message;
        this.type = type;
    }
}
var ClientErrorType;
(function (ClientErrorType) {
    ClientErrorType["HateoasLinkMissing"] = "HateoasLinkMissing";
    ClientErrorType["TaskLinkMissing"] = "TaskLinkMissing";
})(ClientErrorType || (ClientErrorType = {}));

const TRANSFER_LINK_REL = 'upload:default';
const HATEOAS_HEADER = 'Link';
// tslint:disable:variable-name
/**
 * Parse out Link headers using a very lazily implemented pull parser
 * @param header '<url1>;name1="value1",name2="value2",<url2>;name3="value3,value4"'
 * @returns parsed link headers
 */
function parseHeaderHateoasLinks(header) {
    const results = [];
    if (!header) {
        return results;
    }
    const headerFieldMappings = {
        href: 'href',
        model: 'type',
        title: 'id',
        rel: 'rel'
    };
    let tokenIndex = -1;
    function peek(token) {
        return header.indexOf(token, tokenIndex + 1);
    }
    function next(token) {
        const nextIndex = peek(token);
        if (nextIndex === -1) {
            throw new Error(JSON.stringify({ header, token, tokenIndex }));
        }
        tokenIndex = nextIndex;
        return tokenIndex;
    }
    while (peek('<') > -1) {
        try {
            const hrefStart = next('<');
            const hrefEnd = next('>');
            const href = header.substring(hrefStart + 1, hrefEnd);
            const result = { href, type: null, id: null, rel: null, vCloudExtension: [] };
            let comma = peek(',');
            let semicolon = peek(';');
            while ((semicolon > -1 && comma > -1 && semicolon < comma) || (semicolon > -1 && comma === -1)) {
                const nameStart = next(';');
                const nameEnd = next('=');
                const name = header.substring(nameStart + 1, nameEnd).trim().toLowerCase();
                const valueStart = next('"');
                const valueEnd = next('"');
                const value = header.substring(valueStart + 1, valueEnd);
                const mappedName = headerFieldMappings[name];
                if (mappedName) {
                    // @ts-ignore
                    result[mappedName] = decodeURIComponent(value);
                }
                comma = peek(',');
                semicolon = peek(';');
            }
            results.push(result);
        }
        catch (error) { // We will try the next one...
            console.log(error);
        }
    }
    return results;
}
var LinkRelType;
(function (LinkRelType) {
    LinkRelType["add"] = "add";
    LinkRelType["remove"] = "remove";
    LinkRelType["edit"] = "edit";
})(LinkRelType || (LinkRelType = {}));
/**
 * A basic client for interacting with the VMware Cloud Director APIs.
 *
 * A VMware Cloud Director plugin can get a reference to this client by using angular injection.
 * ```
 *     constructor(private vcdApi: VcdApiClient) {}
 * ```
 *
 * VcdApiClient reuses the authentication from the VCD platform so in general there is
 * no need of an explicit authentication/login.
 *
 * When dealing with the session management there are two APIs:
 * 1. Deprecated legacy API that is using the `api/session` endpoint and the corresponding models
 * 2. Newly added API that is using the `cloudapi` endpoint and the corresponding models
 *
 * Note that if a plugin performs an explicit cloud api authentication call through
 * {@link VcdApiClient#setCloudApiAuthentication} or {@link VcdApiClient#cloudApiLogin}
 * from that moment on the VcdApiClient uses only cloud api session management.
 * This means calls to {@link VcdApiClient#setAuthentication} or {@link VcdApiClient#login} have no effect.
 */
class VcdApiClient {
    set baseUrl(_baseUrl) {
        this._baseUrl = _baseUrl;
    }
    get version() {
        return this.http.requestHeadersInterceptor.version;
    }
    constructor(http, injector, config) {
        var _a;
        this.http = http;
        this.injector = injector;
        this.config = config;
        /**
         * @deprecated Use {@link VcdApiClient#_cloudApiSession}
         */
        this._session = new BehaviorSubject(null);
        this._sessionObservable = this._session.asObservable()
            .pipe(skipWhile(session => !session));
        /**
         * CloudApi Session
         */
        this._cloudApiSession = new BehaviorSubject(null);
        this._cloudApiSessionObservable = this._cloudApiSession.asObservable()
            .pipe(skipWhile(session => !session));
        this._cloudApiSessionLinks = new BehaviorSubject([]);
        /**
         * This property determines if it is an explicit cloud api login.
         * In this case the old API (/api/session) should not be used at all.
         */
        this._isCloudApiLogin = false;
        this._baseUrl = this.injector.get(API_ROOT_URL);
        let negotiatedVersion;
        if ((_a = this.config) === null || _a === void 0 ? void 0 : _a.apiVersion) {
            negotiatedVersion = of(this.config.apiVersion).pipe(map((version) => {
                this.setVersion(version);
                return version;
            }));
        }
        else {
            negotiatedVersion = this.http.get(`${this._baseUrl}/api/versions`).pipe(map(versions => this.negotiateVersion(versions)), tap(version => this.setVersion(version)));
        }
        this._negotiateVersion = negotiatedVersion.pipe(share({ connector: () => new ReplaySubject(1), resetOnError: false, resetOnComplete: false, resetOnRefCountZero: false }));
        const tokenHolder = this.injector.get(AuthTokenHolderService, { token: '' });
        const token = tokenHolder.jwt ? `Bearer ${tokenHolder.jwt}` : tokenHolder.token;
        this._getSession = this.setAuthentication(token)
            .pipe(share({ connector: () => new ReplaySubject(1), resetOnError: false, resetOnComplete: false, resetOnRefCountZero: false }));
        this._getCloudApiSession = this.setCloudApiAuthentication(token)
            .pipe(share({ connector: () => new ReplaySubject(1), resetOnError: false, resetOnComplete: false, resetOnRefCountZero: false }));
        // This is not an explicit cloud api login
        this._isCloudApiLogin = false;
    }
    negotiateVersion(serverVersions) {
        const supportedVersions = serverVersions.versionInfo.map(versionInfo => versionInfo.version);
        // Default API Version used is the Latest API Version in VMware Cloud Director
        return supportedVersions[supportedVersions.length - 1];
    }
    /**
     * The purpose of this function is to ensure that prior to sending any call to the backend
     * the version has been set and the current session has been retrieved.
     * Note that this is important during the automatic authentication that is done during the
     * constructor initialization, when the plugin is not required to perform its own explicit authentication
     * but rather the ones from the underlying framework is used.
     */
    validateRequestContext() {
        return (this.version ? of(this.version) : this._negotiateVersion)
            .pipe(
        // In case of a cloud api login we are not interested in the /api/session session
        concatMap(() => this._isCloudApiLogin ? of(null) : this._getSession), concatMap(() => this._getCloudApiSession
            // In case of cloud api failure we do not want to prevent further execution
            // for backward compatibility considerations since this may be a case
            // when cloud api is not supported at all for the specified version
            .pipe(catchError((e) => of(true)))), map(() => true));
    }
    /**
     *
     * For use cases wich solely depends on cloudapi without any backward compatibility
     * there should be no dependence on the old /api endpoint at all
     */
    validateRequestContextCloudApiOnly() {
        return (this.version ? of(this.version) : this._negotiateVersion).pipe(concatMap(() => this._getCloudApiSession));
    }
    setVersion(_version) {
        this.http.requestHeadersInterceptor.version = _version;
        return this;
    }
    /**
     * Global configuration for the service, that allows a provider user to execute API requests
     * in the scope of a specific tenant.
     *
     * If you want to execute single API request in scope of specific tenant you can do it
     * by passing "X-VMWARE-VCLOUD-TENANT-CONTEXT" header to the specific API Request.
     *
     * This scoping is available to query-based API calls and to bulk GET calls in the
     * /cloudapi space.
     *
     * @param actAs an entityRef of the tenant (organization) to scope subsequent calls to in
     *  the VcdApiClient, or null/no parameter to remove tenant-specific scoping
     * @returns the current VcdApiClient instance (for chaining)
     */
    actAs(actAs = null) {
        this.http.requestHeadersInterceptor.actAs = !actAs ? null : actAs.id;
        return this;
    }
    /**
     * @deprecated Use {@link VcdApiClient#setCloudApiAuthentication}
     *
     * Sets the authentication token to use for the VcdApiClient.
     *
     * After setting the token, the client will get the current session
     * information associated with the authenticated token.
     *
     * @param authentication the authentication string (to be used in either the 'Authorization'
     *  or 'x-vcloud-authorization' header)
     * @returns the session associated with the authentication token
     */
    setAuthentication(authentication) {
        if (this._isCloudApiLogin) {
            return throwError('Only cloud api auth is allowed since it was already used');
        }
        this.http.requestHeadersInterceptor.authentication = authentication;
        return this.http.get(`${this._baseUrl}/api/session`).pipe(tap(session => {
            // automatically set actAs for provider in tenant scope
            if (session.org === 'System' && this.injector.get(SESSION_SCOPE) === 'tenant') {
                // Automatic actAs only works in versions >=9.5
                try {
                    this.actAs({ id: this.injector.get(SESSION_ORG_ID) });
                }
                catch (e) {
                    console.warn('No SESSION_ORG_ID set in container. Automatic actAs is disabled.');
                }
            }
        }), tap(session => this._session.next(session)));
    }
    /**
     * Sets the authentication token to use for the VcdApiClient.
     *
     * After setting the token, the client will get the current session
     * information associated with the authenticated token.
     *
     * @param authentication the authentication string (to be used in either the 'Authorization'
     *  or 'x-vcloud-authorization' header)
     *
     * @returns session observable associated with the authentication token
     */
    setCloudApiAuthentication(authentication) {
        this.onBeforeCloudApiAuthentication();
        return of(true)
            .pipe(
        // Set the authentication as part of the observable in order to ensure the caller has subscribed to the observable
        tap(() => this.http.requestHeadersInterceptor.authentication = authentication), switchMap(() => this.http.get(`${this._baseUrl}/cloudapi/1.0.0/sessions/current`, { observe: 'response' })))
            .pipe(this.onCloudApiAuthentication());
    }
    enableLogging() {
        this.http.loggingInterceptor.enabled = true;
        return this;
    }
    /**
     * @deprecated Use {@link VcdApiClient#cloudApiLogin}
     *
     * Creates an authenticated session for the specified credential data.
     *
     * @param username the name of the user to authenticate
     * @param tenant the organization the user belongs to
     * @param password the password for the user
     * @returns an authenticated session for the given credentials
     */
    login(username, tenant, password) {
        if (this._isCloudApiLogin) {
            return throwError('Only cloud api auth is allowed since it was already used');
        }
        const authString = btoa(`${username}@${tenant}:${password}`);
        return this.http.post(`${this._baseUrl}/api/sessions`, null, {
            observe: 'response',
            headers: new HttpHeaders({ Authorization: `Basic ${authString}` })
        })
            .pipe(tap((response) => 
        // tslint:disable-next-line:max-line-length
        this.http.requestHeadersInterceptor.authentication = `${response.headers.get('x-vmware-vcloud-token-type')} ${response.headers.get('x-vmware-vcloud-access-token')}`), map(response => response.body), tap(session => this._session.next(session)));
    }
    /**
     * Creates an authenticated session for the specified credential data using cloud api endpoint.
     *
     * @param username the name of the user to authenticate
     * @param tenant the organization the user belongs to
     * @param password the password for the user
     * @returns an authenticated session for the given credentials
     */
    cloudApiLogin(username, tenant, password) {
        this.onBeforeCloudApiAuthentication();
        const authString = btoa(`${username}@${tenant}:${password}`);
        let url = `${this._baseUrl}/cloudapi/1.0.0/sessions`;
        if (tenant.toLowerCase() === 'system') {
            url += '/provider';
        }
        return this.http.post(url, null, {
            observe: 'response',
            headers: new HttpHeaders({ [HTTP_HEADERS.Authorization]: `Basic ${authString}` })
        }).pipe(tap((response) => {
            // tslint:disable-next-line:max-line-length
            const token = `${response.headers.get('x-vmware-vcloud-token-type')} ${response.headers.get('x-vmware-vcloud-access-token')}`;
            this.http.requestHeadersInterceptor.authentication = token;
        }), this.onCloudApiAuthentication());
    }
    /**
     * It is necessary to know if an explicit cloud api auth request was done.
     * This function handles this by setting the corresponding flags, properties etc.
     */
    onBeforeCloudApiAuthentication() {
        // In case of an explicit cloud api auth request:
        // Set the flag _isCloudApiLogin in order to know that explicit cloud api authentication is done
        // This will help us skip code related to the old api, i.e. we should not allow explicit mix of both the api-s
        this._isCloudApiLogin = true;
        // In case of an explicit cloud api auth request:
        // There is no need of _getCloudApiSession observable which is needed in the automatic login during the constructor initialization.
        // The explicit cloud api auth request will retrieve the session.
        this._getCloudApiSession = of(null);
    }
    /**
     * Handle authentication.
     * This includes getting HATEOAS links, setting the session, handling errors etc.
     */
    onCloudApiAuthentication() {
        return (source) => source.pipe(tap((resp) => {
            // Get HATEOAS links
            try {
                this.setCloudApiSessionLinks(parseHeaderHateoasLinks(resp.headers.get(HATEOAS_HEADER)));
            }
            catch (e) {
                console.log('Error when parsing session HATEOAS links:', e);
            }
        }), map(resp => resp.body), tap((session) => {
            // Clear previous actAs
            this.actAs(null);
            // automatically set actAs for provider in tenant scope
            if (session.org && session.org.name === 'System' && this.injector.get(SESSION_SCOPE) === 'tenant') {
                // Automatic actAs only works in versions >=9.5
                try {
                    this.actAs({ id: this.injector.get(SESSION_ORG_ID) });
                }
                catch (e) {
                    console.warn('No SESSION_ORG_ID set in container. Automatic actAs is disabled.');
                }
            }
        }), tap((session) => this._cloudApiSession.next(session)), catchError((e) => {
            this.onCloudApiAuthenticationError();
            return throwError(e);
        }));
    }
    onCloudApiAuthenticationError() {
        // Clear the authentication so that any subsequent backend calls are not authenticated
        this.http.requestHeadersInterceptor.authentication = '';
        // _getCloudApiSession is in the center of any backend call, nullify it in order not to prevent those calls
        // since it is easier to troubleshoot failing backend rather than no call
        this._getCloudApiSession = of(null);
        // Clear the links
        this._cloudApiSessionLinks.next([]);
        // Clear the session
        this._cloudApiSession.next(null);
    }
    setCloudApiSessionLinks(links) {
        this._cloudApiAccessibleLocations = null;
        this._cloudApiSessionLinks.next(links || []);
    }
    get(endpoint, options) {
        return this.validateRequestContext().pipe(concatMap(() => this.http.get(this.buildEndpointUrl(endpoint), Object.assign({}, options))));
    }
    list(endpoint, queryBuilder, multisite, options) {
        let url = this.buildEndpointUrl(endpoint);
        if (queryBuilder) {
            url = `${url}${queryBuilder.getCloudAPI()}`;
        }
        if (multisite) {
            if (!options) {
                return this.http.get(url, { headers: new HttpHeaders({ _multisite: this.parseMultisiteValue(multisite) }) });
            }
            else if (options === null || options === void 0 ? void 0 : options.headers) {
                options.headers.append("_multisite", this.parseMultisiteValue(multisite));
                return this.http.get(url, Object.assign({}, options));
            }
        }
        return this.validateRequestContext().pipe(concatMap(() => this.http.get(url, Object.assign({}, options))));
    }
    createSync(endpoint, item, options) {
        return this.validateRequestContext().pipe(concatMap(() => this.http.post(this.buildEndpointUrl(endpoint), item, Object.assign({}, options))));
    }
    createAsync(endpoint, item, options) {
        return this.validateRequestContext().pipe(concatMap(() => this.http.post(this.buildEndpointUrl(endpoint), item, Object.assign(Object.assign({}, options), { observe: 'response' }))), concatMap(response => this.mapResponseToTask(response, 'POST')));
    }
    getTransferLink(endpoint, item, transferRel = TRANSFER_LINK_REL) {
        return this.http
            .post(this.buildEndpointUrl(endpoint), item, { observe: 'response' })
            .pipe(map((res) => {
            const headerLinks = res.headers.has(HATEOAS_HEADER)
                ? parseHeaderHateoasLinks(res.headers.get(HATEOAS_HEADER))
                : [];
            const links = res.body ? (res.body.link || []) : [];
            const link = [...headerLinks, ...links]
                .find((l) => l.rel === transferRel);
            if (!link) {
                throw new ClientError(`Response from ${endpoint} did not contain a transfer link`, ClientErrorType.HateoasLinkMissing);
            }
            return link.href;
        }));
    }
    startTransfer(endpoint, item, transferRel = TRANSFER_LINK_REL) {
        return this.getTransferLink(endpoint, item, transferRel)
            .pipe(map((transferUrl) => new VcdTransferClient(this.http, transferUrl)));
    }
    updateSync(endpoint, item, options) {
        return this.validateRequestContext().pipe(concatMap(() => this.http.put(this.buildEndpointUrl(endpoint), item, Object.assign({}, options))));
    }
    updateAsync(endpoint, item, options) {
        return this.validateRequestContext().pipe(concatMap(() => this.http.put(this.buildEndpointUrl(endpoint), item, Object.assign(Object.assign({}, options), { observe: 'response' }))), concatMap(response => this.mapResponseToTask(response, 'PUT')));
    }
    deleteSync(endpoint, options) {
        return this.validateRequestContext().pipe(concatMap(() => this.http.delete(this.buildEndpointUrl(endpoint), Object.assign({}, options))));
    }
    deleteAsync(endpoint, options) {
        return this.validateRequestContext().pipe(concatMap(() => this.http.delete(this.buildEndpointUrl(endpoint), Object.assign(Object.assign({}, options), { observe: 'response' }))), concatMap(response => this.mapResponseToTask(response, 'DELETE')));
    }
    mapResponseToTask(response, httpVerb) {
        if (response.headers.has('Location') && response.status === 202) {
            return this.http.get(response.headers.get('Location'));
        }
        else if (response.body && response.body.type.startsWith('application/vnd.vmware.vcloud.task+')) {
            const task = Object.assign(new TaskType(), response.body);
            return of(task);
        }
        return throwError(() => new ClientError(`An asynchronous request was made to [${httpVerb} ${response.url}], but no task was returned.  The operation may still have been successful.`, ClientErrorType.TaskLinkMissing));
    }
    getEntity(entityRefOrUrn) {
        const entityResolver = typeof entityRefOrUrn === 'string' ?
            this.http.get(`${this._baseUrl}/api/entity/${entityRefOrUrn}`) :
            this.http.get(`${this._baseUrl}/api/entity/urn:vcloud:${entityRefOrUrn.type}:${entityRefOrUrn.id}`);
        return this.validateRequestContext().pipe(concatMap(() => entityResolver), concatMap(entity => this.http.get(`${entity.link[0].href}`)));
    }
    updateTask(task, options) {
        return this.validateRequestContext().pipe(concatMap(() => this.http.get(task.href, Object.assign({}, options))));
    }
    isTaskComplete(task) {
        return ['success', 'error', 'canceled', 'aborted'].indexOf(task.status) > -1;
    }
    removeItem(item, options) {
        const link = this.findLink(item, 'remove', null);
        if (!link) {
            return throwError(() => new ClientError(`No 'remove' link for specified resource.`, ClientErrorType.HateoasLinkMissing));
        }
        return this.validateRequestContext().pipe(concatMap(() => this.http.delete(link.href, Object.assign({}, options))));
    }
    query(builder, multisite, options) {
        return this.getQueryPage(`${this._baseUrl}/api/query${builder.get()}`, multisite, options);
    }
    firstPage(result, multisite, options) {
        const link = this.findLink(result, 'firstPage', result.type);
        if (!link) {
            return throwError(() => new ClientError(`No 'firstPage' link for specified query.`, ClientErrorType.HateoasLinkMissing));
        }
        return this.getQueryPage(link.href, multisite, options);
    }
    hasFirstPage(result) {
        return !!this.findLink(result, 'firstPage', result.type);
    }
    previousPage(result, multisite, options) {
        const link = this.findLink(result, 'previousPage', result.type);
        if (!link) {
            return throwError(() => new ClientError(`No 'previousPage' link for specified query.`, ClientErrorType.HateoasLinkMissing));
        }
        return this.getQueryPage(link.href, multisite, options);
    }
    hasPreviousPage(result) {
        return !!this.findLink(result, 'previousPage', result.type);
    }
    nextPage(result, multisite, options) {
        const link = this.findLink(result, 'nextPage', result.type);
        if (!link) {
            return throwError(() => new ClientError(`No 'nextPage' link for specified query.`, ClientErrorType.HateoasLinkMissing));
        }
        return this.getQueryPage(link.href, multisite, options);
    }
    hasNextPage(result) {
        return !!this.findLink(result, 'nextPage', result.type);
    }
    lastPage(result, multisite, options) {
        const link = this.findLink(result, 'lastPage', result.type);
        if (!link) {
            return throwError(() => new ClientError(`No 'lastPage' link for specified query.`, ClientErrorType.HateoasLinkMissing));
        }
        return this.getQueryPage(link.href, multisite, options);
    }
    hasLastPage(result) {
        return !!this.findLink(result, 'lastPage', result.type);
    }
    getQueryPage(href, multisite, options) {
        if (multisite) {
            if (!options) {
                return this.http.get(href, { headers: new HttpHeaders({ _multisite: this.parseMultisiteValue(multisite) }) });
            }
            else if (options === null || options === void 0 ? void 0 : options.headers) {
                options.headers.append("_multisite", this.parseMultisiteValue(multisite));
                return this.http.get(href, Object.assign({}, options));
            }
        }
        return this.validateRequestContext().pipe(concatMap(() => this.http.get(href, Object.assign({}, options))));
    }
    /**
     * Use to perform action availability check before calling the API
     * @param item - the navigable item (containing link collection)
     * @param linkRelType - the link rel type, pass either LinkRelType or string
     * @param entityRefType - the entity reference type
     */
    canPerformAction(item, linkRelType, entityRefType) {
        return !!this.findLink(item, linkRelType, entityRefType);
    }
    findLink(item, rel, type) {
        if (!item || !item.link) {
            return undefined;
        }
        return item.link.find((link) => {
            if (type) {
                return link.rel.includes(rel) && link.type === type;
            }
            return link.rel.includes(rel);
        });
    }
    parseMultisiteValue(multisite) {
        return typeof multisite === 'boolean' ? (multisite ? 'global' : 'local') : multisite.map(site => site.locationId).join(',');
    }
    /**
     * @deprecated Use cloudApiSession
     */
    get session() {
        return this.validateRequestContext().pipe(concatMap(() => this._sessionObservable));
    }
    /**
     * Get Session observable
     */
    get cloudApiSession() {
        return this.validateRequestContextCloudApiOnly().pipe(concatMap(() => this._cloudApiSessionObservable));
    }
    get username() {
        return merge(this.cloudApiSession.pipe(filter(() => this._isCloudApiLogin), map(session => session && session.user && session.user.name)), this.session.pipe(filter(() => !this._isCloudApiLogin), map(session => session && session.user)));
    }
    get organization() {
        return merge(this.cloudApiSession.pipe(filter(() => this._isCloudApiLogin), map(session => session && session.org && session.org.name)), this.session.pipe(filter(() => !this._isCloudApiLogin), map(session => session && session.org)));
    }
    /**
     * @deprecated Use cloudApiLocation
     */
    get location() {
        return this.session.pipe(map(session => session.authorizedLocations.location.find(location => location.locationId === session.locationId)));
    }
    /**
     * Gets the location corresponding to the current session
     */
    get cloudApiLocation() {
        return this.cloudApiSession.pipe(switchMap(() => {
            if (!this._cloudApiAccessibleLocations) {
                // Ensure caching for getting AccessibleLocations
                this._cloudApiAccessibleLocations = this._cloudApiSessionLinks
                    .pipe(
                // Get the AccessibleLocations link
                map((links) => this.findLink({ link: links }, 'down', 'AccessibleLocations')), 
                // Fetch AccessibleLocations from the backend
                switchMap((link) => link ? this.http.get(link.href) : of(null)), 
                // Get the array with all locations (what if there are many pages)
                map((accessibleLocations) => accessibleLocations && accessibleLocations.values))
                    .pipe(share({ connector: () => new ReplaySubject(1), resetOnError: false, resetOnComplete: false, resetOnRefCountZero: false }));
            }
            return this._cloudApiAccessibleLocations;
        }), 
        // Need to have the session in order to get its location
        withLatestFrom(this.cloudApiSession), 
        // Find the location that corresponds to this session
        map(([accessibleLocations, session]) => {
            if (!accessibleLocations || !session) {
                return null;
            }
            const sessionLocation = session.location;
            if (!sessionLocation) {
                return null;
            }
            return accessibleLocations.find(location => location.locationId === sessionLocation);
        }));
    }
    getLocation(session) {
        return session.authorizedLocations.location.find(location => location.locationId === session.locationId);
    }
    /**
     * Build the endpoint url. If the provided endpoint is already an absolute URL, then return it as it is without
     * any modifications, otherwise consider it as a relative one and prepend the baseUrl as defined by the host application.
     */
    buildEndpointUrl(endpoint) {
        return endpoint.indexOf('://') > -1 ? endpoint : `${this._baseUrl}/${endpoint}`;
    }
}
VcdApiClient.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: VcdApiClient, deps: [{ token: VcdHttpClient }, { token: i0.Injector }, { token: VcdSdkConfig, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
VcdApiClient.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: VcdApiClient });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: VcdApiClient, decorators: [{
            type: Injectable
        }], ctorParameters: function () {
        return [{ type: VcdHttpClient }, { type: i0.Injector }, { type: VcdSdkConfig, decorators: [{
                        type: Optional
                    }] }];
    } });

/**
 * Extensions should import this module.
 * They can then wire in SDK components as desired.
 */
class VcdSdkModule {
    static forRoot(config) {
        return {
            ngModule: VcdSdkModule,
            providers: [
                RequestHeadersInterceptor,
                LoggingInterceptor,
                ResponseNormalizationInterceptor,
                VcdHttpClient,
                VcdApiClient,
                {
                    provide: VcdSdkConfig,
                    useValue: config || {}
                },
            ]
        };
    }
}
VcdSdkModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: VcdSdkModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
VcdSdkModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.0", ngImport: i0, type: VcdSdkModule, imports: [HttpClientModule,
        CommonModule] });
VcdSdkModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: VcdSdkModule, imports: [HttpClientModule,
        CommonModule] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.0", ngImport: i0, type: VcdSdkModule, decorators: [{
            type: NgModule,
            args: [{
                    imports: [
                        HttpClientModule,
                        CommonModule,
                    ],
                    declarations: [],
                    exports: [],
                }]
        }] });

/*
 * Public API Surface of sdk
 */

/**
 * Generated bundle index. Do not edit.
 */

export { API_ROOT_URL, ApiResult, ApiResultService, AuthTokenHolderService, ClientError, ClientErrorType, EXTENSION_ASSET_URL, EXTENSION_ROUTE, EntityActionExtensionComponent, ExtensionNavRegistrationAction, FLEX_APP_URL, Filter, HATEOAS_HEADER, LinkRelType, LoggingInterceptor, MAX_CHUNK_RETRY_COUNT, MAX_CHUNK_SIZE, PluginModule, Query, RequestHeadersInterceptor, ResponseNormalizationInterceptor, SDK_MODE, SESSION_ORGANIZATION, SESSION_ORG_ID, SESSION_SCOPE, TRANSFER_LINK_REL, TransferError, VCD_HTTP_INTERCEPTORS, VcdApiClient, VcdHttpClient, VcdSdkConfig, VcdSdkModule, VcdTransferClient, WizardExtensionComponent, WizardExtensionWithContextComponent, WizardExtensionWithValidationComponent, _EntityActionExtensionComponent, _WizardExtensionComponent, _WizardExtensionWithContextComponent, _WizardExtensionWithValidationComponent, parseHeaderHateoasLinks };
//# sourceMappingURL=vcd-sdk.mjs.map
