/*
 * ******************************************************************
 * Copyright (c) 2024 Broadcom. All Rights Reserved.
 * Broadcom Confidential. The term "Broadcom" refers to Broadcom Inc.
 * and/or its subsidiaries.
 * ******************************************************************
 */

import { inject } from "@angular/core";
import { ActivatedRouteSnapshot, CanActivateFn, GuardResult, MaybeAsync, Route, Router, RouterStateSnapshot } from "@angular/router";
import { ExtensionPointRoutingHandler } from "./extension-point-routing.handler";
import { Observable, catchError, filter, map, mergeMap, of, take } from "rxjs";
import { HYPER_PLUGIN_CONFIG, HyperpluginConfig } from "../../../lib-interfaces";
import { GeneralExtensibilityService } from "../../../consumer/general-extensibility.service";

export const DYNAMIC_PATH_GUARD_FN: (redirectToHome: boolean) => CanActivateFn = (redirectToHome) => {
    return (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): MaybeAsync<GuardResult> => {
        const router = inject(Router);
        const extensionPointRoutingHandler = inject(ExtensionPointRoutingHandler);
        const generalExtensibilityService = inject(GeneralExtensibilityService);
        const hyperPluginConfig = inject(HYPER_PLUGIN_CONFIG);

        const dynamicPathGuard = new DynamicPathGuard(router,
            extensionPointRoutingHandler, generalExtensibilityService, hyperPluginConfig, redirectToHome);
        return dynamicPathGuard.canActivate(route, state);
    }
}
export class DynamicPathGuard {
    private routerConfigTimeout;
    private routerUrlsTimeout;

    constructor(
        private router: Router,
        private extensionPointRoutingHandler: ExtensionPointRoutingHandler,
        private generalExtensibilityService: GeneralExtensibilityService,
        private HYPER_PLUGIN_CONFIG: HyperpluginConfig,
        private redirectToHome: boolean,
    ) {}

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<GuardResult> {
        /**
         * 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<GuardResult>((resolve, reject) => {
            this.routerConfigTimeout = setTimeout(() => {
                try {
                    // Phase 1 - The current route the user tries to navigate to is UI plugin route.
                    const navigatesToPluginRoute = this.HYPER_PLUGIN_CONFIG.routes.some((extRoute) => {
                        const pluginRouteSegments = extRoute.path.split("/").filter(Boolean);
                        const currentUrlSegments = state.url.split("/").map(decodeURIComponent).filter(Boolean);
                        
                        let segmentsMatch = true;
                        while (pluginRouteSegments.length && segmentsMatch) {
                            const pluginSegment = pluginRouteSegments.shift();
                            const currentSegment = currentUrlSegments.shift();
                            
                            // In case route parameter is hit continue with the search for next pair of url segments.
                            if (pluginSegment.startsWith(":")) {
                                continue;
                            }
    
                            segmentsMatch = pluginSegment === currentSegment;
                        }
    
                        return segmentsMatch;
                    });

                    // Phase 2 - The UI plugin route actually exits
                    if (navigatesToPluginRoute) {
                        this.generalExtensibilityService.allExtensionsLoaded.pipe(
                            filter(Boolean),
                            mergeMap(() => {
                                return this.doesPathExists(state.url.split("/").map(decodeURIComponent).filter(Boolean));
                            }),
                            map((pathExists) => {
                                if (pathExists) {
                                    // Trigger change detection so url is known for router
                                    this.routerUrlsTimeout = setTimeout(() => {
                                        const urlTree = this.router.createUrlTree([decodeURIComponent(state.url)]);
                                        resolve(urlTree);
                                        this.clearTimeouts();
                                    }, 0);
                                } else {
                                    const urlTree = this.router.createUrlTree(["/"]);
                                    resolve(urlTree);
                                    this.clearTimeouts();
                                }
                            }),
                            catchError((error: any) => {
                                const urlTree = this.router.createUrlTree(["/"]);
                                resolve(urlTree);
                                this.clearTimeouts();
                                return of(error);
                            })
                        ).subscribe();
                    } else {
                        if (this.redirectToHome) {
                            const urlTree = this.router.createUrlTree(["/"]);
                            resolve(urlTree);
                        } else {
                            resolve(true);
                        }
                        this.clearTimeouts();
                    }
                } catch (e) {
                    console.error("Error occurred while checking for dynamic route existance.", e);
                    const urlTree = this.router.createUrlTree(["/"]);
                    resolve(urlTree);
                    this.clearTimeouts();
                }
            }, 0);
        });
    }

    /**
     * Check if the requested path is supported in static and dynamic routes
     */
    private doesPathExists(urlSegments: string[]): Observable<boolean> {
        const segments = urlSegments ? [...urlSegments] : [];
        const mainPath = segments.shift();
        
        let mainRoute = this.router.config.find((route) => {
            return route.path === mainPath;
        });

        if (!mainRoute) {
            const emptyPathRoot = this.router.config.find((route) => {
                return route.path === "";
            });

            if (!emptyPathRoot) {
                return of(false);
            }

            if (!emptyPathRoot.children) {
                return of(false);
            }

            mainRoute = emptyPathRoot.children.find((route) => {
                return route.path === mainPath;
            });
        }

        return this.extensionPointRoutingHandler.findRouteChain(mainRoute, segments).pipe(
            map((route: Route) => {
                return !!route;
            }),
            take(1),
        );
    }

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

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