import type { EncodingAction } from './squared';

import type { IRequest } from './index';
import type { BinaryAction } from './asset';
import type { HeadersAction, HttpAgentOptions, HttpProtocolVersion, HttpRequestClient, InternetProtocolVersion } from './http';
import type { IncludeAction } from './module';
import type { ErrorCode } from './node';
import type { HttpHostSettings, RequestModule } from './settings';

import type { IncomingHttpHeaders, OutgoingHttpHeaders } from 'node:http';
import type { Readable, Writable } from 'node:stream';

interface KeepAliveAction {
    keepAlive?: boolean;
    /** @deprecated */
    agentTimeout?: number;
    agentOptions?: HttpAgentOptions;
}

interface SilentAction {
    silent?: boolean;
}

interface CopyAction extends HeadersAction {
    pathname?: string | URL;
    signal?: AbortSignal;
}

interface ProtocolAction {
    httpVersion?: HttpProtocolVersion;
    ipVersion?: InternetProtocolVersion;
}

export interface IHttpHost {
    localhost: boolean;
    hasProtocol(version: HttpProtocolVersion): Promise<number>;
    upgrade(version: HttpProtocolVersion, altSvc: string | undefined): void;
    success(version: HttpProtocolVersion, status?: boolean): number;
    failed(version: HttpProtocolVersion, status?: boolean): number;
    error(version: HttpProtocolVersion, status?: boolean): number;
    /** @deprecated altSvc.did */
    didAltSvc(version: HttpProtocolVersion): boolean;
    /** @deprecated altSvc.next */
    nextAltSvc(): boolean;
    /** @deprecated altSvc.close */
    closeAltSvc(error?: boolean): boolean;
    /** @deprecated altSvc.clear */
    clearAltSvc(version?: HttpProtocolVersion): void;
    /** @deprecated altSvc.flag */
    flagAltSvc(version: HttpProtocolVersion, value: number): void;
    reset(): void;
    v1(): boolean;
    v2(): boolean;
    get altSvc(): IHttpHostAltSvc;
    set version(value);
    get version(): HttpProtocolVersion;
    get protocol(): string;
    get secure(): boolean;
    get hostname(): string;
    get port(): string;
    get origin(): string;
    get streamSize(): number;
}

export interface HttpHostConstructor {
    normalizeOrigin(value: string): string;
    formatBasicAuth(url: URL): string;
    getBasicAuth(url: URL): OutgoingHttpHeaders | undefined;
    defineLocalHost(values: string[]): void;
    defineProtocolNegotiation(data: ObjectMap<string[]>): void;
    defineHostConfig(settings: HttpHostSettings): void;
    readonly prototype: IHttpHost;
    new(url: URL, httpVersion?: HttpProtocolVersion): IHttpHost;
}

export interface IHttpHostAltSvc {
    did(version: HttpProtocolVersion): boolean;
    next(): boolean;
    close(error?: boolean): boolean;
    clear(version?: HttpProtocolVersion): void;
    flag(version: HttpProtocolVersion, value: number): void;
    valid(hostname: string, port: string, version: number): boolean;
    set available(value: unknown[]);
    get errors(): AltSvcInfo[];
    get hostname(): string | undefined;
    get port(): string | undefined;
    get origin(): string | undefined;
}

export interface HttpHostAltSvcConstructor {
    readonly prototype: IHttpHostAltSvc;
    new(host: IHttpHost, versionData: AltSvcData): IHttpHostAltSvc;
}

export interface IHttpAdapter<T extends OpenOptions = OpenOptions> {
    instance: IRequest;
    state: ControllerState;
    uri: string | URL;
    contentLength: number;
    retries: number;
    redirects: number;
    closed: boolean;
    aborted: boolean;
    timeout: NodeJS.Timeout | null;
    dataTime: bigint | null;
    delayTime: number | bigint | undefined;
    opts: HostConfig & T;
    client: HttpRequestClient;
    resolve: (value: unknown) => void;
    reject: (reason?: any) => void;
    readonly startTime: bigint;
    start(): Promise<object | Bufferable | null>;
    init(): void;
    setOpts(uri?: string | URL): void;
    setWriteStream(): void;
    retryDownload(downgrade: boolean, message: string): void;
    acceptResponse(headers: IncomingHttpHeaders): void;
    updateProgress(dataLength: number, contentLength: number): void;
    redirectResponse(statusCode: number, location?: string): void;
    endResponse(result: unknown, dataLength: number, logging?: boolean): void;
    abortResponse(): void;
    errorResponse(err: unknown): void;
    retryResponse(statusCode: number, retryAfter?: string): void;
    isRetry(value: number): boolean;
    retryTimeout(): void;
    terminate(err: unknown): void;
    sendWarning(message: string): void;
    formatStatus(value: number | string, hint?: string): string;
    close(retry?: boolean): void;
    cleanup(): void;
    set abortController(value);
    get abortController(): AbortController | null;
    set outStream(value);
    get outStream(): Writable | null;
    get destroyed(): boolean;
    get host(): IHttpHost;
    get httpVersion(): HttpProtocolVersion;
    get pipeTo(): string | Writable | undefined;
    get silent(): boolean;
    get retryLimit(): number;
    get retryWait(): number;
    get retryAfter(): number;
    get redirectLimit(): number;
}

export interface HttpAdapterConstructor<T extends OpenOptions = OpenOptions> {
    constructorOf<U extends HttpAdapterConstructor>(value: unknown): value is U;
    isUnsupported(value: number): boolean;
    isDowngrade(err: unknown): err is Error;
    wasAborted(err: unknown): err is Error;
    isConnectionError(err: unknown): err is Required<ErrorCode<string>>;
    defineHostConfig(settings: RequestModule): void;
    readonly prototype: IHttpAdapter<T>;
    new(instance: IRequest, state: ControllerState, uri: string | URL, options: T): IHttpAdapter<T>;
}

export interface OpenOptions extends KeepAliveAction, SilentAction, EncodingAction {
    host?: IHttpHost;
    url?: URL;
    base?: boolean;
    socketPath?: string;
    httpVersion?: HttpProtocolVersion;
    method?: HttpMethod | Lowercase<HttpMethod>;
    search?: StringMap;
    followRedirect?: boolean;
    expectContinue?: boolean;
    expectTimeout?: number;
    maxBufferSize?: number | string;
    maxConcurrentStreams?: number;
    format?: BufferFormat | { out?: BufferFormat; parser?: PlainObject };
    headers?: OutgoingHttpHeaders | Headers;
    signal?: AbortSignal;
    timeout?: number;
    pipeTo?: string | Writable;
    postData?: unknown;
    connected?: (headers: IncomingHttpHeaders) => boolean | void;
    trailers?: (headers: IncomingHttpHeaders) => void;
    statusMessage?: string;
    progressId?: number | string;
    outFormat?: { out: BufferFormat; parser?: PlainObject };
    outFilename?: string | null;
    outContentType?: string;
    outHeaders?: IncomingHttpHeaders | null;
    outStream?: Writable | null;
    outAbort?: AbortController | null;
}

export interface PutOptions extends OpenOptions {
    contentType?: string;
    dataEncoding?: BufferEncoding;
}

export interface PostOptions extends PutOptions {
    formData?: FormDataPart | File | PostFileParts;
}

export interface FormDataPart {
    name?: string;
    data?: Buffer | Readable | string;
    value?: unknown;
    contentType?: string;
    filename?: string;
}

export interface Aria2Options extends BinaryAction, SilentAction, CopyAction {}

export interface RcloneOptions extends Aria2Options {
    command?: "copy" | "copyto" | "copyurl";
    update?: boolean;
}

export interface HostConfig extends OpenOptions {
    host: IHttpHost;
    url: URL;
}

export interface ProxySettings extends KeepAliveAction, IncludeAction {
    host: URL;
}

export interface RequestInit extends ProtocolAction {
    headers?: unknown;
    readTimeout?: number;
}

export interface ClientConfig {
    timeout?: number;
    connectTimeout?: number;
    redirectLimit?: number;
    retryWait?: number;
    retryAfter?: number;
    retryLimit?: number;
}

export interface ApplyOptions extends ProtocolAction, PlainObject {
    client?: ClientConfig;
    readExpect?: ReadExpectType;
    acceptEncoding?: boolean;
    keepAlive?: boolean;
}

export interface ControllerState {
    readonly verbose: boolean;
    readonly log: boolean;
    readonly singleton: boolean;
    readonly config: Required<ClientConfig>;
}

export interface AltSvcInfo {
    hostname: string;
    port: string;
    version: number;
}

export interface AltSvcLocation extends Partial<AltSvcInfo> {
    origin?: string;
    timeout?: NodeJS.Timeout | null;
}

export interface AltSvcAvailability extends AltSvcInfo {
    expires: number;
    persist: boolean;
}

export interface AltSvcData {
    success: number;
    failed: number;
    errors: number;
    alpn: number;
    status: number;
}

export type HttpMethod = "GET" | "POST" | "PUT" | "HEAD" | "DELETE";
export type BufferFormat = "json" | "yaml" | "json5" | "xml" | "toml";
export type ReadExpectType = "always" | "string" | "none";
export type PostFileParts = (File | FormDataPart)[];
export type DataEncodedResult<T extends { encoding?: BufferEncoding }> = T extends { encoding: BufferEncoding } ? string : Bufferable | null;
export type DataObjectResult<T extends { format?: unknown; encoding?: BufferEncoding }> = T extends { format: string | PlainObject } ? object | null : DataEncodedResult<T>;
export type StatusOnCallback = (code: number, headers: IncomingHttpHeaders, url?: URL) => boolean | void;
export type HeadersOnCallback = (headers: IncomingHttpHeaders, url?: URL) => boolean | void;