import type { B, Call, Fn, Objects, Pipe, Strings, Tuples } from 'hotscript';
import { type O } from 'ts-toolbelt';
import { HTTPMethod, NotOkStatusCode, OkStatusCode, StatusCode, TypedResponse } from '../typed-fetch.js';
import type { ExtractPathParamsWithBrackets, ExtractPathParamsWithPattern, FromSchema, JSONSchema, OpenAPIDocument, Simplify } from '../types.js';
import type { OASOAuthPath, OAuth2AuthParams } from './auth/oauth.js';
import { ClientTypedResponsePromise } from './clientResponse.js';
type JSONSchema7TypeName = 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null';
type Mutable<Type> = FixJSONSchema<{
    -readonly [Key in keyof Type]: Mutable<Type[Key]>;
}>;
type RefToPath<T extends string> = T extends `#/${infer Ref}` ? Call<Strings.Split<'/'>, Ref> : never;
type ResolveRef<TObj, TRef extends string> = {
    $id: TRef;
} & O.Path<TObj, RefToPath<TRef>>;
type ResolveRefInObj<T, TBase> = T extends {
    $ref: infer Ref;
} ? Ref extends string ? ResolveRef<TBase, Ref> : T : T;
type ResolveRefsInObj<T, TBase = T> = {
    [K in keyof T]: ResolveRefsInObj<ResolveRefInObj<T[K], TBase>, TBase>;
};
/**
 * Resolve all $refs in the OpenAPI document and normalizes the types for the client generic
 */
export type NormalizeOAS<TOAS> = Mutable<ResolveRefsInObj<TOAS>>;
export type OASPathMap<TOAS extends OpenAPIDocument> = TOAS['paths'];
export type OASMethodMap<TOAS extends OpenAPIDocument, TPath extends keyof OASPathMap<TOAS>> = OASPathMap<TOAS>[TPath];
export type OASStatusMap<TOAS extends OpenAPIDocument, TPath extends keyof OASPathMap<TOAS>, TMethod extends keyof OASMethodMap<TOAS, TPath>> = OASMethodMap<TOAS, TPath>[TMethod] extends {
    responses: any;
} ? OASMethodMap<TOAS, TPath>[TMethod]['responses'] : never;
export type OASResponseSchemas<TOAS extends OpenAPIDocument, TPath extends keyof OASPathMap<TOAS>, TMethod extends keyof OASMethodMap<TOAS, TPath>, TStatus extends keyof OASStatusMap<TOAS, TPath, TMethod>> = OASStatusMap<TOAS, TPath, TMethod>[TStatus]['content'];
export type OASJSONResponseSchema<TOAS extends OpenAPIDocument, TPath extends keyof OASPathMap<TOAS>, TMethod extends keyof OASMethodMap<TOAS, TPath>, TStatus extends keyof OASStatusMap<TOAS, TPath, TMethod>> = OASStatusMap<TOAS, TPath, TMethod>[TStatus] extends {
    content: any;
} ? OASResponseSchemas<TOAS, TPath, TMethod, TStatus>[keyof OASResponseSchemas<TOAS, TPath, TMethod, TStatus>]['schema'] : OASStatusMap<TOAS, TPath, TMethod>[TStatus]['schema'];
type ToNumber<T> = T extends `${infer N extends number}` ? N : never;
export type OASResponse<TOAS extends OpenAPIDocument, TPath extends keyof TOAS['paths'], TMethod extends keyof TOAS['paths'][TPath]> = {
    [TStatus in keyof OASStatusMap<TOAS, TPath, TMethod>]: TypedResponse<FromSchema<OASJSONResponseSchema<TOAS, TPath, TMethod, TStatus>>, Record<string, string>, TStatus extends StatusCode ? TStatus : TStatus extends 'default' ? OASStatusMap<TOAS, TPath, TMethod> extends Record<'200' | 200, any> ? Exclude<NotOkStatusCode, ToNumber<Exclude<keyof OASStatusMap<TOAS, TPath, TMethod>, symbol>>> : Exclude<OkStatusCode, ToNumber<Exclude<keyof OASStatusMap<TOAS, TPath, TMethod>, symbol>>> : TStatus extends `${StatusCode}` ? ToNumber<TStatus> : 200>;
}[keyof OASStatusMap<TOAS, TPath, TMethod>];
interface OASParamPropMap {
    query: 'query';
    path: 'params';
    header: 'headers';
}
export type OASParamObj<TParameter extends {
    name: string;
}> = TParameter extends {
    required: true;
} ? {
    [TName in TParameter['name']]: TParameter extends {
        schema: JSONSchema;
    } ? FromSchema<TParameter['schema']> : TParameter extends {
        type: JSONSchema7TypeName;
        enum?: any[];
    } ? FromSchema<{
        type: TParameter['type'];
        enum: TParameter['enum'];
    }> : unknown;
} : {
    [TName in TParameter['name']]?: TParameter extends {
        schema: JSONSchema;
    } ? FromSchema<TParameter['schema']> : TParameter extends {
        type: JSONSchema7TypeName;
        enum?: any[];
    } ? FromSchema<{
        type: TParameter['type'];
        enum: TParameter['enum'];
    }> : unknown;
};
interface OASParamToRequestParam<TParameters extends {
    in: string;
    required?: boolean;
}[]> extends Fn {
    return: this['arg0'] extends {
        name: string;
        in: infer TParamType;
    } ? Extract<TParameters[number], {
        in: TParamType;
        required: true;
    }> extends never ? {
        [TKey in TParamType extends keyof OASParamPropMap ? OASParamPropMap[TParamType] : never]?: OASParamObj<this['arg0']>;
    } : {
        [TKey in TParamType extends keyof OASParamPropMap ? OASParamPropMap[TParamType] : never]: OASParamObj<this['arg0']>;
    } : {};
}
export type OASParamMap<TParameters extends {
    name: string;
    in: string;
}[]> = Pipe<TParameters, [
    Tuples.Map<OASParamToRequestParam<TParameters>>,
    Tuples.ToIntersection
]>;
export type OASClient<TOAS extends OpenAPIDocument, TAuthParamsRequired extends boolean = true> = {
    [TPath in keyof OASPathMap<TOAS>]: {
        [TMethod in keyof OASMethodMap<TOAS, TPath>]: OASRequestParams<TOAS, TPath, TMethod, TAuthParamsRequired> extends {
            json: {};
        } | {
            params: {};
        } | {
            headers: {};
        } | {
            query: {};
        } ? (requestParams: Simplify<OASRequestParams<TOAS, TPath, TMethod, TAuthParamsRequired>> & ClientRequestInit) => ClientTypedResponsePromise<OASResponse<TOAS, TPath, TMethod>> : (requestParams?: Simplify<OASRequestParams<TOAS, TPath, TMethod, TAuthParamsRequired>> & ClientRequestInit) => ClientTypedResponsePromise<OASResponse<TOAS, TPath, TMethod>>;
    };
} & OASOAuthPath<TOAS>;
export type OASModel<TOAS extends OpenAPIDocument, TName extends TOAS extends {
    components: {
        schemas: Record<string, JSONSchema>;
    };
} ? keyof TOAS['components']['schemas'] : TOAS extends {
    definitions: Record<string, JSONSchema>;
} ? keyof TOAS['definitions'] : never> = TOAS extends {
    components: {
        schemas: {
            [TModelName in TName]: JSONSchema;
        };
    };
} ? FromSchema<TOAS['components']['schemas'][TName]> : TOAS extends {
    definitions: {
        [TModelName in TName]: JSONSchema;
    };
} ? FromSchema<TOAS['definitions'][TName]> : never;
export type FixJSONSchema<T> = RemoveExclusiveMinimumAndMaximum<FixAdditionalPropertiesForAllOf<FixMissingAdditionalProperties<FixMissingTypeObject<FixExtraRequiredFields<T>>>>>;
type FixAdditionalPropertiesForAllOf<T> = T extends {
    allOf: any[];
} ? Omit<T, 'allOf'> & {
    allOf: Call<Tuples.Map<Objects.Omit<'additionalProperties'>>, T['allOf']>;
} : T;
type LooksLikeSchemaObject<T> = T extends {
    type: JSONSchema7TypeName;
} | {
    $ref: string;
} | {
    enum: any;
} | {
    const: any;
} | {
    oneOf: any;
} | {
    anyOf: any;
} | {
    allOf: any;
} | {
    not: any;
} | {
    additionalProperties: any;
} | {
    items: any;
} ? true : false;
type FixMissingTypeObject<T> = T extends {
    type: any;
    properties: any;
} ? T : T extends {
    properties: infer TProps;
} ? LooksLikeSchemaObject<TProps> extends true ? T : T & {
    type: 'object';
} : T;
type FixMissingAdditionalProperties<T> = T extends {
    type: 'object';
    properties: any;
} ? Omit<T, 'additionalProperties'> & {
    additionalProperties: false;
} : T extends {
    type: readonly (infer TType)[];
    properties: any;
} ? 'object' extends TType ? Omit<T, 'additionalProperties'> & {
    additionalProperties: false;
} : T : T;
type FixExtraRequiredFields<T> = T extends {
    properties: Record<string, any>;
    required: string[];
} ? Omit<T, 'required'> & {
    required: Call<Tuples.Filter<B.Extends<keyof T['properties']>>, T['required']>;
} : T;
type RemoveExclusiveMinimumAndMaximum<T> = T extends {
    exclusiveMinimum?: boolean;
    exclusiveMaximum?: boolean;
} ? Omit<T, 'exclusiveMinimum' | 'exclusiveMaximum'> : T;
export type OASRequestParams<TOAS extends OpenAPIDocument, TPath extends keyof OASPathMap<TOAS>, TMethod extends keyof OASMethodMap<TOAS, TPath>, TAuthParamsRequired extends boolean = true> = (OASMethodMap<TOAS, TPath>[TMethod] extends {
    requestBody: {
        content: {
            'application/json': {
                schema: JSONSchema;
            };
        };
    };
} ? OASMethodMap<TOAS, TPath>[TMethod]['requestBody'] extends {
    required: true;
} ? {
    /**
     * The request body in JSON is required for this request.
     *
     * The value of `json` will be stringified and sent as the request body with `Content-Type: application/json`.
     */
    json: FromSchema<OASMethodMap<TOAS, TPath>[TMethod]['requestBody']['content']['application/json']['schema']>;
} : {
    /**
     * The request body in JSON is optional for this request.
     *
     * The value of `json` will be stringified and sent as the request body with `Content-Type: application/json`.
     */
    json?: FromSchema<OASMethodMap<TOAS, TPath>[TMethod]['requestBody']['content']['application/json']['schema']>;
} : OASMethodMap<TOAS, TPath>[TMethod] extends {
    requestBody: {
        content: {
            'multipart/form-data': {
                schema: JSONSchema;
            };
        };
    };
} ? OASMethodMap<TOAS, TPath>[TMethod]['requestBody'] extends {
    required: true;
} ? {
    /**
     * The request body in multipart/form-data is required for this request.
     *
     * The value of `formData` will be sent as the request body with `Content-Type: multipart/form-data`.
     */
    formData: FromSchema<OASMethodMap<TOAS, TPath>[TMethod]['requestBody']['content']['multipart/form-data']['schema']>;
} : {
    /**
     * The request body in multipart/form-data is optional for this request.
     *
     * The value of `formData` will be sent as the request body with `Content-Type: multipart/form-data`.
     */
    formData?: FromSchema<OASMethodMap<TOAS, TPath>[TMethod]['requestBody']['content']['multipart/form-data']['schema']>;
} : OASMethodMap<TOAS, TPath>[TMethod] extends {
    requestBody: {
        content: {
            'application/x-www-form-urlencoded': {
                schema: JSONSchema;
            };
        };
    };
} ? OASMethodMap<TOAS, TPath>[TMethod]['requestBody'] extends {
    required: true;
} ? {
    /**
     * The request body in application/x-www-form-urlencoded is required for this request.
     *
     * The value of `formUrlEncoded` will be sent as the request body with `Content-Type: application/x-www-form-urlencoded`.
     */
    formUrlEncoded: FromSchema<OASMethodMap<TOAS, TPath>[TMethod]['requestBody']['content']['application/x-www-form-urlencoded']['schema']>;
} : {
    /**
     * The request body in application/x-www-form-urlencoded is optional for this request.
     *
     * The value of `formUrlEncoded` will be sent as the request body with `Content-Type: application/x-www-form-urlencoded`.
     */
    formUrlEncoded?: FromSchema<OASMethodMap<TOAS, TPath>[TMethod]['requestBody']['content']['application/x-www-form-urlencoded']['schema']>;
} : {}) & (OASMethodMap<TOAS, TPath>[TMethod] extends {
    parameters: {
        name: string;
        in: string;
    }[];
} ? OASParamMap<OASMethodMap<TOAS, TPath>[TMethod]['parameters']> : {}) & (TPath extends `${string}{${string}}${string}` ? {
    /**
     * Parameters defined in the path are required for this request.
     *
     * The value of `params` will be used to replace the path parameters.
     *
     * For example if path is `/todos/{id}` and `params` is `{ id: '1' }`, the path will be `/todos/1`
     */
    params: Record<ExtractPathParamsWithBrackets<TPath>, string | number | bigint | boolean>;
} : {}) & (TPath extends `${string}:${string}${string}` ? {
    /**
     * Parameters defined in the path are required for this request.
     *
     * The value of `params` will be used to replace the path parameters.
     *
     * For example if path is `/todos/:id` and `params` is `{ id: '1' }`, the path will be `/todos/1`.
     */
    params: Record<ExtractPathParamsWithPattern<TPath>, string | number | bigint | boolean>;
} : {}) & (TAuthParamsRequired extends true ? OASSecurityParamsBySecurityRef<TOAS, OASMethodMap<TOAS, TPath>[TMethod]> : DeepPartial<OASSecurityParamsBySecurityRef<TOAS, OASMethodMap<TOAS, TPath>[TMethod]>>) & (TAuthParamsRequired extends true ? OASSecurityParamsBySecurityRef<TOAS, TOAS> : DeepPartial<OASSecurityParamsBySecurityRef<TOAS, TOAS>>);
type DeepPartial<T> = T extends Record<string, any> ? {
    [K in keyof T]?: DeepPartial<T[K]>;
} : T;
export type OASInput<TOAS extends OpenAPIDocument, TPath extends keyof OASPathMap<TOAS>, TMethod extends keyof OASMethodMap<TOAS, TPath>, TRequestType extends keyof OASRequestParams<TOAS, TPath, TMethod>> = OASRequestParams<TOAS, TPath, TMethod>[TRequestType];
export type OASOutput<TOAS extends OpenAPIDocument, TPath extends keyof OASPathMap<TOAS>, TMethod extends keyof OASMethodMap<TOAS, TPath>, TStatusCode extends keyof OASStatusMap<TOAS, TPath, TMethod> = 200> = FromSchema<OASJSONResponseSchema<TOAS, TPath, TMethod, TStatusCode>>;
export type OASComponentSchema<TOAS extends OpenAPIDocument, TName extends string> = TOAS extends {
    components: {
        schemas: {
            [TModelName in TName]: JSONSchema;
        };
    };
} ? FromSchema<TOAS['components']['schemas'][TName]> : never;
export interface ClientOptions {
    /**
     * The base URL of the API
     */
    endpoint?: string;
    /**
     * WHATWG compatible fetch implementation
     *
     * @see https://the-guild.dev/openapi/fets/client/client-configuration#customizing-the-fetch-function
     */
    fetchFn?: ClientFetchFn;
    /**
     * Plugins to extend the client functionality
     *
     * @see https://the-guild.dev/openapi/fets/client/plugins
     */
    plugins?: ClientPlugin[];
    /**
     * Global parameters
     */
    globalParams?: ClientRequestParams;
}
type ServerVariableType<TVarName extends string, TVariables> = TVariables extends Record<string, unknown> ? TVarName extends keyof TVariables ? TVariables[TVarName] extends {
    enum: readonly (infer TEnum extends string)[];
} ? TEnum : string : string : string;
type ResolveServerUrl<TUrl extends string, TVariables> = TUrl extends `${infer Before}{${infer VarName}}${infer After}` ? `${Before}${ServerVariableType<VarName, TVariables>}${ResolveServerUrl<After, TVariables>}` : TUrl;
export type ClientOptionsWithStrictEndpoint<TOAS extends OpenAPIDocument> = Omit<ClientOptions, 'endpoint'> & (TOAS extends {
    servers: (infer TEndpoint extends string)[];
} ? {
    /**
     * The base URL of the API defined in the OAS document.
     *
     * @see https://swagger.io/docs/specification/api-host-and-base-path/
     */
    endpoint: TEndpoint;
} : TOAS extends {
    servers: {
        url: infer TEndpoint extends string;
        variables?: infer TVariables;
    }[];
} ? {
    /**
     * The base URL of the API defined in the OAS document.
     *
     * @see https://swagger.io/docs/specification/api-host-and-base-path/
     */
    endpoint: ResolveServerUrl<TEndpoint, TVariables>;
} : TOAS extends {
    host: infer THost extends string;
    basePath: infer TBasePath extends string;
    schemes: (infer TProtocol extends string)[];
} ? {
    /**
     * REST APIs have a base URL to which the endpoint paths are appended. The base URL is defined by `schemes`, `host` and `basePath` on the root level of the API specification.
     *
     * @see https://swagger.io/docs/specification/2-0/api-host-and-base-path/
     */
    endpoint: `${TProtocol}://${THost}${TBasePath}`;
} : {
    endpoint?: string;
});
export interface ClientRequestParams extends ClientRequestInit {
    json?: any;
    formData?: Record<string, string | Blob>;
    formUrlEncoded?: Record<string, string | string[]>;
    params?: Record<string, string>;
    query?: any;
    headers?: Record<string, string>;
}
export type ClientRequestInit = Omit<RequestInit, 'body' | 'method' | 'headers'>;
export type ClientMethod = (requestParams?: ClientRequestParams) => ClientTypedResponsePromise<any>;
export interface ClientPlugin {
    onRequestInit?: OnRequestInitHook;
    onFetch?: OnFetchHook;
    onResponse?: OnResponseHook;
}
export type OnRequestInitHook = (payload: ClientOnRequestInitPayload) => Promise<void> | void;
export type OnFetchHook = (payload: ClientOnFetchHookPayload) => Promise<void> | void;
export type OnResponseHook = (payload: ClientOnResponseHookPayload) => Promise<void> | void;
export interface ClientOnRequestInitPayload {
    path: string;
    method: HTTPMethod;
    requestParams: ClientRequestParams;
    requestInit: RequestInit;
    endResponse(response: Response): void;
}
export type ClientFetchFn = (input: string, init?: RequestInit) => Promise<Response> | Response;
export interface ClientOnFetchHookPayload {
    url: string;
    init: RequestInit;
    fetchFn: ClientFetchFn;
    setFetchFn(fetchFn: ClientFetchFn): void;
}
export interface ClientOnResponseHookPayload {
    path: string;
    method: HTTPMethod;
    requestParams: ClientRequestParams;
    requestInit: RequestInit;
    response: Response;
}
export type BasicAuthParams<TSecurityScheme> = TSecurityScheme extends {
    type: 'http';
    scheme: 'basic';
} | {
    type: 'basic';
} ? {
    headers: {
        /**
         * `Authorization` header is required for basic authentication
         * @see https://en.wikipedia.org/wiki/Basic_access_authentication
         *
         * It contains the word `Basic` followed by a space and a base64-encoded string `username:password`
         *
         * @example
         * ```
         * Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
         * ```
         */
        Authorization: `Basic ${string}`;
    };
} : {};
export type BearerAuthParams<TSecurityScheme> = TSecurityScheme extends {
    type: 'http';
    scheme: 'bearer';
} | {
    type: 'bearer';
} ? {
    /**
     * `Authorization` header is required for bearer authentication
     * @see https://swagger.io/docs/specification/authentication/bearer-authentication/
     */
    headers: {
        /**
         * It contains the word `Bearer` followed by a space and the token
         *
         * @example
         * ```
         * Authorization: Bearer {token}
         * ```
         */
        Authorization: `Bearer ${string}`;
    };
} : {};
export type ApiKeyAuthParams<TSecurityScheme> = TSecurityScheme extends {
    type: 'apiKey';
    in: 'header';
    name: infer TApiKeyHeaderName;
} ? {
    headers: {
        [THeaderName in TApiKeyHeaderName extends string ? TApiKeyHeaderName : never]: string;
    };
} : TSecurityScheme extends {
    type: 'apiKey';
    in: 'query';
    name: infer TApiKeyQueryName;
} ? {
    query: {
        [TQueryName in TApiKeyQueryName extends string ? TApiKeyQueryName : never]: string;
    };
} : {};
export type SecuritySchemeName<T extends {
    security: {
        [key: string]: any;
    }[];
}> = Call<Tuples.Map<Objects.Keys>, T['security']>[number];
export type OASSecurityParams<TSecurityScheme> = BasicAuthParams<TSecurityScheme> & BearerAuthParams<TSecurityScheme> & ApiKeyAuthParams<TSecurityScheme> & OAuth2AuthParams<TSecurityScheme>;
export type OASSecurityParamsBySecurityRef<TOAS, TSecurityObj> = TSecurityObj extends {
    security: {
        [key: string]: any;
    }[];
} ? TOAS extends {
    components: {
        securitySchemes: {
            [TSecuritySchemeNameKey in SecuritySchemeName<TSecurityObj> extends string ? SecuritySchemeName<TSecurityObj> : never]: infer TSecurityScheme;
        };
    };
} | {
    securityDefinitions: {
        [TSecuritySchemeNameKey in SecuritySchemeName<TSecurityObj> extends string ? SecuritySchemeName<TSecurityObj> : never]: infer TSecurityScheme;
    };
} ? OASSecurityParams<TSecurityScheme> : SecuritySchemeName<TSecurityObj> extends `basic${string}` ? BasicAuthParams<{
    type: 'http';
    scheme: 'basic';
}> : SecuritySchemeName<TSecurityObj> extends `bearer${string}` ? BearerAuthParams<{
    type: 'http';
    scheme: 'bearer';
}> : SecuritySchemeName<TSecurityObj> extends `oauth${string}` ? OAuth2AuthParams<{
    type: 'oauth2';
}> : {} : {};
export {};
