/* eslint-disable @typescript-eslint/no-explicit-any */
import { navigate } from '../lib/route.js';
/**
 * @param value The pattern to evaluate
 */
function getPattern(value) {
    return new RegExp(`^${value}$`);
}
/**
 * @param uri The path value of the current URL.
 * @param pattern The pattern to evaluate
 */
function testRoute(uri, pattern) {
    return getPattern(pattern).test(uri);
}
/**
 * @param pattern The pattern to evaluate
 * @param uri The path value of the current URL.
 */
function parseParams(pattern, uri) {
    const r = getPattern(pattern);
    const match = r.exec(uri);
    if (!match) {
        return undefined;
    }
    const { groups } = match;
    const result = {};
    if (groups) {
        Object.keys(groups).forEach((key) => {
            let value = groups[key];
            if (value[0] === '/') {
                value = value.substring(1);
            }
            if (value.includes('/')) {
                result[key] = value.split('/').map((i) => decodeURIComponent(i));
            }
            else {
                result[key] = decodeURIComponent(value);
            }
        });
    }
    return result;
}
function warnNoFallback(path) {
    // eslint-disable-next-line no-console
    console.warn(`Unable to find a fallback route for path ${path || '/'}.`);
}
function warnNoRoutes() {
    // eslint-disable-next-line no-console
    console.warn('The route callback was called but no routes are defined on the class.');
}
export function RouteMixin(superClass) {
    class RouteMixinClass extends superClass {
        static get routes() {
            return [];
        }
        currentRoute;
        constructor(...args) {
            super(...args);
            this._routeHandler = this._routeHandler.bind(this);
        }
        initializeRouting() {
            window.onpopstate = () => this._routeHandler();
            requestAnimationFrame(() => {
                this._routeHandler();
            });
        }
        _routeHandler() {
            const routes = Reflect.get(this.constructor, 'routes');
            if (!Array.isArray(routes) || !routes.length) {
                warnNoRoutes();
                return;
            }
            const url = new URL(window.location.href);
            let path = url.hash.replace('#', '');
            if (!path.startsWith('/')) {
                path = `/${path}`;
            }
            const patternRoutes = routes.filter((r) => !!r.pattern && r.pattern !== '*');
            const requiredRoutes = routes.filter((r) => r.pattern === '*');
            let activeRoute = patternRoutes.find((r) => testRoute(path, r.pattern));
            if (!activeRoute) {
                activeRoute = routes.find((r) => r.fallback);
                if (!activeRoute) {
                    warnNoFallback(path);
                    return;
                }
            }
            const routeCopy = { ...activeRoute };
            const argument = {
                route: routeCopy,
            };
            if (activeRoute.pattern) {
                const params = parseParams(activeRoute.pattern, path);
                if (params) {
                    argument.params = params;
                }
            }
            this.currentRoute = argument;
            this._callRoute(activeRoute, argument);
            if (activeRoute.title) {
                document.title = activeRoute.title;
            }
            requiredRoutes.forEach((r) => {
                this._callRoute(r, argument);
            });
        }
        _callRoute(route, argument) {
            if (typeof route.method !== 'string') {
                // eslint-disable-next-line no-console
                console.warn('The route method is not a string.');
                return;
            }
            // @ts-expect-error This will go away.
            // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
            const fn = this[route.method];
            if (typeof fn !== 'function') {
                // eslint-disable-next-line no-console
                console.warn(`The route method "${route.method}" is not defined.`);
                return;
            }
            fn.apply(this, [argument]);
        }
        /**
         * Navigates to an internal route.
         *
         * @param route Optional route params to add to the hash part of the url.
         */
        navigate(...route) {
            navigate(...route);
        }
    }
    return RouteMixinClass;
}
//# sourceMappingURL=RouteMixin.js.map