import * as i0 from '@angular/core';
import { InjectionToken, Injectable, Inject, inject, NgModule } from '@angular/core';
import * as i1 from '@angular/common/http';
import { HttpClient } from '@angular/common/http';
import { getHttpParams, getHttpHeader, makeUrlSegments, buildUrl } from '@angular-ru/cdk/http/utils';
import { isTrue, isNil, isNotNil } from '@angular-ru/cdk/utils';
import { Subject, throwError } from 'rxjs';
import { switchMap, finalize, tap, catchError, take } from 'rxjs/operators';
import { RequestType } from '@angular-ru/cdk/http/typings';
import { replaceWithNull } from '@angular-ru/cdk/object';

const DATA_REQUEST_OPTIONS_CONFIG = {
    baseUrl: null,
    hostUrl: null,
    body: null,
    headers: {},
    queryParams: {},
    pathVariables: {},
    emitSuccess: false,
    emitFailure: true,
    responseType: 'json',
    reportProgress: true,
    additionalOptions: {},
    nullInsteadEmpty: true,
    limitConcurrency: 255
};

const DATA_CONFIG_SERVICE_TOKEN = new InjectionToken('DATA_CONFIG_SERVICE_TOKEN');

class DataConfiguratorService {
    constructor(config) {
        this.config = config;
    }
    mergeGlobalOptionsWith(local, options) {
        return Object.assign(Object.assign(Object.assign({}, this.config), local), options);
    }
}
/** @nocollapse */ /** @nocollapse */ DataConfiguratorService.ɵfac = function DataConfiguratorService_Factory(t) { return new (t || DataConfiguratorService)(i0.ɵɵinject(DATA_CONFIG_SERVICE_TOKEN)); };
/** @nocollapse */ /** @nocollapse */ DataConfiguratorService.ɵprov = /** @pureOrBreakMyCode */ i0.ɵɵdefineInjectable({ token: DataConfiguratorService, factory: DataConfiguratorService.ɵfac });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(DataConfiguratorService, [{
        type: Injectable
    }], function () { return [{ type: undefined, decorators: [{
                type: Inject,
                args: [DATA_CONFIG_SERVICE_TOKEN]
            }] }]; }, null); })();

const DATA_HTTP_CLIENT_INTERCEPTOR = new InjectionToken('DATA_HTTP_CLIENT_INTERCEPTOR');

class RestTemplate {
    constructor(options = {}) {
        this.options = options;
        this.markAsRequest = false;
        this._client = null;
        this.operators = [];
    }
    setPath(path) {
        this.path = path;
        return this;
    }
    setMethodType(type) {
        this.methodType = type;
        return this;
    }
    setClient(client) {
        this._client = client;
        return this;
    }
    setEmitOptions(options) {
        var _a, _b;
        if (isTrue(options.override)) {
            this.options.emitSuccess = options.emitSuccess;
            this.options.emitFailure = options.emitFailure;
        }
        else {
            this.options.emitSuccess = (_a = this.options.emitSuccess) !== null && _a !== void 0 ? _a : options.emitSuccess;
            this.options.emitFailure = (_b = this.options.emitFailure) !== null && _b !== void 0 ? _b : options.emitFailure;
        }
        return this;
    }
    setBody(body) {
        this.options.body = body;
        return this;
    }
    setParams(queryParams) {
        this.options.queryParams = queryParams;
        return this;
    }
    setOptions(options) {
        this.options = Object.assign(Object.assign({}, this.options), options);
        return this;
    }
    asProxyObservable() {
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        const that = this;
        const fakeProxy = {
            restTemplateRef: that,
            pipe(...operators) {
                that.operators.push(...operators);
                return fakeProxy;
            },
            subscribe: () => {
                if (!that.markAsRequest) {
                    throw new Error('You cannot invoke observable outside data request context. \n' +
                        'Use only @Get, @Post, @Put, @Delete decorators for correct call http method...');
                }
            }
        };
        return fakeProxy;
    }
    asObservable() {
        var _a;
        this.markAsRequest = true;
        if (isNil(this._client)) {
            throw new Error('Not found http client');
        }
        else if (isNil((_a = this._client) === null || _a === void 0 ? void 0 : _a[this.methodType])) {
            throw new Error(`Method ${this.methodType} not supported`);
        }
        const options = this._client.createRequestOptions({
            path: this.path,
            method: this.methodType,
            options: this.options
        });
        let stream$ = this._client.request(options);
        if (this.operators.length) {
            this.operators.forEach((operator) => {
                stream$ = stream$.pipe(operator);
            });
        }
        return stream$;
    }
}

class LimitConcurrencyService {
    constructor() {
        this.activeRequestCount = 0;
        this.requestQueue = [];
    }
    add(request$, limitConcurrency) {
        if (limitConcurrency === Infinity) {
            return request$;
        }
        this.validate(limitConcurrency);
        if (this.activeRequestCount < limitConcurrency) {
            this.activeRequestCount++;
            return this.onComplete(request$);
        }
        const requestInLine$ = new Subject();
        this.requestQueue.push(requestInLine$);
        return requestInLine$.pipe(switchMap(() => this.onComplete(request$)));
    }
    onComplete(request$) {
        return request$.pipe(finalize(() => {
            this.activeRequestCount--;
            this.executeFromQueue();
        }));
    }
    executeFromQueue() {
        const requestInLine$ = this.requestQueue.shift();
        if (isNotNil(requestInLine$)) {
            this.activeRequestCount++;
            requestInLine$.next(true);
            requestInLine$.complete();
        }
    }
    validate(limitConcurrency) {
        if (limitConcurrency <= 0) {
            throw new Error('Limit concurrency should be more than 0');
        }
    }
}
/** @nocollapse */ /** @nocollapse */ LimitConcurrencyService.ɵfac = function LimitConcurrencyService_Factory(t) { return new (t || LimitConcurrencyService)(); };
/** @nocollapse */ /** @nocollapse */ LimitConcurrencyService.ɵprov = /** @pureOrBreakMyCode */ i0.ɵɵdefineInjectable({ token: LimitConcurrencyService, factory: LimitConcurrencyService.ɵfac });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(LimitConcurrencyService, [{
        type: Injectable
    }], null, null); })();

class AbstractHttpClient {
    constructor(http, configurator, limitConcurrency, _interceptor) {
        this.http = http;
        this.configurator = configurator;
        this.limitConcurrency = limitConcurrency;
        this.interceptor = _interceptor;
    }
    get(path, options = {}) {
        return this.request(this.createRequestOptions({ method: RequestType.GET, path, options }));
    }
    post(path, options = {}) {
        return this.request(this.createRequestOptions({ method: RequestType.POST, path, options }));
    }
    put(path, options = {}) {
        return this.request(this.createRequestOptions({ method: RequestType.PUT, path, options }));
    }
    patch(path, options = {}) {
        return this.request(this.createRequestOptions({ method: RequestType.PATCH, path, options }));
    }
    delete(path, options = {}) {
        return this.request(this.createRequestOptions({ method: RequestType.DELETE, path, options }));
    }
    createRequestOptions({ method, path, options }) {
        return { path, method, clientOptions: this.configurator.mergeGlobalOptionsWith(this.local, options !== null && options !== void 0 ? options : {}) };
    }
    createDataHttpRequestOptions(options) {
        var _a, _b, _c, _d, _e, _f, _g, _h;
        const httpParams = getHttpParams(options.path, options.clientOptions.queryParams);
        const headers = getHttpHeader(options.clientOptions.headers);
        return {
            withCredentials: false,
            body: this.createHttpBody(options),
            responseType: (_a = options.clientOptions.responseType) !== null && _a !== void 0 ? _a : 'json',
            reportProgress: (_b = options.clientOptions.reportProgress) !== null && _b !== void 0 ? _b : false,
            headers: (_e = (_d = (_c = this.interceptor).onInterceptHttpHeaders) === null || _d === void 0 ? void 0 : _d.call(_c, options, headers)) !== null && _e !== void 0 ? _e : headers,
            params: (_h = (_g = (_f = this.interceptor).onInterceptHttpParams) === null || _g === void 0 ? void 0 : _g.call(_f, options, httpParams)) !== null && _h !== void 0 ? _h : httpParams
        };
    }
    createHttpBody(options) {
        var _a, _b, _c;
        const payload = options.clientOptions.body;
        if (payload instanceof FormData) {
            return payload;
        }
        const body = (_c = (_b = (_a = this.interceptor).onInterceptBodyPayload) === null || _b === void 0 ? void 0 : _b.call(_a, options, payload)) !== null && _c !== void 0 ? _c : payload;
        return isTrue(options.clientOptions.nullInsteadEmpty) ? replaceWithNull(body) : body;
    }
}
/** @nocollapse */ /** @nocollapse */ AbstractHttpClient.ɵfac = function AbstractHttpClient_Factory(t) { return new (t || AbstractHttpClient)(i0.ɵɵinject(i1.HttpClient), i0.ɵɵinject(DataConfiguratorService), i0.ɵɵinject(LimitConcurrencyService), i0.ɵɵinject(DATA_HTTP_CLIENT_INTERCEPTOR)); };
/** @nocollapse */ /** @nocollapse */ AbstractHttpClient.ɵprov = /** @pureOrBreakMyCode */ i0.ɵɵdefineInjectable({ token: AbstractHttpClient, factory: AbstractHttpClient.ɵfac });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AbstractHttpClient, [{
        type: Injectable
    }], function () { return [{ type: i1.HttpClient }, { type: DataConfiguratorService }, { type: LimitConcurrencyService }, { type: undefined, decorators: [{
                type: Inject,
                args: [DATA_HTTP_CLIENT_INTERCEPTOR]
            }] }]; }, null); })();

class DataHttpClient extends AbstractHttpClient {
    constructor() {
        super(inject(HttpClient), inject(DataConfiguratorService), inject(LimitConcurrencyService), inject(DATA_HTTP_CLIENT_INTERCEPTOR));
    }
    request(options) {
        var _a, _b;
        if (isNil(this.local)) {
            throw new Error(`You must use the @RestClient('controller') decorator for work correctly`);
        }
        (_b = (_a = this.interceptor).onBeforeRequest) === null || _b === void 0 ? void 0 : _b.call(_a, options);
        const meta = this.createMetaDataRequest(options);
        const observable$ = this.http.request(options.method, meta.url, meta.requestOptions);
        const queueRequest$ = this.limitConcurrency.add(observable$, options.clientOptions.limitConcurrency);
        return this.wrapHttpRequestWithMeta(meta, options, queueRequest$);
    }
    restTemplate(options) {
        return new RestTemplate(options).asProxyObservable();
    }
    createMetaDataRequest(options) {
        const { emitSuccess, emitFailure } = options.clientOptions;
        const segments = makeUrlSegments(options.clientOptions, this.controllerUrl, options.path);
        const requestOptions = this.createDataHttpRequestOptions(options);
        const url = buildUrl(segments);
        return { method: options.method, url, emitSuccess, emitFailure, requestOptions, segments };
    }
    wrapHttpRequestWithMeta(meta, options, observable$) {
        return observable$.pipe(tap({
            next: (response) => this.onSuccess(response, meta, options),
            error: (error) => this.onError(error, meta, options)
        }), catchError((error) => this.onCatch(error, meta)), finalize(() => { var _a, _b; return (_b = (_a = this.interceptor).onFinalizeAfterRequest) === null || _b === void 0 ? void 0 : _b.call(_a, meta); }), take(1));
    }
    onSuccess(response, meta, options) {
        var _a, _b, _c, _d, _e;
        if (isTrue(options.clientOptions.emitSuccess)) {
            (_a = this.interceptor.success$) === null || _a === void 0 ? void 0 : _a.next({ options, meta });
            (_c = (_b = this.interceptor).onEmitSuccess) === null || _c === void 0 ? void 0 : _c.call(_b, response, options, meta);
        }
        (_e = (_d = this.interceptor).onTapAfterRequest) === null || _e === void 0 ? void 0 : _e.call(_d, response, meta);
    }
    onError(error, meta, options) {
        var _a, _b, _c, _d, _e;
        if (isTrue(options.clientOptions.emitFailure)) {
            (_a = this.interceptor.errors$) === null || _a === void 0 ? void 0 : _a.next({ error, options, meta });
            (_c = (_b = this.interceptor).onEmitFailure) === null || _c === void 0 ? void 0 : _c.call(_b, error, options, meta);
        }
        (_e = (_d = this.interceptor).onErrorAfterRequest) === null || _e === void 0 ? void 0 : _e.call(_d, error, meta);
    }
    onCatch(error, meta) {
        var _a, _b, _c;
        return (_c = (_b = (_a = this.interceptor).onCatchErrorAfterRequest) === null || _b === void 0 ? void 0 : _b.call(_a, error, meta)) !== null && _c !== void 0 ? _c : throwError(() => error);
    }
}
/** @nocollapse */ /** @nocollapse */ DataHttpClient.ɵfac = function DataHttpClient_Factory(t) { return new (t || DataHttpClient)(); };
/** @nocollapse */ /** @nocollapse */ DataHttpClient.ɵprov = /** @pureOrBreakMyCode */ i0.ɵɵdefineInjectable({ token: DataHttpClient, factory: DataHttpClient.ɵfac });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(DataHttpClient, [{
        type: Injectable
    }], function () { return []; }, null); })();

class DefaultHttpClientInterceptor {
    constructor() {
        this.success$ = new Subject();
        this.errors$ = new Subject();
    }
}
/** @nocollapse */ /** @nocollapse */ DefaultHttpClientInterceptor.ɵfac = function DefaultHttpClientInterceptor_Factory(t) { return new (t || DefaultHttpClientInterceptor)(); };
/** @nocollapse */ /** @nocollapse */ DefaultHttpClientInterceptor.ɵprov = /** @pureOrBreakMyCode */ i0.ɵɵdefineInjectable({ token: DefaultHttpClientInterceptor, factory: DefaultHttpClientInterceptor.ɵfac });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(DefaultHttpClientInterceptor, [{
        type: Injectable
    }], null, null); })();

class DataHttpClientModule {
    static forRoot(clients = [], options = {}) {
        return {
            ngModule: DataHttpClientModule,
            providers: [
                DataConfiguratorService,
                { provide: DATA_CONFIG_SERVICE_TOKEN, useValue: Object.assign(Object.assign({}, DATA_REQUEST_OPTIONS_CONFIG), options) },
                { provide: DATA_HTTP_CLIENT_INTERCEPTOR, useClass: DefaultHttpClientInterceptor },
                DataHttpClient,
                LimitConcurrencyService,
                ...clients
            ]
        };
    }
    static forFeature(clients = []) {
        return { ngModule: DataHttpClientModule, providers: clients };
    }
}
/** @nocollapse */ /** @nocollapse */ DataHttpClientModule.ɵfac = function DataHttpClientModule_Factory(t) { return new (t || DataHttpClientModule)(); };
/** @nocollapse */ /** @nocollapse */ DataHttpClientModule.ɵmod = /** @pureOrBreakMyCode */ i0.ɵɵdefineNgModule({ type: DataHttpClientModule });
/** @nocollapse */ /** @nocollapse */ DataHttpClientModule.ɵinj = /** @pureOrBreakMyCode */ i0.ɵɵdefineInjector({});
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(DataHttpClientModule, [{
        type: NgModule
    }], null, null); })();

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

export { AbstractHttpClient, DATA_CONFIG_SERVICE_TOKEN, DATA_HTTP_CLIENT_INTERCEPTOR, DataConfiguratorService, DataHttpClient, DataHttpClientModule, DefaultHttpClientInterceptor, LimitConcurrencyService, RestTemplate };
//# sourceMappingURL=angular-ru-cdk-http.js.map
