import { assert } from '@ember/debug';
import { assign } from '@ember/polyfills';
let uuid = 0;
class DSL {
    constructor(name = null, options) {
        this.explicitIndex = false;
        this.parent = name;
        this.enableLoadingSubstates = !!(options && options.enableLoadingSubstates);
        this.matches = [];
        this.options = options;
    }
    route(name, options = {}, callback) {
        let dummyErrorRoute = `/_unused_dummy_error_path_route_${name}/:error`;
        if (arguments.length === 2 && typeof options === 'function') {
            callback = options;
            options = {};
        }
        assert(`'${name}' cannot be used as a route name.`, (() => {
            if (options.overrideNameAssertion === true) {
                return true;
            }
            return ['basic', 'application'].indexOf(name) === -1;
        })());
        assert(`'${name}' is not a valid route name. It cannot contain a ':'. You may want to use the 'path' option instead.`, name.indexOf(':') === -1);
        if (this.enableLoadingSubstates) {
            createRoute(this, `${name}_loading`, {
                resetNamespace: options.resetNamespace,
            });
            createRoute(this, `${name}_error`, {
                resetNamespace: options.resetNamespace,
                path: dummyErrorRoute,
            });
        }
        if (callback) {
            let fullName = getFullName(this, name, options.resetNamespace);
            let dsl = new DSL(fullName, this.options);
            createRoute(dsl, 'loading');
            createRoute(dsl, 'error', { path: dummyErrorRoute });
            callback.call(dsl);
            createRoute(this, name, options, dsl.generate());
        }
        else {
            createRoute(this, name, options);
        }
    }
    push(url, name, callback, serialize) {
        let parts = name.split('.');
        if (this.options.engineInfo) {
            let localFullName = name.slice(this.options.engineInfo.fullName.length + 1);
            let routeInfo = assign({ localFullName }, this.options.engineInfo);
            if (serialize) {
                routeInfo.serializeMethod = serialize;
            }
            this.options.addRouteForEngine(name, routeInfo);
        }
        else if (serialize) {
            throw new Error(`Defining a route serializer on route '${name}' outside an Engine is not allowed.`);
        }
        if (url === '' || url === '/' || parts[parts.length - 1] === 'index') {
            this.explicitIndex = true;
        }
        this.matches.push(url, name, callback);
    }
    generate() {
        let dslMatches = this.matches;
        if (!this.explicitIndex) {
            this.route('index', { path: '/' });
        }
        return match => {
            for (let i = 0; i < dslMatches.length; i += 3) {
                match(dslMatches[i]).to(dslMatches[i + 1], dslMatches[i + 2]);
            }
        };
    }
    mount(_name, options = {}) {
        let engineRouteMap = this.options.resolveRouteMap(_name);
        let name = _name;
        if (options.as) {
            name = options.as;
        }
        let fullName = getFullName(this, name, options.resetNamespace);
        let engineInfo = {
            name: _name,
            instanceId: uuid++,
            mountPoint: fullName,
            fullName,
        };
        let path = options.path;
        if (typeof path !== 'string') {
            path = `/${name}`;
        }
        let callback;
        let dummyErrorRoute = `/_unused_dummy_error_path_route_${name}/:error`;
        if (engineRouteMap) {
            let shouldResetEngineInfo = false;
            let oldEngineInfo = this.options.engineInfo;
            if (oldEngineInfo) {
                shouldResetEngineInfo = true;
                this.options.engineInfo = engineInfo;
            }
            let optionsForChild = assign({ engineInfo }, this.options);
            let childDSL = new DSL(fullName, optionsForChild);
            createRoute(childDSL, 'loading');
            createRoute(childDSL, 'error', { path: dummyErrorRoute });
            engineRouteMap.class.call(childDSL);
            callback = childDSL.generate();
            if (shouldResetEngineInfo) {
                this.options.engineInfo = oldEngineInfo;
            }
        }
        let localFullName = 'application';
        let routeInfo = assign({ localFullName }, engineInfo);
        if (this.enableLoadingSubstates) {
            // These values are important to register the loading routes under their
            // proper names for the Router and within the Engine's registry.
            let substateName = `${name}_loading`;
            let localFullName = `application_loading`;
            let routeInfo = assign({ localFullName }, engineInfo);
            createRoute(this, substateName, {
                resetNamespace: options.resetNamespace,
            });
            this.options.addRouteForEngine(substateName, routeInfo);
            substateName = `${name}_error`;
            localFullName = `application_error`;
            routeInfo = assign({ localFullName }, engineInfo);
            createRoute(this, substateName, {
                resetNamespace: options.resetNamespace,
                path: dummyErrorRoute,
            });
            this.options.addRouteForEngine(substateName, routeInfo);
        }
        this.options.addRouteForEngine(fullName, routeInfo);
        this.push(path, fullName, callback);
    }
}
export default DSL;
function canNest(dsl) {
    return dsl.parent !== 'application';
}
function getFullName(dsl, name, resetNamespace) {
    if (canNest(dsl) && resetNamespace !== true) {
        return `${dsl.parent}.${name}`;
    }
    else {
        return name;
    }
}
function createRoute(dsl, name, options = {}, callback) {
    let fullName = getFullName(dsl, name, options.resetNamespace);
    if (typeof options.path !== 'string') {
        options.path = `/${name}`;
    }
    dsl.push(options.path, fullName, callback, options.serialize);
}