import * as i0 from '@angular/core';
import { Injectable, Optional, SkipSelf, runInInjectionContext, Injector, inject, provideAppInitializer, NgModule } from '@angular/core';
import * as i1 from '@c8y/client';
import { FetchClient, BasicAuth, CookieAuth, Realtime, EventBinaryService, EventService, InventoryService, MeasurementService, AlarmService, OperationBulkService, OperationService, ApplicationService, UserService, TenantService, SystemOptionsService, TenantOptionsService, TenantSecurityOptionsService, TenantLoginOptionsService, AuditService, InventoryRoleService, InventoryBinaryService, DeviceRegistrationService, DeviceRegistrationBulkService, UserRoleService, UserGroupService, IdentityService, TrustedCertificateService, CrlService, SmartGroupsService, SmartRulesService, FeatureService, BearerAuthFromSessionStorage } from '@c8y/client';
import { from, Subject, pipe } from 'rxjs';
import { filter, map, scan, distinctUntilChanged, shareReplay } from 'rxjs/operators';
import { pickAuthStrategy } from '@c8y/bootstrap';

class HttpHandler {
}

class HttpInterceptHandler extends HttpHandler {
    constructor(interceptor, nextHandler) {
        super();
        this.interceptor = interceptor;
        this.nextHandler = nextHandler;
    }
    handle(req) {
        return this.interceptor.intercept(req, this.nextHandler);
    }
}
class HttpRequestHandler extends HttpHandler {
    constructor(fetch, apiService) {
        super();
        this.fetch = fetch;
        this.apiService = apiService;
    }
    handle(req) {
        const { options, url } = req;
        const { method } = options;
        this.apiService?.onStart({ method, options, url });
        let fetchPromise = this.fetch(url, options);
        if (typeof options.responseInterceptor === 'function') {
            fetchPromise = fetchPromise.then(options.responseInterceptor);
        }
        fetchPromise.then((response) => this.apiService?.onFinish({ method, options, url, response }), (response) => this.apiService?.onFinish({ method, options, url, response }));
        return from(fetchPromise);
    }
}

class ApiService {
    constructor(client) {
        this.client = client;
        this.callsSubject = new Subject();
        this.interceptors = new Map();
        this.interceptorCounter = 0;
        this.calls = this.callsSubject.asObservable();
        this.hookIntoClientFetch();
        this.isLoading$ = this.calls.pipe(filter(({ url }) => !/notification\/realtime/.test(url)), map(({ phase }) => (phase === 'start' ? 1 : -1)), scan((count, item) => count + item, 0), map(count => count > 0), distinctUntilChanged(), shareReplay(1));
    }
    /**
     * Allows to hook into the responses received by the FetchClient.
     * This is meant to be used to react on the responses, not for manipulation of the responses.
     * @param hookFilter A filter function to filter for specific responses.
     * @returns An Observable of the filtered responses.
     */
    hookResponse(hookFilter) {
        return this.callsSubject.pipe(filter(({ phase }) => phase === 'finish'), filter(hookFilter));
    }
    /**
     * Allows to hook into the requests performed by the FetchClient.
     * This is meant to be used to react on the requests, not for manipulation of the requests.
     * @param hookFilter A filter function to filter for specific requests.
     * @returns An Observable of the filtered requests.
     */
    hookRequest(hookFilter) {
        return this.callsSubject.pipe(filter(({ phase }) => phase === 'start'), filter(hookFilter));
    }
    /**
     * Notifies observers that an API call has finished.
     * @param call The API call that has finished.
     */
    onFinish(call) {
        this.callsSubject.next({ phase: 'finish', ...call });
    }
    /**
     * Notifies observers that an API call has started.
     * @param call The API call that has started.
     */
    onStart(call) {
        this.callsSubject.next({ phase: 'start', ...call });
    }
    /**
     * Resolves data from an API call response.
     * @returns A Promise containing an object with resolved data, method, and URL.
     */
    resolveData(call) {
        const { response, method, url } = call;
        if ('data' in response) {
            return Promise.resolve({ data: response.data, method, url });
        }
        else {
            // No Content success status, for example DELETE request.
            if (response?.status === 204) {
                return Promise.resolve({ data: null, method, url });
            }
            const cb = data => ({ data, method, url });
            return response.clone().json().then(cb, cb);
        }
    }
    /**
     * Can be added to a pipe to exclude any permission call. Permission calls are PUT
     * request with only an id in it, to verify if the user has access to this managed object.
     * @returns The operator to be added to a pipe.
     */
    excludePermissionCall() {
        return pipe(filter(({ method, options }) => {
            if (method === 'PUT' && options.body && typeof options.body === 'string') {
                const parsedBody = JSON.parse(options.body);
                const bodyKeys = Object.keys(parsedBody);
                return !(bodyKeys.length === 1 && bodyKeys[0] === 'id');
            }
            return true;
        }));
    }
    /**
     * Allows to intercept requests performed via the FetchClient requests.
     * @param interceptor The interceptor to be added.
     * @param id An optional unique identifier for the interceptor. The chain of interceptors is ordered by this id. And it can be used to remove the interceptor later on.
     * @returns The id of the interceptor (same as provided id if one was provided, otherwise an id will be generated).
     */
    addInterceptor(interceptor, id) {
        if (!id) {
            id = `${++this.interceptorCounter}`;
        }
        this.interceptors.set(id, interceptor);
        return id;
    }
    /**
     * Allows to remove a previously added interceptor by it's id.
     * @param id The id of the interceptor that should be removed.
     * @returns true if an interceptor existed and has been removed, or false if id does not exist.
     */
    removeInterceptor(id) {
        return this.interceptors.delete(id);
    }
    /**
     * Checks if an interceptor with a given id exists.
     * @param id The id of the interceptor.
     * @returns - Returns true if an interceptor with the given id exists, otherwise false.
     */
    hasInterceptor(id) {
        return this.interceptors.has(id);
    }
    hookIntoClientFetch() {
        const fetch = this.client.fetch.bind(this.client);
        const requestHandler = new HttpRequestHandler(fetch, this);
        this.client.fetch = async (url, options = { method: 'GET' }) => {
            const { method } = options;
            return this.createInterceptorChain({ url, options, method }, requestHandler).toPromise();
        };
    }
    createInterceptorChain(call, requestHandler) {
        let handler = requestHandler;
        // Do some sorting to always apply the interceptors in the specific order
        const sortedInterceptorIds = Array.from(this.interceptors.keys()).sort((a, b) => b.localeCompare(a));
        for (const interceptorId of sortedInterceptorIds) {
            handler = new HttpInterceptHandler(this.interceptors.get(interceptorId), handler);
        }
        return handler.handle(call);
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ApiService, deps: [{ token: i1.FetchClient }], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ApiService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ApiService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root'
                }]
        }], ctorParameters: () => [{ type: i1.FetchClient }] });

const defaultServicesFromClientLib = [
    FetchClient,
    BasicAuth,
    CookieAuth,
    Realtime,
    EventBinaryService,
    EventService,
    InventoryService,
    MeasurementService,
    AlarmService,
    OperationBulkService,
    OperationService,
    ApplicationService,
    UserService,
    TenantService,
    SystemOptionsService,
    TenantOptionsService,
    TenantSecurityOptionsService,
    TenantLoginOptionsService,
    AuditService,
    InventoryRoleService,
    InventoryBinaryService,
    DeviceRegistrationService,
    DeviceRegistrationBulkService,
    UserRoleService,
    UserGroupService,
    IdentityService,
    TrustedCertificateService,
    CrlService,
    SmartGroupsService,
    SmartRulesService,
    FeatureService
];

function toProvider(provide) {
    let deps = [FetchClient, Realtime];
    if (provide === FetchClient) {
        deps = [CookieAuth];
    }
    if (provide === BasicAuth || provide === CookieAuth) {
        deps = [];
    }
    if (provide === Realtime) {
        deps = [FetchClient];
    }
    const depsWithExistingProvider = [
        [provide, new Optional(), new SkipSelf()], // let's see if we can find an existing provider (possibly null)
        ...deps
    ];
    return {
        provide,
        deps: depsWithExistingProvider,
        useFactory: (...args) => {
            // if there was an existing provider, it would be first in the list and we can return it
            if (args.length && args[0] !== null) {
                return args[0];
            }
            // otherwise we need to drop the first element as it would be null
            const providers = args.slice(1);
            // the other providers are the dependencies we need. They match the order of the deps array
            const existingProviders = deps.map((dep, index) => {
                return { provide: dep, useValue: providers[index] };
            });
            // use this to dependency inject the dependencies and instantiate the class
            return runInInjectionContext(Injector.create({
                providers: [...existingProviders, { provide, useClass: provide, deps: deps }]
            }), () => {
                return inject(provide);
            });
        }
    };
}
/**
 * Provides all services from `@c8y/client` library.
 * @returns An array of providers for all services from `@c8y/client` library.
 */
function provideClientLibServices() {
    const providers = defaultServicesFromClientLib
        .map(service => toProvider(service))
        .concat([
        { provide: ApiService, useClass: ApiService, deps: [FetchClient] },
        // ensure that the auth strategy is set before the app is initialized
        provideAppInitializer(() => {
            const client = inject(FetchClient);
            const auth = pickAuthStrategy(BearerAuthFromSessionStorage, BasicAuth, CookieAuth);
            client.setAuth(auth);
        })
    ]);
    return providers;
}
/**
 * @deprecated use provideClientLibServices instead
 */
class DataModule {
    static providers() {
        return provideClientLibServices();
    }
    static forRoot() {
        return {
            ngModule: DataModule,
            providers: provideClientLibServices()
        };
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
    static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.19", ngImport: i0, type: DataModule }); }
    static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataModule, providers: provideClientLibServices() }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: DataModule, decorators: [{
            type: NgModule,
            args: [{
                    providers: provideClientLibServices()
                }]
        }] });

// do not expose as it might confuse people on what to implement
// export * from './http-handler.model';

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

export { ApiService, DataModule, HttpHandler, defaultServicesFromClientLib, provideClientLibServices };
//# sourceMappingURL=c8y-ngx-components-api.mjs.map
