import { FetchOptions, ResponseType, FetchHook, FetchContext, FetchResponse } 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;
}
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;

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 FeedOptions {
    http: FeedHttpOptions;
    hooks?: FeedHooksOptions;
}
interface CreateFeedOptions {
    http: FeedHttpOptions;
    hooks?: FeedHooksOptions;
    limiter?: CreateLimiterOptions;
    auth?: CreateAuthOptions;
}
interface RequestOptions {
    query?: string | undefined;
}
type ResourceId = string | number;
type ResourceQuery = string;
interface FeedBaseResponse<R> {
    context?: string;
    data: R | R[];
}
interface FeedEntityResponse<R> extends FeedBaseResponse<R> {
    data: R;
}
interface FeedCollectionResponse<R> extends FeedBaseResponse<R> {
    nextLink?: string;
    count?: string | number;
    data: R[];
}

/**
 * Conditional type utility that determines if a given type 'T' resolves to 'any'.
 *
 * If T is any it resolves to type 'Y'
 * If T is not any it resolves to type 'N'
 */
type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
/**
 * Resolves the type of the Resource (R) based on the Schema and the requested Resource.
 *
 * If Schema is 'any', it defaults to Record<string, unknown>.
 * If Schema is specific, it looks up Schema[R].
 */
type ResolveResourceType<Schema, R> = IfAny<Schema, Record<string, unknown>, // Fallback for 'any' or 'unknown' Schema
Schema extends Record<string, any> ? (R extends keyof Schema ? Schema[R] : never) : never>;

declare class Feed<Schema> {
    http: FeedOptions['http'];
    hooks: FeedOptions['hooks'];
    constructor(opts: FeedOptions);
    /**
     * Fetch the OData $metadata.
     *
     * @param [query] The optional query to apply for the metadata request (rarely used).
     * @returns The raw metadata XML document
     */
    $metadata(query?: ResourceQuery): Promise<string>;
    /**
     * Fetch a single resource entity by its unique ID.
     * @param resource The name of the resource collection.
     * @param id The unique identifier for the specific entity.
     * @param [query] The optional query to apply.
     * @returns The entity
     */
    readById<R extends IfAny<Schema, string, keyof Schema>>(resource: R, id: ResourceId, query?: ResourceQuery): Promise<FeedEntityResponse<ResolveResourceType<Schema, R>>>;
    /**
     * Fetch a resource collection.
     *
     * The generator will yield a response for each page of results
     * and automatically follow the `@odata.nextLink` until all entities are retrieved.
     *
     * @param resource The name of the resource collection.
     * @param [query] The optional query to apply.
     * @returns An async generator that yields paginated collection responses.
     */
    readByQuery<R extends IfAny<Schema, string, keyof Schema>>(resource: R, query?: ResourceQuery): AsyncGenerator<FeedCollectionResponse<ResolveResourceType<Schema, R>>, void, unknown>;
    request<R>(path: string, options?: RequestOptions): Promise<string | FeedCollectionResponse<R> | FeedEntityResponse<R>>;
}

declare function createFeed<Schema>(opts: CreateFeedOptions): Feed<Schema>;

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, FeedCollectionResponse, FeedEntityResponse, FeedErrorOptions, FeedHooksOptions, FeedHttpOptions, FeedLimiter, FeedOptions, IfAny, RequestOptions, ResolveResourceType, ResourceId, ResourceQuery, TransportError, TransportErrorDetail };
