"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.LookupStore = exports.UrlBuilder = void 0;
const qs_1 = __importDefault(require("qs"));
const encodeurl_1 = __importDefault(require("encodeurl"));
const RouterException_1 = require("../Exceptions/RouterException");
/**
 * A class to encapsulate finding routes
 */
class Routes {
    constructor(routes) {
        this.routes = routes;
    }
    /**
     * Find a route by indentifier
     */
    find(routeIdentifier) {
        return (this.routes.find(({ name, pattern, handler }) => {
            return (name === routeIdentifier || pattern === routeIdentifier || handler === routeIdentifier);
        }) || null);
    }
    /**
     * Find a route by indentifier or fail
     */
    findOrFail(routeIdentifier) {
        const route = this.find(routeIdentifier);
        if (!route) {
            throw RouterException_1.RouterException.cannotLookupRoute(routeIdentifier);
        }
        return route;
    }
    /**
     * Find if a route exists
     */
    has(routeIdentifier) {
        return !!this.find(routeIdentifier);
    }
}
/**
 * Url builder is responsible for building the URLs
 */
class UrlBuilder {
    constructor(encryption, routes) {
        this.encryption = encryption;
        this.routes = routes;
        /**
         * A custom query string to append to the URL
         */
        this.queryString = {};
        /**
         * A boolean to know if the route should be looked
         * up inside the route store or not
         */
        this.lookupRoute = true;
    }
    /**
     * Processes the pattern against the params
     */
    processPattern(pattern) {
        let url = [];
        const isParamsAnArray = Array.isArray(this.routeParams);
        /*
         * Split pattern when route has dynamic segments
         */
        const tokens = pattern.split('/');
        let paramsIndex = 0;
        for (const token of tokens) {
            /**
             * Expected wildcard param to be at the end always and hence
             * we must break out from the loop
             */
            if (token === '*') {
                const wildcardParams = isParamsAnArray
                    ? this.routeParams.slice(paramsIndex)
                    : this.routeParams['*'];
                if (!wildcardParams || !Array.isArray(wildcardParams) || !wildcardParams.length) {
                    throw RouterException_1.RouterException.cannotMakeRoute('*', pattern);
                }
                url = url.concat(wildcardParams);
                break;
            }
            /**
             * Token is a static value
             */
            if (!token.startsWith(':')) {
                url.push(token);
            }
            else {
                const isOptional = token.endsWith('?');
                const paramName = token.replace(/^:/, '').replace(/\?$/, '');
                const param = isParamsAnArray ? this.routeParams[paramsIndex] : this.routeParams[paramName];
                paramsIndex++;
                /*
                 * A required param is always required to make the complete URL
                 */
                if (!param && !isOptional) {
                    throw RouterException_1.RouterException.cannotMakeRoute(paramName, pattern);
                }
                url.push(param);
            }
        }
        return url.join('/');
    }
    /**
     * Suffix the query string to the URL
     */
    suffixQueryString(url) {
        if (this.queryString) {
            const encoded = qs_1.default.stringify(this.queryString);
            url = encoded ? `${url}?${(0, encodeurl_1.default)(encoded)}` : url;
        }
        return url;
    }
    /**
     * Prefix a custom url to the final URI
     */
    prefixUrl(url) {
        this.baseUrl = url;
        return this;
    }
    /**
     * Disable route lookup. Calling this method considers
     * the "identifier" as the route pattern
     */
    disableRouteLookup() {
        this.lookupRoute = false;
        return this;
    }
    /**
     * Append query string to the final URI
     */
    qs(queryString) {
        if (!queryString) {
            return this;
        }
        this.queryString = queryString;
        return this;
    }
    /**
     * Define required params to resolve the route
     */
    params(params) {
        if (!params) {
            return this;
        }
        this.routeParams = params;
        return this;
    }
    /**
     * Generate url for the given route identifier
     */
    make(identifier) {
        let url;
        if (this.lookupRoute) {
            const route = this.routes.findOrFail(identifier);
            url = this.processPattern(route.pattern);
        }
        else {
            url = this.processPattern(identifier);
        }
        return this.suffixQueryString(this.baseUrl ? `${this.baseUrl}${url}` : url);
    }
    /**
     * Generate url for the given route identifier
     */
    makeSigned(identifier, options) {
        let url;
        if (this.lookupRoute) {
            const route = this.routes.findOrFail(identifier);
            url = this.processPattern(route.pattern);
        }
        else {
            url = this.processPattern(identifier);
        }
        /*
         * Making the signature from the qualified url. We do not prefix the domain when
         * making signature, since it just makes the signature big.
         *
         * There might be a case, when someone wants to generate signature for the same route
         * on their 2 different domains, but we ignore that case for now and can consider
         * it later (when someone asks for it)
         */
        const signature = this.encryption.verifier.sign(this.suffixQueryString(url), options?.expiresIn, options?.purpose);
        /*
         * Adding signature to the query string and re-making the url again
         */
        Object.assign(this.queryString, { signature });
        return this.suffixQueryString(this.baseUrl ? `${this.baseUrl}${url}` : url);
    }
}
exports.UrlBuilder = UrlBuilder;
/**
 * The look up store to make URLs for a given route by looking
 * it by its name, route handler or the pattern directly.
 */
class LookupStore {
    constructor(encryption) {
        this.encryption = encryption;
        /**
         * Shape of the registered routes. Optimized for lookups
         */
        this.tree = {};
    }
    /**
     * Register a route for lookups
     */
    register(route) {
        const domain = route.domain || 'root';
        this.tree[domain] = this.tree[domain] || [];
        this.tree[domain].push(route);
    }
    /**
     * Returns the route builder for the root domain
     */
    builder() {
        return this.builderForDomain('root');
    }
    /**
     * Returns the route builder a given domain.
     */
    builderForDomain(domainPattern) {
        const domainRoutes = this.tree[domainPattern];
        if (!domainRoutes && domainPattern !== 'root') {
            throw RouterException_1.RouterException.cannotLookupDomain(domainPattern);
        }
        return new UrlBuilder(this.encryption, new Routes(domainRoutes || []));
    }
    /**
     * Find a route by indentifier. Optionally one can find routes inside
     * a given domain
     */
    find(routeIdentifier, domainPattern) {
        const routes = this.tree[domainPattern || 'root'] || [];
        return new Routes(routes || []).find(routeIdentifier);
    }
    /**
     * Find a route by indentifier or fail. Optionally one can find routes inside
     * a given domain
     */
    findOrFail(routeIdentifier, domainPattern) {
        const routes = this.tree[domainPattern || 'root'] || [];
        return new Routes(routes || []).findOrFail(routeIdentifier);
    }
    /**
     * Find if a route for given identifier exists. Optionally one can find routes inside
     * a given domain
     */
    has(routeIdentifier, domainPattern) {
        const routes = this.tree[domainPattern || 'root'] || [];
        return new Routes(routes || []).has(routeIdentifier);
    }
}
exports.LookupStore = LookupStore;
