import express, { Request, Response, RouterOptions } from 'express';
import { ZodObject, ZodAny, z } from 'zod';
import { ZodiosEndpointDefinitions, Method, ZodiosPathsByMethod, ZodiosPathParamsByPath, ZodiosBodyByPath, ZodiosQueryParamsByPath, ZodiosResponseByPath, ZodiosEndpointDefinition, ZodiosErrorByPath } from '@zodios/core';
import { IfEquals, Narrow } from '@zodios/core/lib/utils.types';
import http from 'http';

type ZodiosSuccessCodes = 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226;
type WithZodiosContext<T, Context extends ZodObject<any>> = Context extends ZodAny ? T : T & z.infer<Context>;
interface ZodiosRequestHandler<Api extends ZodiosEndpointDefinitions, Context extends ZodObject<any>, M extends Method, Path extends ZodiosPathsByMethod<Api, M>, ReqPath = ZodiosPathParamsByPath<Api, M, Path, false>, ReqBody = ZodiosBodyByPath<Api, M, Path, false>, ReqQuery = ZodiosQueryParamsByPath<Api, M, Path, false>, Res = ZodiosResponseByPath<Api, M, Path, false>> {
    (req: WithZodiosContext<express.Request<ReqPath, Res, ReqBody, ReqQuery>, Context>, res: Omit<express.Response<Res>, "status"> & {
        status<StatusCode extends number, API extends ZodiosEndpointDefinition[] = Api, METHOD extends Method = M, PATH extends ZodiosPathsByMethod<Api, M> = Path>(status: StatusCode): StatusCode extends ZodiosSuccessCodes ? express.Response<Res> : express.Response<ZodiosErrorByPath<API, METHOD, PATH, StatusCode, false>>;
    }, next: express.NextFunction): void;
}
type ZodiosHandler<Router, Context extends ZodObject<any>, Api extends ZodiosEndpointDefinitions, M extends Method> = <Path extends ZodiosPathsByMethod<Api, M>>(path: Path, ...handlers: Array<ZodiosRequestHandler<Api, Context, M, Path>>) => Router;
type ZodiosRouterContextRequestHandler<Context extends ZodObject<any>> = (req: WithZodiosContext<express.Request, Context>, res: express.Response, next: express.NextFunction) => void;
type ZodiosRouterContextErrorHandler<Context extends ZodObject<any>> = (error: any, req: WithZodiosContext<express.Request, Context>, res: express.Response, next: express.NextFunction) => void;
type ZodiosRouterValidationErrorHandler<Context extends ZodObject<any>> = (error: {
    context: string;
    error: z.ZodIssue[];
}, req: WithZodiosContext<express.Request, Context>, res: express.Response, next: express.NextFunction) => void;
interface ZodiosUse<Context extends ZodObject<any>> {
    use(...handlers: Array<ZodiosRouterContextRequestHandler<Context>>): this;
    use(handlers: Array<ZodiosRouterContextRequestHandler<Context>>): this;
    use(errorHandler: ZodiosRouterContextErrorHandler<Context>): this;
    use(path: string, ...handlers: Array<ZodiosRouterContextRequestHandler<Context>>): this;
    use(path: string, handlers: Array<ZodiosRouterContextRequestHandler<Context>>): this;
}
interface ZodiosHandlers<Api extends ZodiosEndpointDefinitions, Context extends ZodObject<any>> extends ZodiosUse<Context> {
    get: ZodiosHandler<this, Context, Api, "get">;
    post: ZodiosHandler<this, Context, Api, "post">;
    put: ZodiosHandler<this, Context, Api, "put">;
    patch: ZodiosHandler<this, Context, Api, "patch">;
    delete: ZodiosHandler<this, Context, Api, "delete">;
    options: ZodiosHandler<this, Context, Api, "options">;
    head: ZodiosHandler<this, Context, Api, "head">;
}
interface ZodiosValidationOptions {
    /**
     * validate request parameters - default is true
     */
    validate?: boolean;
    /**
     * transform request parameters - default is false
     */
    transform?: boolean;
}
interface ZodiosAppOptions<Context extends ZodObject<any>> extends ZodiosValidationOptions {
    /**
     * express app intance - default is express()
     */
    express?: ReturnType<typeof express>;
    /**
     * enable express json body parser - default is true
     */
    enableJsonBodyParser?: boolean;
    context?: Context;
    validationErrorHandler?: ZodiosRouterValidationErrorHandler<Context>;
}
interface ZodiosRouterOptions<Context extends ZodObject<any>> extends ZodiosValidationOptions {
    /**
     * express router instance - default is express.Router
     */
    router?: ReturnType<typeof express.Router>;
    context?: Context;
    validationErrorHandler?: ZodiosRouterValidationErrorHandler<Context>;
}
interface ZodiosUnknownApp<Context extends ZodObject<any>> extends Omit<ReturnType<typeof express>, "use">, ZodiosUse<Context> {
    (req: Request | http.IncomingMessage, res: Response | http.ServerResponse): any;
}
interface ZodiosApiApp<Api extends ZodiosEndpointDefinition[], Context extends ZodObject<any>> extends Omit<ReturnType<typeof express>, Method | "use">, ZodiosHandlers<Api, Context> {
    (req: Request | http.IncomingMessage, res: Response | http.ServerResponse): any;
}
type ZodiosApp<Api extends ZodiosEndpointDefinitions, Context extends ZodObject<any>> = IfEquals<Api, any, ZodiosUnknownApp<Context>, ZodiosApiApp<Api, Context>>;
type ZodiosRouter<Api extends ZodiosEndpointDefinitions, Context extends ZodObject<any>> = IfEquals<Api, any, Omit<ReturnType<typeof express.Router>, "use"> & ZodiosUse<Context> & ZodiosRouterContextRequestHandler<Context>, Omit<ReturnType<typeof express.Router>, Method | "use"> & ZodiosHandlers<Api, Context> & ZodiosRouterContextRequestHandler<Context>>;

/**
 * create a zodios app based on the given api and express
 * @param api - api definition
 * @param options - options to configure the app
 * @returns
 */
declare function zodiosApp<Api extends ZodiosEndpointDefinitions = any, Context extends ZodObject<any> = ZodObject<any>>(api?: Narrow<Api>, options?: ZodiosAppOptions<Context>): ZodiosApp<Api, Context>;
/**
 * create a zodios router based on the given api and express router
 * @param api - api definition
 * @param options - options to configure the router
 * @returns
 */
declare function zodiosRouter<Api extends ZodiosEndpointDefinitions, Context extends ZodObject<any> = ZodObject<any>>(api: Narrow<Api>, options?: RouterOptions & ZodiosRouterOptions<Context>): ZodiosRouter<Api, Context>;
/**
 * create a zodios app for nextjs
 * @param options - options to configure the app
 * @returns - a zodios app
 */
declare function zodiosNextApp<Api extends ZodiosEndpointDefinitions = any, Context extends ZodObject<any> = ZodObject<any>>(api?: Narrow<Api>, options?: ZodiosAppOptions<Context>): ZodiosApp<Api, Context>;
declare class ZodiosContext<Context extends ZodObject<any>> {
    context?: Context | undefined;
    constructor(context?: Context | undefined);
    app<Api extends ZodiosEndpointDefinitions = any>(api?: Narrow<Api>, options?: ZodiosAppOptions<Context>): ZodiosApp<Api, Context>;
    nextApp<Api extends ZodiosEndpointDefinitions = any>(api?: Narrow<Api>, options?: ZodiosAppOptions<Context>): ZodiosApp<Api, Context>;
    router<Api extends ZodiosEndpointDefinitions>(api: Narrow<Api>, options?: RouterOptions & ZodiosRouterOptions<Context>): ZodiosRouter<Api, Context>;
}
declare function zodiosContext<Context extends ZodObject<any> = ZodObject<any>>(context?: Context): ZodiosContext<Context>;

type MapPrefixPath<T extends readonly unknown[], PrefixValue extends string, ACC extends unknown[] = []> = T extends readonly [infer Head, ...infer Tail] ? MapPrefixPath<Tail, PrefixValue, [
    ...ACC,
    {
        [K in keyof Head]: K extends "path" ? Head[K] extends string ? `${PrefixValue}${Head[K]}` : Head[K] : Head[K];
    }
]> : ACC;
declare function prefixApi<Prefix extends string, Api extends readonly any[]>(prefix: Prefix, api: Api): MapPrefixPath<Api, Prefix, []>;

export { WithZodiosContext, ZodiosApp, ZodiosAppOptions, ZodiosContext, ZodiosHandler, ZodiosHandlers, ZodiosRequestHandler, ZodiosRouter, ZodiosRouterContextRequestHandler, ZodiosRouterOptions, ZodiosSuccessCodes, ZodiosUse, ZodiosValidationOptions, prefixApi, zodiosApp, zodiosContext, zodiosNextApp, zodiosRouter };
