import { RouterTree } from "@lib/routerTree.ts";
import { none, Option, some } from "@shared/utils/option.ts";
import { Context } from "@lib/context.ts";

type RequestHandler<S extends string> = (
    c: Context<S>,
) => Promise<Response> | Response;
export type Middleware = (
    c: Context<string>,
    next: () => Promise<void>,
) => Promise<Response | undefined> | Response | undefined;

type MethodHandlers<S extends string> = Partial<
    Record<string, RequestHandler<S>>
>;

const DEFAULT_NOT_FOUND_HANDLER = () => new Response("404 Not found");

class HttpRouter {
    routerTree = new RouterTree<MethodHandlers<any>>();
    pathPreprocessor?: (path: string) => string;
    middlewareChain: Middleware[] = [];
    defaultNotFoundHandler: RequestHandler<string> = DEFAULT_NOT_FOUND_HANDLER;

    setPathProcessor(processor: (path: string) => string) {
        this.pathPreprocessor = processor;
    }

    use(mw: Middleware): HttpRouter {
        this.middlewareChain.push(mw);
        return this;
    }

    add<S extends string>(
        path: S,
        method: string,
        handler: RequestHandler<S>,
    ): HttpRouter;
    add<S extends string>(
        path: S[],
        method: string,
        handler: RequestHandler<string>,
    ): HttpRouter;

    add(
        path: string | string[],
        method: string,
        handler: RequestHandler<string>,
    ): HttpRouter {
        const paths = Array.isArray(path) ? path : [path];

        for (const p of paths) {
            this.routerTree.getHandler(p).match(
                (mth) => {
                    mth[method] = handler;
                },
                () => {
                    const mth: MethodHandlers<string> = {};
                    mth[method] = handler;
                    this.routerTree.add(p, mth);
                },
            );
        }

        return this;
    }

    // Overload signatures for 'get'
    get<S extends string>(path: S, handler: RequestHandler<S>): HttpRouter;
    get<S extends string>(
        path: S[],
        handler: RequestHandler<string>,
    ): HttpRouter;

    // Non-generic implementation for 'get'
    get(path: string | string[], handler: RequestHandler<string>): HttpRouter {
        if (Array.isArray(path)) {
            return this.add(path, "GET", handler);
        }
        return this.add(path, "GET", handler);
    }

    post<S extends string>(path: S, handler: RequestHandler<S>): HttpRouter;
    post<S extends string>(
        path: string[],
        handler: RequestHandler<string>,
    ): HttpRouter;

    post(path: string | string[], handler: RequestHandler<string>): HttpRouter {
        if (Array.isArray(path)) {
            return this.add(path, "POST", handler);
        }
        return this.add(path, "POST", handler);
    }

    async handleRequest(
        req: Request,
        connInfo: Deno.ServeHandlerInfo<Deno.Addr>,
    ): Promise<Response> {
        const c = new Context(req, connInfo, {});

        let i = 0;
        const mw = this.middlewareChain[i++];

        const path = this.pathPreprocessor
            ? this.pathPreprocessor(c.path)
            : c.path;

        return await this.routerTree
            .find(path)
            .andThen((routeMatch) => {
                const { value, params } = routeMatch;
                const handler = value[req.method];
                return handler
                    ? some(handler(Context.setParams(c, params)))
                    : none;
            })
            .unwrapOrElse(() => this.defaultNotFoundHandler(c));
    }
}

export type ExtractRouteParams<T extends string> = T extends string
    ? T extends `${infer _Start}:${infer Param}/${infer Rest}`
        ? Param | ExtractRouteParams<Rest>
    : T extends `${infer _Start}:${infer Param}` ? Param
    : T extends `${infer _Start}*` ? "restOfThePath"
    : never
    : never;

export type Params<Keys extends string> = {
    [K in Keys]: string;
};

export default HttpRouter;
