import { FetchOptions, ResponseType, FetchHook, FetchContext, FetchResponse, $Fetch } from 'ofetch';
import PQueue from 'p-queue';

type AuthOptionType = 'bearer' | 'credentials';
interface BaseAuthOptions {
    type: AuthOptionType;
}
interface AuthBearerOptions extends BaseAuthOptions {
    type: 'bearer';
    credentials: {
        token: string;
        tokenType?: string;
    };
}
interface AuthClientCredentialsOptions extends BaseAuthOptions {
    type: 'credentials';
    credentials: {
        tokenURL: string;
        clientId: string;
        clientSecret: string;
        scope?: string;
        grantType?: string;
    };
    refreshBuffer?: number;
}
type AuthOptions = AuthBearerOptions | AuthClientCredentialsOptions;
type CreateAuthOptions = AuthOptions;

interface TransportError {
    code: string | number;
    message: string;
    target: string;
    details: TransportErrorDetail[];
    [key: string]: unknown;
}
interface TransportErrorDetail {
    code: string;
    target: string;
    message: string;
}
type TransportResponse<V extends Record<string, unknown>> = TransportMultiResponse<V> | TransportSingleResponse<V>;
interface TransportBaseResponse {
    '@odata.context'?: string;
    '@odata.nextLink'?: string;
    '@odata.count'?: string | number;
}
type TransportSingleResponse<V> = TransportBaseResponse & V & {
    value: never;
};
interface TransportMultiResponse<V extends Record<string, unknown>> extends TransportBaseResponse {
    value: V[];
}
declare enum TransportErrorCode {
    ServiceUnavailable = "SERVICE_UNAVAILABLE",
    Forbidden = "FORBIDDEN",
    BadRequest = "BAD_REQUEST",
    NotFound = "NOT_FOUND",
    RequestEntityTooLarge = "REQUEST_ENTITY_TO_LARGE",
    UnsupportedMedia = "UNSUPPORTED_MEDIA",
    TooManyRequests = "TOO_MANY_REQUESTS",
    InternalServerError = "INTERNAL_SERVER_ERROR",
    NotImplemented = "NOT_IMPLEMENTED",
    Unknown = "UNKNOWN"
}

type FeedErrorOptions = {
    message?: string;
} & Partial<Pick<TransportError, 'code' | 'target' | 'details'>>;

interface CreateLimiterOptions {
    duration?: number;
    points?: number;
}
type FeedLimiter = PQueue;

interface CreateFeedOptions {
    http: FeedHttpOptions;
    hooks?: FeedHooksOptions;
    limiter?: CreateLimiterOptions;
    auth?: CreateAuthOptions;
}
type FeedHttpOptions = Omit<FetchOptions, 'onResponse' | 'onResponseError' | 'onRequest' | 'onRequestError' | 'baseURL'> & Required<Pick<FetchOptions, 'baseURL'>>;
interface FeedHooksOptions<T = unknown, R extends ResponseType = ResponseType> {
    onRequest?: FetchHook<FetchContext<T, R>>[];
    onRequestError?: FetchHook<FetchContext<T, R> & {
        error: Error;
    }>[];
    onResponse?: FetchHook<FetchContext<T, R> & {
        response: FetchResponse<T>;
    }>[];
    onResponseError?: FetchHook<FetchContext<T, R> & {
        response: FetchResponse<T>;
    }>[];
}
interface FeedBaseResponse {
    context?: string;
    nextLink?: string;
    count?: string | number;
}
interface FeedMultiResponse<V> extends FeedBaseResponse {
    values: V[];
}
interface FeedSingleResponse<V> extends FeedBaseResponse {
    value: V;
}
type FeedResponse<V> = FeedMultiResponse<V> | FeedSingleResponse<V>;
interface FeedOptions {
    http: FeedHttpOptions;
    hooks?: FeedHooksOptions;
}
type FeedRequestOptions = Omit<FetchOptions, 'onResponse' | 'onResponseError' | 'onRequest' | 'onRequestError' | 'baseURL'>;

declare class Feed {
    http: FeedOptions['http'];
    hooks: FeedOptions['hooks'];
    client: $Fetch;
    constructor(opts: FeedOptions);
    $metadata(query?: string): string;
    buildURL(url: string, query?: string): string;
    readByQuery<V extends Record<string, unknown>>(resource: string, query?: string): AsyncGenerator<FeedResponse<V>>;
    readById<V extends Record<string, unknown>>(resource: string, id: string | number, query?: string): Promise<FeedResponse<V>>;
    request<R extends Record<string, unknown>>(path: string, opts?: FeedRequestOptions): Promise<string | FeedResponse<R> | null>;
}

declare function createFeed(opts: CreateFeedOptions): Feed;

declare class FeedError extends Error {
    name: TransportErrorCode;
    code: string | number;
    target: string | null;
    details: NonNullable<FeedErrorOptions['details']>;
    constructor(opts: FeedErrorOptions);
    toString(): string;
}

export { FeedError, TransportErrorCode, createFeed };
export type { AuthBearerOptions, AuthClientCredentialsOptions, AuthOptionType, AuthOptions, BaseAuthOptions, CreateAuthOptions, CreateFeedOptions, CreateLimiterOptions, FeedBaseResponse, FeedErrorOptions, FeedHooksOptions, FeedHttpOptions, FeedLimiter, FeedMultiResponse, FeedOptions, FeedRequestOptions, FeedResponse, FeedSingleResponse, TransportBaseResponse, TransportError, TransportErrorDetail, TransportMultiResponse, TransportResponse, TransportSingleResponse };
