import { Auth, createActionURL } from "@auth/core";
// @ts-expect-error Next.js does not yet correctly use the `package.json#exports` field
import { headers } from "next/headers";
// @ts-expect-error Next.js does not yet correctly use the `package.json#exports` field
import { NextResponse } from "next/server";
import { reqWithEnvURL } from "./env.js";
async function getSession(headers, config) {
    const url = createActionURL("session", 
    // @ts-expect-error `x-forwarded-proto` is not nullable, next.js sets it by default
    headers.get("x-forwarded-proto"), headers, process.env, config);
    const request = new Request(url, {
        headers: { cookie: headers.get("cookie") ?? "" },
    });
    return Auth(request, {
        ...config,
        callbacks: {
            ...config.callbacks,
            // Since we are server-side, we don't need to filter out the session data
            // See https://authjs.dev/getting-started/migrating-to-v5#authenticating-server-side
            // TODO: Taint the session data to prevent accidental leakage to the client
            // https://react.dev/reference/react/experimental_taintObjectReference
            async session(...args) {
                const session = 
                // If the user defined a custom session callback, use that instead
                (await config.callbacks?.session?.(...args)) ?? {
                    ...args[0].session,
                    expires: args[0].session.expires?.toISOString?.() ??
                        args[0].session.expires,
                };
                const user = args[0].user ?? args[0].token;
                return { user, ...session };
            },
        },
    });
}
function isReqWrapper(arg) {
    return typeof arg === "function";
}
export function initAuth(config, onLazyLoad // To set the default env vars
) {
    if (typeof config === "function") {
        return async (...args) => {
            if (!args.length) {
                // React Server Components
                const _headers = await headers();
                const _config = await config(undefined); // Review: Should we pass headers() here instead?
                onLazyLoad?.(_config);
                return getSession(_headers, _config).then((r) => r.json());
            }
            if (args[0] instanceof Request) {
                // middleware.ts inline
                // export { auth as default } from "auth"
                const req = args[0];
                const ev = args[1];
                const _config = await config(req);
                onLazyLoad?.(_config);
                // args[0] is supposed to be NextRequest but the instanceof check is failing.
                return handleAuth([req, ev], _config);
            }
            if (isReqWrapper(args[0])) {
                // middleware.ts wrapper/route.ts
                // import { auth } from "auth"
                // export default auth((req) => { console.log(req.auth) }})
                const userMiddlewareOrRoute = args[0];
                return async (...args) => {
                    const _config = await config(args[0]);
                    onLazyLoad?.(_config);
                    return handleAuth(args, _config, userMiddlewareOrRoute);
                };
            }
            // API Routes, getServerSideProps
            const request = "req" in args[0] ? args[0].req : args[0];
            const response = "res" in args[0] ? args[0].res : args[1];
            const _config = await config(request);
            onLazyLoad?.(_config);
            // @ts-expect-error -- request is NextRequest
            return getSession(new Headers(request.headers), _config).then(async (authResponse) => {
                const auth = await authResponse.json();
                for (const cookie of authResponse.headers.getSetCookie())
                    if ("headers" in response)
                        response.headers.append("set-cookie", cookie);
                    else
                        response.appendHeader("set-cookie", cookie);
                return auth;
            });
        };
    }
    return (...args) => {
        if (!args.length) {
            // React Server Components
            return Promise.resolve(headers()).then((h) => getSession(h, config).then((r) => r.json()));
        }
        if (args[0] instanceof Request) {
            // middleware.ts inline
            // export { auth as default } from "auth"
            const req = args[0];
            const ev = args[1];
            return handleAuth([req, ev], config);
        }
        if (isReqWrapper(args[0])) {
            // middleware.ts wrapper/route.ts
            // import { auth } from "auth"
            // export default auth((req) => { console.log(req.auth) }})
            const userMiddlewareOrRoute = args[0];
            return async (...args) => {
                return handleAuth(args, config, userMiddlewareOrRoute).then((res) => {
                    return res;
                });
            };
        }
        // API Routes, getServerSideProps
        const request = "req" in args[0] ? args[0].req : args[0];
        const response = "res" in args[0] ? args[0].res : args[1];
        return getSession(
        // @ts-expect-error
        new Headers(request.headers), config).then(async (authResponse) => {
            const auth = await authResponse.json();
            for (const cookie of authResponse.headers.getSetCookie())
                if ("headers" in response)
                    response.headers.append("set-cookie", cookie);
                else
                    response.appendHeader("set-cookie", cookie);
            return auth;
        });
    };
}
async function handleAuth(args, config, userMiddlewareOrRoute) {
    const request = reqWithEnvURL(args[0]);
    const sessionResponse = await getSession(request.headers, config);
    const auth = await sessionResponse.json();
    let authorized = true;
    if (config.callbacks?.authorized) {
        authorized = await config.callbacks.authorized({ request, auth });
    }
    let response = NextResponse.next?.();
    if (authorized instanceof Response) {
        // User returned a custom response, like redirecting to a page or 401, respect it
        response = authorized;
        const redirect = authorized.headers.get("Location");
        const { pathname } = request.nextUrl;
        // If the user is redirecting to the same NextAuth.js action path as the current request,
        // don't allow the redirect to prevent an infinite loop
        if (redirect &&
            isSameAuthAction(pathname, new URL(redirect).pathname, config)) {
            authorized = true;
        }
    }
    else if (userMiddlewareOrRoute) {
        // Execute user's middleware/handler with the augmented request
        const augmentedReq = request;
        augmentedReq.auth = auth;
        response =
            (await userMiddlewareOrRoute(augmentedReq, args[1])) ??
                NextResponse.next();
    }
    else if (!authorized) {
        const signInPage = config.pages?.signIn ?? `${config.basePath}/signin`;
        if (request.nextUrl.pathname !== signInPage) {
            // Redirect to signin page by default if not authorized
            const signInUrl = request.nextUrl.clone();
            signInUrl.pathname = signInPage;
            signInUrl.searchParams.set("callbackUrl", request.nextUrl.href);
            response = NextResponse.redirect(signInUrl);
        }
    }
    const finalResponse = new Response(response?.body, response);
    // Preserve cookies from the session response
    for (const cookie of sessionResponse.headers.getSetCookie())
        finalResponse.headers.append("set-cookie", cookie);
    return finalResponse;
}
function isSameAuthAction(requestPath, redirectPath, config) {
    const action = redirectPath.replace(`${requestPath}/`, "");
    const pages = Object.values(config.pages ?? {});
    return ((actions.has(action) || pages.includes(redirectPath)) &&
        redirectPath === requestPath);
}
const actions = new Set([
    "providers",
    "session",
    "csrf",
    "signin",
    "signout",
    "callback",
    "verify-request",
    "error",
]);
