import * as i1$1 from '@rxap/data-source';
import { RxapDataSourceError, BaseDataSource, RXAP_DATA_SOURCE_METADATA, RXAP_DATA_SOURCE_REFRESH, RxapDataSource, DataSourceLoader } from '@rxap/data-source';
import * as i2 from 'rxjs';
import { Subject, firstValueFrom, ReplaySubject, EMPTY, of, throwError } from 'rxjs';
import * as i1 from '@angular/common/http';
import { HttpRequest, HttpClient, HttpEventType, HttpResponse } from '@angular/common/http';
import { joinPath, deepMerge, hasIndexSignature } from '@rxap/utilities';
import * as i0 from '@angular/core';
import { Injectable, Inject, Optional } from '@angular/core';
import { tap, switchMap, finalize, skip, retry, filter, catchError, map, take, timeout } from 'rxjs/operators';
import { RequestInProgressSubject } from '@rxap/rxjs';

class RxapHttpDataSourceError extends RxapDataSourceError {
    constructor(message, code, className) {
        super(message, code, className);
        this.addSubPackageName('http');
    }
}

class BaseHttpDataSource extends BaseDataSource {
    constructor(http, metadata = null, refresh$ = null) {
        super(metadata);
        this.http = http;
        this.requestsInProgress$ = new RequestInProgressSubject();
        this.refresh$ = new Subject();
        this.refreshed$ = new Subject();
        this.timeout = 60 * 1000;
        this._refreshSubscription = null;
        this.timeout = this.metadata.timeout ?? this.timeout;
        this.loading$ = this.requestsInProgress$.loading$();
        if (http === undefined) {
            throw new RxapDataSourceError('HttpClient is undefined. Ensure that the HttpClient is added to the deps property!', '', this.constructor.name);
        }
        if (refresh$) {
            this.refresh$ = refresh$;
        }
    }
    request$(options = this._options, merge = false) {
        this.init();
        if (merge) {
            // TODO : add deep merge
            options = { ...this._options, ...options };
        }
        // test if options is defined. Can be undefined if the init method is not yet been called
        if (!options) {
            options = this._options;
        }
        return firstValueFrom(this.buildRequest(options));
    }
    reset() {
        this._data = undefined;
        if (this._refreshSubscription) {
            this._refreshSubscription.unsubscribe();
        }
        super.reset();
        this._initialised = false;
        this.init();
    }
    refresh(options = this._options, merge = false, force = false) {
        this.init();
        if (force || this._connectedViewer.size !== 0) {
            if (merge) {
                // TODO : add deep merge
                options = { ...this._options, ...options };
            }
            // test if options is defined. Can be undefined if the init method is not yet been called
            if (!options) {
                options = this._options;
            }
            this.refresh$.next(options);
            this.refreshed$.next();
        }
    }
    init() {
        if (this._initialised) {
            return;
        }
        super.init();
        if (this._refreshSubscription) {
            throw new RxapHttpDataSourceError('The refresh subscription is already initialised', '');
        }
        this._options = {
            params: this.metadata.params,
            pathParams: this.metadata.pathParams,
            body: this.metadata.body,
            headers: this.metadata.headers,
        };
        this._data$ = new ReplaySubject(this.metadata.bufferSize ?? 1);
        this._refreshSubscription = this.refresh$.pipe(tap(options => this._options = options), tap(() => this.requestsInProgress$.increase()), switchMap(options => this.buildRequest(options).pipe(finalize(() => this.requestsInProgress$.decrease())))).subscribe(this._data$);
    }
    isEqualToLastOptions(options) {
        const keys = [
            'url',
            'pathParams',
            'body',
            'params',
            'headers',
        ];
        return !!this._options && keys.every(key => this._options[key] === options[key]);
    }
    _connect(viewer) {
        this.init();
        if (viewer.url === this.metadata.url) {
            delete viewer.url;
        }
        if (viewer.viewChange ===
            EMPTY &&
            (!viewer.lazy || this._data === undefined || !this.isEqualToLastOptions(viewer))) {
            viewer.realtime = this._data !== undefined;
            viewer.viewChange = of(viewer);
        }
        return [
            viewer.realtime ? this._data$.pipe(skip(this.metadata.bufferSize ?? 1)) : this._data$,
            viewer.viewChange.pipe(tap(options => this.refresh(options, true, true))).subscribe(),
        ];
    }
    getRequestUrl() {
        if (typeof this.metadata.url === 'function') {
            return this.metadata.url();
        }
        return this.metadata.url;
    }
    buildHttpRequest(options) {
        return new HttpRequest(this.metadata.method ?? 'GET', this.buildUrlWithParams(joinPath(this.getRequestUrl(), options.url ?? null), deepMerge(this.metadata.pathParams ?? {}, options.pathParams ?? {})), options.body ?? this.metadata.body ?? null, {
            params: options.params ?? this.metadata.params,
            headers: options.headers ?? this.metadata.headers,
            reportProgress: true,
            responseType: this.metadata.responseType ?? 'json',
            withCredentials: this.metadata.withCredentials,
        });
    }
    buildUrlWithParams(url, pathParams) {
        const matches = url.match(/\{[^}]+\}/g);
        if (matches) {
            if (!hasIndexSignature(pathParams)) {
                throw new RxapDataSourceError(`Path params for connection '${this.id}' has not an index signature`, '', this.constructor.name);
            }
            for (const match of matches) {
                const param = match.substr(1, match.length - 2);
                let replace = null;
                // eslint-disable-next-line no-prototype-builtins
                if (pathParams.hasOwnProperty(param)) {
                    replace = encodeURIComponent(pathParams[param]);
                }
                if (replace === null) {
                    throw new RxapDataSourceError(`Path params for connection '${this.id}' has not a defined value for '${param}'`, '', this.constructor.name);
                }
                url = url.replace(match, replace);
            }
        }
        return url;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: BaseHttpDataSource, deps: [{ token: HttpClient }, { token: RXAP_DATA_SOURCE_METADATA, optional: true }, { token: RXAP_DATA_SOURCE_REFRESH, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: BaseHttpDataSource }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: BaseHttpDataSource, decorators: [{
            type: Injectable
        }], ctorParameters: () => [{ type: i1.HttpClient, decorators: [{
                    type: Inject,
                    args: [HttpClient]
                }] }, { type: undefined, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [RXAP_DATA_SOURCE_METADATA]
                }] }, { type: i2.Subject, decorators: [{
                    type: Optional
                }, {
                    type: Inject,
                    args: [RXAP_DATA_SOURCE_REFRESH]
                }] }] });

class HttpDataSource extends BaseHttpDataSource {
    constructor() {
        super(...arguments);
        this.onSentEvent$ = new Subject();
        this.onResponse$ = new Subject();
        this.onDownloadProgress$ = new Subject();
        this.onUploadProgress$ = new Subject();
        this.onResponseHeader$ = new Subject();
    }
    derive(id, metadata = this.metadata, isolated = false) {
        return new HttpDataSource(this.http, {
            ...deepMerge(this.metadata, metadata),
            id,
        }, isolated ? null : this.refresh$);
    }
    transform(data) {
        return data;
    }
    handelHttpEvent(event) {
        switch (event.type) {
            case HttpEventType.Sent:
                this.onSentEvent$.next(event);
                break;
            case HttpEventType.Response:
                this.onResponse$.next(event);
                break;
            case HttpEventType.DownloadProgress:
                this.onDownloadProgress$.next(event);
                break;
            case HttpEventType.UploadProgress:
                this.onUploadProgress$.next(event);
                break;
            case HttpEventType.ResponseHeader:
                this.onResponseHeader$.next(event);
                break;
        }
    }
    buildRequest(options) {
        return this.http.request(this.buildHttpRequest(options)).pipe(retry(this.metadata.retry ?? 0), tap(event => this.handelHttpEvent(event)), filter((event) => event instanceof HttpResponse), tap((response) => {
            this.interceptors?.forEach(interceptor => interceptor
                .next({
                response,
                options,
            }));
        }), catchError((response) => {
            this.interceptors?.forEach(interceptor => interceptor
                .next({
                response,
                options,
            }));
            return throwError(response);
        }), map((event) => event.body), map(data => this.transform(data)), take(1), timeout(this.timeout));
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: HttpDataSource, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: HttpDataSource }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: HttpDataSource, decorators: [{
            type: Injectable
        }] });
function RxapHttpDataSource(metadata, className = 'HttpDataSource', packageName = '@rxap/data-source/http') {
    return function (target) {
        RxapDataSource(metadata, className, packageName)(target);
    };
}

class HttpDataSourceLoader {
    // Instead of extanding the DataSourceLoader class the DataSourceLoader instance
    // will be injected, else there could be multiple instance of DataSourceLoader's.
    constructor(dataSourceLoader) {
        this.dataSourceLoader = dataSourceLoader;
    }
    request$(dataSourceIdOrInstanceOrToken, options, merge = false, metadata, injector, notFoundValue, flags) {
        const dataSource = this
            .dataSourceLoader
            .load(dataSourceIdOrInstanceOrToken, metadata, injector, notFoundValue, flags);
        if (!(dataSource instanceof HttpDataSource)) {
            throw new RxapDataSourceError(`The data source is not a HttpDataSource`, '', 'HttpDataSourceLoader');
        }
        return dataSource.request$(options, merge);
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: HttpDataSourceLoader, deps: [{ token: DataSourceLoader }], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: HttpDataSourceLoader, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.3", ngImport: i0, type: HttpDataSourceLoader, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }], ctorParameters: () => [{ type: i1$1.DataSourceLoader, decorators: [{
                    type: Inject,
                    args: [DataSourceLoader]
                }] }] });

// region 
// endregion

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

export { BaseHttpDataSource, HttpDataSource, HttpDataSourceLoader, RxapHttpDataSource, RxapHttpDataSourceError };
//# sourceMappingURL=rxap-data-source-http.mjs.map
