// Copyright 2017 - 2026 will Farrell, Luciano Mammino, and Middy contributors.
// SPDX-License-Identifier: MIT
import type {
	Context as LambdaContext,
	Handler as LambdaHandler,
} from "aws-lambda";
import type { DurableContext as LambdaContextDurable } from "@aws/durable-execution-sdk-js";

declare type PluginHook = () => void;
declare type PluginHookWithMiddlewareName = (middlewareName: string) => void;
declare type PluginHookPromise = (
	request: Request,
) => Promise<unknown> | unknown;
export type PluginExecutionMode = () => void;
export declare const executionModeStandard: PluginExecutionMode;
export declare const executionModeDurableContext: PluginExecutionMode;
export declare const executionModeStreamifyResponse: PluginExecutionMode;

interface PluginObject {
	internal?: Record<string, unknown>;
	beforePrefetch?: PluginHook;
	requestStart?: PluginHook;
	beforeMiddleware?: PluginHookWithMiddlewareName;
	afterMiddleware?: PluginHookWithMiddlewareName;
	beforeHandler?: PluginHook;
	timeoutEarlyInMillis?: number;
	timeoutEarlyResponse?: PluginHook;
	afterHandler?: PluginHook;
	requestEnd?: PluginHookPromise;
	executionMode?: PluginExecutionMode;
}

export interface Request<
	TEvent = unknown,
	TResult = any,
	TErr = Error,
	TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
	TInternal extends Record<string, unknown> = {},
> {
	event: TEvent;
	context: TContext;
	response: TResult | null | undefined;
	earlyResponse?: TResult | null | undefined;
	error: TErr | null | undefined;
	internal: TInternal;
}

declare type MiddlewareFn<
	TEvent = unknown,
	TResult = any,
	TErr = Error,
	TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
	TInternal extends Record<string, unknown> = {},
> = (request: Request<TEvent, TResult, TErr, TContext, TInternal>) => any;

export interface MiddlewareObj<
	TEvent = unknown,
	TResult = any,
	TErr = Error,
	TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
	TInternal extends Record<string, unknown> = {},
> {
	before?: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>;
	after?: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>;
	onError?: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>;
	name?: string;
}

export interface MiddyHandlerObject {
	/**
	 * An abort signal that will be canceled just before the lambda times out.
	 * @see timeoutEarlyInMillis
	 */
	signal: AbortSignal;
}

// The AWS provided Handler type uses void | Promise<TResult> so we have no choice but to follow and suppress the linter warning
type MiddyInputHandler<
	TEvent,
	TResult,
	TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
> = (
	event: TEvent,
	context: TContext,
	opts: MiddyHandlerObject,
) => undefined | Promise<TResult> | TResult;
type MiddyInputPromiseHandler<
	TEvent,
	TResult,
	TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
> = (event: TEvent, context: TContext) => Promise<TResult>;

export interface MiddyfiedHandler<
	TEvent = unknown,
	TResult = any,
	TErr = Error,
	TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
	TInternal extends Record<string, unknown> = {},
> extends MiddyInputHandler<TEvent, TResult, TContext>,
		MiddyInputPromiseHandler<TEvent, TResult, TContext> {
	use: UseFn<TEvent, TResult, TErr, TContext, TInternal>;
	before: AttachMiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>;
	after: AttachMiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>;
	onError: AttachMiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>;
	handler: <
		TInputHandlerEventProps = TEvent,
		TInputHandlerResultProps = TResult,
	>(
		handler: MiddlewareHandler<
			LambdaHandler<TInputHandlerEventProps, TInputHandlerResultProps>,
			TContext,
			TResult,
			TEvent
		>,
	) => MiddyfiedHandler<
		TInputHandlerEventProps,
		TInputHandlerResultProps,
		TErr,
		TContext,
		TInternal
	>;
}

declare type AttachMiddlewareFn<
	TEvent = unknown,
	TResult = any,
	TErr = Error,
	TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
	TInternal extends Record<string, unknown> = {},
> = (
	middleware: MiddlewareFn<TEvent, TResult, TErr, TContext, TInternal>,
) => MiddyfiedHandler<TEvent, TResult, TErr, TContext, TInternal>;

declare type AttachMiddlewareObj<
	TEvent = unknown,
	TResult = any,
	TErr = Error,
	TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
	TInternal extends Record<string, unknown> = {},
> = (
	middleware: MiddlewareObj<TEvent, TResult, TErr, TContext, TInternal>,
) => MiddyfiedHandler<TEvent, TResult, TErr, TContext, TInternal>;

declare type UseFn<
	TEvent = unknown,
	TResult = any,
	TErr = Error,
	TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
	TInternal extends Record<string, unknown> = {},
> = <
	TMiddlewares extends
		| MiddlewareObj<any, any, Error, any, any>
		| MiddlewareObj<any, any, Error, any, any>[],
>(
	middlewares: TMiddlewares,
) => TMiddlewares extends MiddlewareObj<
	infer TMiddlewareEvent,
	any,
	Error,
	infer TMiddlewareContext,
	infer TMiddlewareInternal
>
	? MiddyfiedHandler<
			TMiddlewareEvent & TEvent,
			TResult,
			TErr,
			TMiddlewareContext & TContext,
			TMiddlewareInternal & TInternal
		>
	: TMiddlewares extends MiddlewareObj<
				infer TMiddlewareEvent,
				any,
				Error,
				infer TMiddlewareContext,
				infer TMiddlewareInternal
			>[]
		? MiddyfiedHandler<
				TEvent & TMiddlewareEvent,
				TResult,
				TErr,
				TContext & TMiddlewareContext,
				TInternal & TMiddlewareInternal
			>
		: never;

declare type MiddlewareHandler<
	THandler extends LambdaHandler<any, any>,
	TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
	TResult = any,
	TEvent = unknown,
> =
	THandler extends LambdaHandler<TEvent, TResult> // always true
		? MiddyInputHandler<TEvent, TResult, TContext>
		: never;

/**
 * Middy factory function. Use it to wrap your existing handler to enable middlewares on it.
 * @param handler your original AWS Lambda function
 * @param plugin wraps around each middleware and handler to add custom lifecycle behaviours (e.g. to profile performance)
 */
declare function middy<
	TEvent = unknown,
	TResult = any,
	TErr = Error,
	TContext extends LambdaContext | LambdaContextDurable = LambdaContext,
	TInternal extends Record<string, unknown> = {},
>(
	handler?:
		| LambdaHandler<TEvent, TResult>
		| MiddlewareHandler<
				LambdaHandler<TEvent, TResult>,
				TContext,
				TResult,
				TEvent
		  >
		| PluginObject,
	plugin?: PluginObject,
): MiddyfiedHandler<TEvent, TResult, TErr, TContext, TInternal>;

declare namespace middy {
	export type {
		Request,
		PluginHook,
		PluginHookWithMiddlewareName,
		PluginObject,
		MiddlewareFn,
		MiddlewareObj,
		MiddyfiedHandler,
	};
}

export default middy;
