/*
 * Copyright 2023-2025 VMware, Inc. All rights reserved. VMware Confidential
 */

import { Injectable } from "@angular/core";
import { Route, Router } from "@angular/router";
import { ExtensionContainerGuard } from "./extension-container/extension-container-guard";
import { ExtensionContainerComponent } from "./extension-container/extension-container.component";
import { getHashedString } from "./loader/external/create-external-extension-route.step";
import { Observable, filter, map, mergeMap, of, take } from "rxjs";
import { HyperPluginRoutesStorage } from "../../../hyper-plugin-routes-storage.service";

/**
 * Handler for registering extensions in the routing.
 */
@Injectable()
export class ExtensionPointRoutingHandler {

    constructor(
        private hyperPluginRoutesStorage: HyperPluginRoutesStorage,
        private router: Router
    ) {}

    public registerExtensionRouting(
        resourcesRoute: Route,
        vendor: string,
        vcdRoute: string,
        extensionName: string): void {
        try {
            if (!resourcesRoute.children) {
                resourcesRoute.children = [];
            }

            let extensionRoute = {
                path: vcdRoute,
                component: ExtensionContainerComponent,
                data: {
                    vcdRoute,
                },
                canDeactivate: [ExtensionContainerGuard],
                children: [
                    {
                        path: "**",
                        component: ExtensionContainerComponent,
                        canDeactivate: [ExtensionContainerGuard],
                    },
                ],
            };

            const vendorHash = getHashedString(vendor);
            let pluginVendorRoute = resourcesRoute.children.find((route) => {
                return route.path === vendorHash;
            });

            if (!pluginVendorRoute) {
                pluginVendorRoute = {
                    path: vendorHash,
                    children: [
                        {
                            path: "",
                            pathMatch: "full",
                            redirectTo: vcdRoute,
                        },
                        extensionRoute,
                    ],
                };

                resourcesRoute.children.push(pluginVendorRoute);
            }

            pluginVendorRoute.children.push(extensionRoute);
        } catch (e) {
            throw Error(
                `Unable to register dynamic route ${vcdRoute} for extension point ${extensionName} in static module.`);
        }
    }

    /**
     * Finds the route for the provided path, traversing static and lazy loaded routes.
     * 
     * Example:
     * Find the route "here" from given URL "/my/path/starts/here".
     * 
     * mainPath - "my"
     * subPaths - ["path", "starts", "here"]
     * 
     * The function will search all child routes in the Router (static or lazy loaded)
     * and return a reference to the found route.
     * 
     * @param mainPath
     * @param subPaths
     *
     * @returns Promise with the found matching Route, or null in case it could not be matched.
     */
    public findRouteChain(mainRoute: Route, childSegments?: string[]): Observable<Route> {
        let childRoute: Route = null;
        let mainRouteChildren: Route[] = mainRoute.children;

        if (!mainRouteChildren) {
            mainRouteChildren = mainRoute["_loadedRoutes"];
        }

        if (mainRoute && !childSegments.length) {
            return of(mainRoute);
        }

        // Handle lazy loaded routes
        if (!mainRouteChildren) {
            return this.hyperPluginRoutesStorage.lazyLoadedRoutesMap[mainRoute.path].pipe(
                filter(Boolean),
                take(1),
                mergeMap((mainRoute: Route) => {
                    mainRouteChildren = mainRoute["_loadedRoutes"];

                    let currentSegment = childSegments.shift();
                    childRoute = mainRouteChildren.find((route) => {
                        return route.path === currentSegment;
                    });

                    // Check for empty path that has the route we search for as direct child 
                    const emptyRoute = mainRouteChildren.find((route) => {
                        return route.path === "";
                    });
                    if (!childRoute && emptyRoute) {
                        childRoute = emptyRoute.children.find((route) => {
                            return route.path === currentSegment;
                        });
                    }

                    /**
                     * If the route ends with "**" return the parent path, i.e mainPath,
                     * let the hosting application to load the route where extension must be loaded.
                     */
                    const anyRouteDeclaration = mainRouteChildren.find((route) => {
                        return route.path === "**";
                    })
                    if (!childRoute && anyRouteDeclaration) {
                        return of(mainRoute);
                    }

                    // If some route segments can't be found return null
                    if (!childRoute) {
                        return of(null);
                    }
                    
                    // If there are no more url segments
                    if (!childSegments.length) {
                        // Return the child route if it's ready/loaded
                        if (childRoute.children || childRoute["_loadedRoutes"]) {
                            return of(childRoute);
                        }

                        // Wait until the router fetch the route configuration
                        return this.hyperPluginRoutesStorage.lazyLoadedRoutesMap[childRoute.path].pipe(
                            filter(Boolean),
                            take(1),
                            map(() => {
                                return childRoute;
                            }),
                        )
                    }
        
                    return this.findRouteChain(childRoute, childSegments);
                })
            )
        } else {
            // Handle static routes
            let currentSegment = childSegments.shift();
            childRoute = mainRouteChildren.find((route) => {
                return route.path === currentSegment;
            });

            // Check for empty path that has the route we search for as direct child 
            const emptyRoute = mainRouteChildren.find((route) => {
                return route.path === "";
            });
            if (!childRoute && emptyRoute) {
                childRoute = emptyRoute.children.find((route) => {
                    return route.path === currentSegment;
                });
            }

            /**
             * If the route ends with "**" return the parent path, i.e mainPath,
             * let the hosting application to load the route where extension must be loaded.
             */
            const anyRouteDeclaration = mainRouteChildren.find((route) => {
                return route.path === "**";
            })
            if (!childRoute && anyRouteDeclaration) {
                return of(mainRoute);
            }

            // If some route segments can't be found return null
            if (!childRoute) {
                return of(null);
            }

            // If there are no more url segments
            if (!childSegments.length) {
                // Return the child route if it's ready/loaded
                if (childRoute.children || childRoute["_loadedRoutes"]) {
                    return of(childRoute);
                }

                // Wait until the router fetch the route configuration
                return this.hyperPluginRoutesStorage.lazyLoadedRoutesMap[childRoute.path].pipe(
                    filter(Boolean),
                    take(1),
                    map(() => {
                        return childRoute;
                    }),
                )
            }

            // Recursively search for given url segment list.
            return this.findRouteChain(childRoute, childSegments)
        }
    }

    /**
     * Finds the route for the provided paths.
     * @param mainPath
     * @param subPaths
     *
     * @returns the found matching Route, or null in case it could not be matched.
     */
    public findRoute(mainPath: string, subPaths?: string[]): Route {
        if (!mainPath) {
            return null;
        }

        const mainRoute: Route = this.router.config.find((route) => route.path === mainPath);

        if (!subPaths || subPaths.length < 1) {
            return mainRoute;
        }

        let route: Route = mainRoute;
        subPaths.forEach((subPath: string) => {
            let subRoute = this.findSubRoute(route, subPath);
            if (!!subRoute) {
                route = subRoute;
            }
        });

        return route || null;
    }

    private findSubRoute(currentRoute: Route, subPath: string): Route {
        if (!currentRoute.children || currentRoute.children.length < 1) {
            if (currentRoute.path == "''") {
                return null;
            }
            return currentRoute;
        }

        return currentRoute.children.find((route) => {
            return route.path === subPath;
        });
    }
}
