import { Type, InjectionToken, EnvironmentProviders } from '@angular/core';
import { Observable, OperatorFunction } from 'rxjs';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';

/**
 * This class represents errors thrown by _Angular HAL Client_. It
 * unifies errors produced by _Angular HTTP Client_ and errors originating
 * in the API.
 */
declare class HalError extends Error {
    /**
     * The URI path where the error originated.
     */
    path?: string;
    /**
     * The HTTP status code of the error.
     */
    status?: number;
    /**
     * The description of the HTTP status.
     */
    error?: string;
    /**
     * Free form error properties.
     */
    [key: string]: any;
    /**
     * @param err the object used to set properties
     */
    constructor(err: any);
}

interface Link {
    readonly href: string;
    readonly templated?: boolean;
    readonly methods?: string[];
}
/**
 * The common base for resource and accessor.
 */
declare abstract class HalBase {
    protected readonly _client: HttpClient;
    protected readonly _links: Readonly<Record<string, Link>>;
    protected readonly _embedded: Readonly<Record<string, Object>>;
    /**
     * The URI of `self` link.
     */
    get self(): string;
    /**
     * This property is `true` when the `self` link exists and either
     * `methods` array does not exist in the `self` link or the array
     * exists and contains `POST` string. It is `false` otherwise.
     */
    get canCreate(): boolean;
    /**
     * This property is `true` when the `self` link exists and either
     * `methods` array does not exist in the `self` link or the array
     * exists and contains `GET` string. It is `false` otherwise.
     */
    get canRead(): boolean;
    /**
     * This property is `true` when the `self` link exists and either
     * `methods` array does not exist in the `self` link or the array
     * exists and contains `DELETE` string. It is `false` otherwise.
     */
    get canDelete(): boolean;
    /**
     * @param obj the object used to assign the properties
     */
    constructor(obj: Object);
    /**
     * Create a new resource in the collection identified by `self` link.
     * It makes a `POST` call to the URI in `self` link and returns an
     * observable for the call. The observable emits an accessor for
     * the newly created resource. The `self` link in the accessor
     * may be set to `undefined` if `Location` header is not returned by
     * the call.
     *
     * @param obj the payload for the `POST` method call
     * @returns an observable of the resource's accessor
     */
    create(obj: any): Observable<Accessor>;
    /**
     * Delete the resource identified by `self` link.
     *
     * @returns an observable that emits next signal on successful delete
     */
    delete(): Observable<void>;
    protected withUriFor<T>(method: string, func: (x: string) => Observable<T>): Observable<T>;
    protected accessor(href?: string, methods?: string[]): Accessor;
    protected instanceOf<T>(type: Type<T>, obj: Object): T;
    protected roInstanceOf<T>(type: Type<T>, obj: Object): T;
    protected handleError(err: HttpErrorResponse): Observable<never>;
    protected uriFor(method: string): string | undefined;
}

/**
 * This type represent parameters for templated links.
 */
type Params = Record<string, string | number | boolean>;
/**
 * This class represents an in-memory instance of a HAL resource.
 */
declare class Resource extends HalBase {
    /**
     * This property is `true` when the `self` link exists and either
     * `methods` array does not exist in the `self` link or the array
     * exists and contains `PUT` string. It is `false` otherwise.
     */
    get canUpdate(): boolean;
    /**
     * Follow the relation link. Returns an accessor for the resource.
     * The `self` link in the accessor is set to `undefined` if no such
     * relation exists.
     *
     * @param rel the name of the relation link
     * @param params parameters for a templated link
     * @returns an accessor for the linked resource
     */
    follow(rel: string, params?: Params): Accessor;
    /**
     * Refresh the resource. In other words, read the resource
     * identified by `self` link.
     *
     * @returns an observable of the refreshed resource instance
     */
    read(): Observable<this>;
    /**
     * Persist the resource. Uses `PUT` request to send the new resource
     * state.
     *
     * @returns an observable of the resource instance
     */
    update(): Observable<this>;
    /**
     * Returns a single embedded resource or `undefined` if no such
     * embedded exists. If the named resource is an array it returns
     * the first element.
     *
     * @param type the resource type
     * @param rel the name of the embedded resource
     * @returns the resource or `undefined`
     */
    get<T extends Resource>(type: Type<T>, rel: string): T | undefined;
    /**
     * Returns an embedded array of resources or `undefined` if the named
     * embedded does not exist. If the named resource is not an array it
     * wraps the resource in an array.
     *
     * @param type the element resource type
     * @param rel the name of the embedded resource array
     * @returns the resource array or `undefined`
     */
    getArray<T extends Resource>(type: Type<T>, rel: string): T[] | undefined;
    /**
     * Clones the resource instance and allows to modify the clone's
     * properties using the provided function.
     *
     * @param update the update function to apply to the clone
     * @returns the updated clone of the resource
     */
    mutate(update: (x: this) => void): this;
    protected roArrayOf<T extends Resource>(type: Type<T>, values: Object[]): T[];
}

/**
 * This class represents an in-memory collection of resources.
 */
declare class Collection<T extends Resource> extends Resource {
    private readonly type;
    /**
     * Zero-based offset of the first element of the `values` array in
     * the collection.
     */
    readonly start: number;
    /**
     * A page of values of the collection starting with `start` offset.
     */
    readonly values: T[];
    /**
     * @param type the element resource type
     * @param obj the object used to assign the properties
     */
    constructor(type: Type<T>, obj: any);
    /**
     * Follow the `next` link. If the link does not exist, the accessor
     * will have the `self` link set to `undefined`.
     *
     * @returns the accessor for the link
     */
    next(): Accessor;
    /**
     * Follow the `prev` link. If the link does not exist, the accessor
     * will have the `self` link set to `undefined`.
     *
     * @returns the accessor for the link
     */
    prev(): Accessor;
    /**
     * Refresh the resource collection. In other words, read
     * the resource collection identified by `self` link.
     *
     * @returns an observable of the refreshed resource collection
     * instance
     */
    read(): Observable<this>;
}

/**
 * Convert any item to {@link Object} or {@link Array}. On conversion
 * HAL-related properties (`_client`, `_links`, `_embedded`)
 * are not included in the resulting object. Primitive types
 * as well as `null` or `undefined` are returned unchanged.
 * If the argument is an array, every item of the returned array
 * is converted recursively. For {@link Collection} it returns
 * the array of values where each item is converted recursively.
 *
 * @param item the item to convert
 * @returns an object, array, primitive value, `undefined` or `null`
 */
declare function objectFrom(item: any): any;
/**
 * @param arg argument to test
 * @returns `false` if the argument is `null` or `undefined` and `true`
 * otherwise
 */
declare function isDefined<T>(arg: T | null | undefined): arg is T extends null | undefined ? never : T;

/**
 * Returns an RxJS operator that makes the source {@link Observable}
 * complete at the same time as the lifetime {@link Observable}.
 *
 * @param lifetime the lifetime observable
 * @returns a function that transforms the source {@link Observable}
 */
declare function completeWith<T>(lifetime: Observable<any>): OperatorFunction<T, T>;
/**
 * Returns an RxJS operator to follow a link on a {@link Resource}. It is
 * equivalent to
 * ```ts
 * map(resource => resource.follow(rel, params))
 * ```
 *
 * @param rel the relation to follow
 * @param params the parameters to expand the link
 * @returns a function that transforms the source {@link Observable}
 */
declare function follow(rel: string, params?: Params): OperatorFunction<Resource, Accessor>;
/**
 * Returns an RxJS operator to create a new resource identified by
 * an {@link Accessor} or a {@link Resource}. It is equivalent to
 * ```ts
 * switchMap(x => x.create(obj))
 * ```
 *
 * @param obj the value for the resource
 * @returns a function that transforms the source {@link Observable}
 */
declare function create(obj: any): OperatorFunction<Accessor | Resource, Accessor>;
/**
 * Returns an RxJS operator to read a {@link Resource} using an
 * {@link Accessor}. It is equivalent to
 * ```ts
 * switchMap(accessor => accessor.read(type))
 * ```
 *
 * @param type the resource type
 * @returns a function that transforms the source {@link Observable}
 */
declare function read<T extends Resource>(type: Type<T>): OperatorFunction<Accessor, T>;
/**
 * Returns an RxJS operator to read a {@link Collection} of resources
 * using an {@link Accessor}. It is equivalent to
 * ```ts
 * switchMap(accessor => accessor.readCollection(type))
 * ```
 *
 * @param type the collection element type
 * @returns a function that transforms the source {@link Observable}
 */
declare function readCollection<T extends Resource>(type: Type<T>): OperatorFunction<Accessor, Collection<T>>;
/**
 * Returns an RxJS operator to refresh a {@link Resource} or
 * {@link Collection} of resources. It is equivalent to
 * ```ts
 * switchMap(resource => resource.read())
 * ```
 *
 * @returns a function that transforms the source {@link Observable}
 */
declare function refresh<T extends Resource>(): OperatorFunction<T, T>;
/**
 * Returns an RxJS operator to clone a {@link Resource} and modify
 * the cloned instance using the provided update function. It is
 * equivalent to
 * ```ts
 * map(resource => resource.mutate(update))
 * ```
 *
 * @param update the update function to apply
 * @returns a function that transforms the source {@link Observable}
 */
declare function mutate<T extends Resource>(update: (x: T) => void): OperatorFunction<T, T>;
/**
 * Returns an RxJS operator to update a {@link Resource}.
 * It is equivalent to
 * ```ts
 * switchMap(resource => resource.update())
 * ```
 *
 * @returns a function that transforms the source {@link Observable}
 */
declare function update<T extends Resource>(): OperatorFunction<T, T>;
/**
 * Returns an RxJS operator to delete a resource identified by
 * an {@link Accessor} or a {@link Resource}. It is equivalent to
 * ```ts
 * switchMap(x => x.delete())
 * ```
 *
 * @returns a function that transforms the source {@link Observable}
 */
declare function del(): OperatorFunction<Accessor | Resource, void>;
/**
 * Returns an RxJS operator to filter out `null` and `undefined` items
 * from the source {@link Observable}.
 *
 * @returns a function that transforms the source {@link Observable}
 */
declare function defined<T>(): OperatorFunction<T | null | undefined, T extends null | undefined ? never : T>;

/**
 * This class provides a way to execute operations on HAL resources
 * without reading them first. Accessors are obtained either by following
 * resource links or by getting the root entry point for the API.
 */
declare class Accessor extends HalBase {
    /**
     * Read the resource identified by `self` link.
     *
     * @param type the resource type
     * @returns an observable of the resource instance
     */
    read<T extends Resource>(type: Type<T>): Observable<T>;
    /**
     * Read the resource collection identified by `self` link.
     *
     * @param type the collection element type
     * @returns an observable of the collection instance
     */
    readCollection<T extends Resource>(type: Type<T>): Observable<Collection<T>>;
}
/**
 * The injection token for HAL root URL.
 */
declare const HAL_ROOT: InjectionToken<Accessor>;
/**
 * Configure HAL client using the provided the root URL for the API.
 *
 * @param url the root URL
 * @return an array of environment providers with a single factory
 * provider for the {@link Accessor} for the API root
 */
declare function provideHalRoot(url: string): EnvironmentProviders;

export { Accessor, Collection, HAL_ROOT, HalError, Resource, completeWith, create, defined, del, follow, isDefined, mutate, objectFrom, provideHalRoot, read, readCollection, refresh, update };
export type { Params };
