"use strict";
/**
 * @adonisjs/http-server
 *
 * (c) Harminder Virk <virk@adonisjs.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Server = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const ms_1 = __importDefault(require("ms"));
const Hooks_1 = require("./Hooks");
const Router_1 = require("../Router");
const Request_1 = require("../Request");
const Response_1 = require("../Response");
const PreCompiler_1 = require("./PreCompiler");
const HttpContext_1 = require("../HttpContext");
const RequestHandler_1 = require("./RequestHandler");
const MiddlewareStore_1 = require("../MiddlewareStore");
const ExceptionManager_1 = require("./ExceptionManager");
const LocalStorage_1 = require("../HttpContext/LocalStorage");
/**
 * Server class handles the HTTP requests by using all Adonis micro modules.
 */
class Server {
    constructor(application, encryption, httpConfig) {
        this.application = application;
        this.encryption = encryption;
        this.httpConfig = httpConfig;
        /**
         * The middleware store to register global and named middleware
         */
        this.middleware = new MiddlewareStore_1.MiddlewareStore(this.application.container);
        /**
         * The route to register routes
         */
        this.router = new Router_1.Router(this.encryption, (route) => this.precompiler.compileRoute(route));
        /**
         * Server before/after hooks
         */
        this.hooks = new Hooks_1.Hooks();
        /**
         * Precompiler to set the finalHandler for the route
         */
        this.precompiler = new PreCompiler_1.PreCompiler(this.application.container, this.middleware);
        /**
         * Exception manager to handle exceptions
         */
        this.exception = new ExceptionManager_1.ExceptionManager(this.application.container);
        /**
         * Request handler to handle request after route is found
         */
        this.requestHandler = new RequestHandler_1.RequestHandler(this.middleware, this.router);
        /*
         * Pre process config to convert max age string to seconds.
         */
        if (httpConfig.cookie.maxAge && typeof httpConfig.cookie.maxAge === 'string') {
            httpConfig.cookie.maxAge = (0, ms_1.default)(httpConfig.cookie.maxAge) / 1000;
        }
        (0, LocalStorage_1.useAsyncLocalStorage)(httpConfig.useAsyncLocalStorage || false);
    }
    /**
     * Handles HTTP request
     */
    async runBeforeHooksAndHandler(ctx) {
        /*
         * Start with before hooks upfront. If they raise error
         * then execute error handler.
         */
        return this.hooks.executeBefore(ctx).then((shortcircuit) => {
            if (!shortcircuit) {
                return this.requestHandler.handle(ctx);
            }
        });
    }
    /**
     * Returns the profiler row
     */
    getProfilerRow(request) {
        return this.application.profiler.create('http:request', {
            request_id: request.id(),
            url: request.url(),
            method: request.method(),
        });
    }
    /**
     * Returns the context for the request
     */
    getContext(request, response, profilerRow) {
        return new HttpContext_1.HttpContext(request, response, this.application.logger.child({
            request_id: request.id(),
        }), profilerRow);
    }
    /**
     * Handle the request
     */
    async handleRequest(ctx, requestAction, res) {
        /*
         * Handle request by executing hooks, request middleware stack
         * and route handler
         */
        try {
            await this.runBeforeHooksAndHandler(ctx);
        }
        catch (error) {
            await this.exception.handle(error, ctx);
        }
        /*
         * Excute hooks when there are one or more hooks. The `ctx.response.finish`
         * is intentionally inside both the `try` and `catch` blocks as a defensive
         * measure.
         *
         * When we call `response.finish`, it will serialize the response body and may
         * encouter errors while doing so and hence will be catched by the catch
         * block.
         */
        try {
            await this.hooks.executeAfter(ctx);
            requestAction.end({ status_code: res.statusCode });
            ctx.response.finish();
        }
        catch (error) {
            await this.exception.handle(error, ctx);
            requestAction.end({ status_code: res.statusCode, error });
            ctx.response.finish();
        }
    }
    /**
     * Define custom error handler to handler all errors
     * occurred during HTTP request
     */
    errorHandler(handler) {
        this.exception.registerHandler(handler);
        return this;
    }
    /**
     * Optimizes internal handlers, based upon the existence of
     * before handlers and global middleware. This helps in
     * increasing throughput by 10%
     */
    optimize() {
        this.router.commit();
        this.hooks.commit();
        this.requestHandler.commit();
    }
    /**
     * Handles a given HTTP request. This method can be attached to any HTTP
     * server
     */
    async handle(req, res) {
        const request = new Request_1.Request(req, res, this.encryption, this.httpConfig);
        const response = new Response_1.Response(req, res, this.encryption, this.httpConfig, this.router);
        const requestAction = this.getProfilerRow(request);
        const ctx = this.getContext(request, response, requestAction);
        /*
         * Reset accept header when `forceContentNegotiationTo` is defined
         */
        const accept = this.httpConfig.forceContentNegotiationTo;
        if (accept) {
            req.headers['accept'] = typeof accept === 'function' ? accept(ctx) : accept;
        }
        if (LocalStorage_1.usingAsyncLocalStorage) {
            return LocalStorage_1.httpContextLocalStorage.run(ctx, () => this.handleRequest(ctx, requestAction, res));
        }
        else {
            return this.handleRequest(ctx, requestAction, res);
        }
    }
}
exports.Server = Server;
