"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.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.Router = void 0;
/// <reference path="../../adonis-typings/index.ts" />
const helpers_1 = require("@poppinss/utils/build/helpers");
const Route_1 = require("./Route");
const Store_1 = require("./Store");
const Group_1 = require("./Group");
const BriskRoute_1 = require("./BriskRoute");
const Resource_1 = require("./Resource");
const RouterException_1 = require("../Exceptions/RouterException");
const helpers_2 = require("../helpers");
const Matchers_1 = require("./Matchers");
const LookupStore_1 = require("./LookupStore");
/**
 * Router class exposes unified API to create new routes, group them or
 * create route resources.
 *
 * @example
 * ```ts
 * const router = new Router()
 *
 * router.get('/', async function () {
 *   // handle request
 * })
 * ```
 */
class Router extends LookupStore_1.LookupStore {
    constructor(encryption, routeProcessor) {
        super(encryption);
        this.routeProcessor = routeProcessor;
        /**
         * Collection of routes, including route resource and route
         * group. To get a flat list of routes, call `router.toJSON()`
         */
        this.routes = [];
        /**
         * Exposing BriskRoute, RouteGroup and RouteResource constructors
         * to be extended from outside
         */
        this.BriskRoute = BriskRoute_1.BriskRoute;
        this.RouteGroup = Group_1.RouteGroup;
        this.RouteResource = Resource_1.RouteResource;
        this.Route = Route_1.Route;
        this.RouteMatchers = Matchers_1.RouteMatchers;
        /**
         * Shortcut methods for commonly used route matchers
         */
        this.matchers = new Matchers_1.RouteMatchers();
        /**
         * Global matchers to test route params against regular expressions.
         */
        this.paramMatchers = {};
        /**
         * Store with tokenized routes
         */
        this.store = new Store_1.Store();
        /**
         * A boolean to tell the router that a group is in
         * open state right now
         */
        this.openedGroups = [];
    }
    getRecentGroup() {
        return this.openedGroups[this.openedGroups.length - 1];
    }
    /**
     * Add route for a given pattern and methods
     */
    route(pattern, methods, handler) {
        const route = new Route_1.Route(pattern, methods, handler, this.paramMatchers);
        const openedGroup = this.getRecentGroup();
        if (openedGroup) {
            openedGroup.routes.push(route);
        }
        else {
            this.routes.push(route);
        }
        return route;
    }
    /**
     * Define a route that handles all common HTTP methods
     */
    any(pattern, handler) {
        return this.route(pattern, ['HEAD', 'OPTIONS', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE'], handler);
    }
    /**
     * Define `GET` route
     */
    get(pattern, handler) {
        return this.route(pattern, ['GET', 'HEAD'], handler);
    }
    /**
     * Define `POST` route
     */
    post(pattern, handler) {
        return this.route(pattern, ['POST'], handler);
    }
    /**
     * Define `PUT` route
     */
    put(pattern, handler) {
        return this.route(pattern, ['PUT'], handler);
    }
    /**
     * Define `PATCH` route
     */
    patch(pattern, handler) {
        return this.route(pattern, ['PATCH'], handler);
    }
    /**
     * Define `DELETE` route
     */
    delete(pattern, handler) {
        return this.route(pattern, ['DELETE'], handler);
    }
    /**
     * Creates a group of routes. A route group can apply transforms
     * to routes in bulk
     */
    group(callback) {
        /*
         * Create a new group with empty set of routes
         */
        const group = new Group_1.RouteGroup([]);
        /*
         * See if there is any opened existing route groups. If yes, then we
         * push this new group to the old group, otherwise we push it to
         * the list of routes.
         */
        const openedGroup = this.getRecentGroup();
        if (openedGroup) {
            openedGroup.routes.push(group);
        }
        else {
            this.routes.push(group);
        }
        /*
         * Track the group, so that the upcoming calls inside the callback
         * can use this group
         */
        this.openedGroups.push(group);
        /*
         * Execute the callback. Now all registered routes will be
         * collected seperately from the `routes` array
         */
        callback();
        /*
         * Now the callback is over, get rid of the opened group
         */
        this.openedGroups.pop();
        return group;
    }
    /**
     * Registers a route resource with conventional set of routes
     */
    resource(resource, controller) {
        const resourceInstance = new Resource_1.RouteResource(resource, controller, this.paramMatchers);
        const openedGroup = this.getRecentGroup();
        if (openedGroup) {
            openedGroup.routes.push(resourceInstance);
        }
        else {
            this.routes.push(resourceInstance);
        }
        return resourceInstance;
    }
    /**
     * Register a route resource with shallow nested routes.
     */
    shallowResource(resource, controller) {
        const resourceInstance = new Resource_1.RouteResource(resource, controller, this.paramMatchers, true);
        const openedGroup = this.getRecentGroup();
        if (openedGroup) {
            openedGroup.routes.push(resourceInstance);
        }
        else {
            this.routes.push(resourceInstance);
        }
        return resourceInstance;
    }
    /**
     * Returns a brisk route instance for a given URL pattern
     */
    on(pattern) {
        const briskRoute = new BriskRoute_1.BriskRoute(pattern, this.paramMatchers);
        const openedGroup = this.getRecentGroup();
        if (openedGroup) {
            openedGroup.routes.push(briskRoute);
        }
        else {
            this.routes.push(briskRoute);
        }
        return briskRoute;
    }
    /**
     * Define global route matcher
     */
    where(param, matcher) {
        if (typeof matcher === 'string') {
            this.paramMatchers[param] = { match: new RegExp(matcher) };
        }
        else if (helpers_1.types.isRegexp(matcher)) {
            this.paramMatchers[param] = { match: matcher };
        }
        else {
            this.paramMatchers[param] = matcher;
        }
        return this;
    }
    /**
     * Returns a flat list of routes JSON
     */
    toJSON() {
        const lookupStoreRoutes = this.tree;
        const domains = Object.keys(lookupStoreRoutes);
        return domains.reduce((result, domain) => {
            result[domain] = lookupStoreRoutes[domain].map((route) => {
                const routeJSON = this.store.tree.domains[domain][route.methods[0]].routes[route.pattern];
                return Object.assign({ methods: route.methods }, routeJSON);
            });
            return result;
        }, {});
    }
    /**
     * Commit routes to the store. After this, no more
     * routes can be registered.
     */
    commit() {
        const names = [];
        (0, helpers_2.toRoutesJSON)(this.routes).forEach((route) => {
            /*
             * Raise error when route name is already in use. Route names have to be unique
             * to ensure that only one route is returned during lookup.
             */
            if (route.name && names.indexOf(route.name) > -1) {
                throw RouterException_1.RouterException.duplicateRouteName(route.name);
            }
            /*
             * If route has a unique, then track the name for checking duplicates
             */
            if (route.name) {
                names.push(route.name);
            }
            /*
             * If a pre-processor is defined then pass the [[RouteNode]]
             * to it.
             */
            if (this.routeProcessor) {
                this.routeProcessor(route);
            }
            /**
             * Register the route with the lookup store
             */
            this.register(route);
            this.store.add(route);
        });
        this.routes = [];
        this.paramMatchers = {};
    }
    /**
     * Find route for a given URL, method and optionally domain
     */
    match(url, method, domain) {
        /*
         * 1. If domain is not mentioned, then we lookup for root level defined
         *    routes.
         *
         * 2. If domain is mentioned, then we check the store to see if user has registered
         *    one or more routes for that domain or not.
         *
         *    - If they have not registered any routes for the mentioned domain, it means,
         *      they don't want any special treatment for this domain, hence we search
         *      with the the root level routes. (Same as 1)
         *
         *    - Else we search within the routes of the mentioned domain.
         */
        let response = null;
        const matchingDomain = domain ? this.store.matchDomain(domain) : [];
        if (!matchingDomain.length) {
            response = this.store.match(url, method);
        }
        else {
            /*
             * Search within the domain
             */
            response = this.store.match(url, method, {
                storeMatch: matchingDomain,
                value: domain,
            });
        }
        return response;
    }
    /**
     * Makes url to a registered route by looking it up with the route pattern,
     * name or the controller.method
     */
    makeUrl(routeIdentifier, params, options) {
        const normalizedOptions = (0, helpers_2.normalizeMakeUrlOptions)(params, options);
        const builder = normalizedOptions.domain
            ? this.builderForDomain(normalizedOptions.domain)
            : this.builder();
        normalizedOptions.params && builder.params(normalizedOptions.params);
        normalizedOptions.qs && builder.qs(normalizedOptions.qs);
        normalizedOptions.prefixUrl && builder.prefixUrl(normalizedOptions.prefixUrl);
        normalizedOptions.disableRouteLookup && builder.disableRouteLookup();
        return builder.make(routeIdentifier);
    }
    /**
     * Makes a signed url, which can be confirmed for it's integrity without
     * relying on any sort of backend storage.
     */
    makeSignedUrl(routeIdentifier, params, options) {
        const normalizedOptions = (0, helpers_2.normalizeMakeSignedUrlOptions)(params, options);
        const builder = normalizedOptions.domain
            ? this.builderForDomain(normalizedOptions.domain)
            : this.builder();
        normalizedOptions.params && builder.params(normalizedOptions.params);
        normalizedOptions.qs && builder.qs(normalizedOptions.qs);
        normalizedOptions.prefixUrl && builder.prefixUrl(normalizedOptions.prefixUrl);
        normalizedOptions.disableRouteLookup && builder.disableRouteLookup();
        return builder.makeSigned(routeIdentifier, normalizedOptions);
    }
}
exports.Router = Router;
