/*
 * Copyright 2020-2022 VMware, Inc. All rights reserved. VMware Confidential
 */

import { Injectable } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivate, Route, Router, RouterStateSnapshot, UrlSegment } from "@angular/router";

@Injectable()
export class DynamicPathGuard implements CanActivate {
    private routerConfigTimeout;
    private routerUrlsTimeout;

    constructor(private router: Router) {}

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
        /**
         * Check if the url which the browser is trying to navigate to
         * is actaully an extension. If so we've to trigger change detection
         * in order to make our dynamic extension urls available in the router config.
         *
         * Example:
         *
         * The user pastes absolute url in the browser (https://my.host.name/vdcs/:vdcId/resources/:id/:extPath/home).
         * The expected result is that our platform will open the link without errors (Render UI Plugin).
         *
         * The problem is there some of our modules are lazy loaded and the router config and the
         * route object are not always available at bootstrap time. Because of this problem we need
         * to trigger the change detection (via timeout) in order to get the latest route and router
         * configs.
         */
        return new Promise<boolean>((resolve, reject) => {
            this.routerConfigTimeout = setTimeout(() => {
                try {
                    if (this.doesPathExists(route.url)) {
                        // Trigger change detection so url is known for router
                        this.routerUrlsTimeout = setTimeout(() => {
                            this.router.navigateByUrl(state.url);
                            this.clearTimeouts();
                        }, 0);
                    } else {
                        this.router.navigateByUrl("/");
                    }

                    resolve(false);
                } catch (e) {
                    console.error("Error occurred while checking for dynamic route existance.", e);
                    this.router.navigateByUrl("/");
                    this.clearTimeouts();
                    resolve(false);
                }
            }, 0);
        });
    }

    /**
     * Check if the requested path is supported in static and dynamic routes
     */
    private doesPathExists(urlSegments: UrlSegment[]): any {
        let segments = urlSegments ? [...urlSegments] : [];

        const found = this.searchForSegmentInRoutersConfigByLevel(this.router.config, segments, true);
        return found;
    }

    /**
     * Search for existance of a segment in a specfic router layer, the search is recursive.
     * @param routes Router Config for certain route layer, i.e root -> child -> grandchild
     * @param segments The segments of the url which existance we want to check
     * Example:
     * /my/cool/route -> ["my", "cool", "route"]
     */
    private searchForSegmentInRoutersConfigByLevel(routes: Route[], segments: UrlSegment[], isRoot: boolean): any {
        const segment = segments.shift();
        let found = false;
        let routeFound = routes.find((route) => {
            return route.path.includes(segment.path) || (!isRoot && route.path === "**");
        });

        if (!routeFound) {
            routeFound = routes.find((route) => {
                return route.path.startsWith(":");
            });
        }

        if (routeFound && routeFound.children && routeFound.children.length) {
            found = this.searchForSegmentInRoutersConfigByLevel(routeFound.children, segments, false);
            return found;
        }

        if (routeFound && routeFound.loadChildren && routeFound["_loadedConfig"]) {
            found = this.searchForSegmentInRoutersConfigByLevel(routeFound["_loadedConfig"].routes, segments, false);
            return found;
        }

        if (routeFound) {
            if (routeFound.loadChildren || !routeFound["_loadedConfig"]) {
                return true;
            }
        }

        return false;
    }

    private clearTimeouts(): void {
        if (this.routerConfigTimeout) {
            clearTimeout(this.routerConfigTimeout);
        }

        if (this.routerUrlsTimeout) {
            clearTimeout(this.routerUrlsTimeout);
        }
    }
}
