import { AgnosticRouteMatch } from "@remix-run/router";
import Hostname from "../../Types/API/Hostname";
import Route from "../../Types/API/Route";
import URL from "../../Types/API/URL";
import Dictionary from "../../Types/Dictionary";
import BadDataException from "../../Types/Exception/BadDataException";
import ObjectID from "../../Types/ObjectID";
import {
  Location,
  NavigateFunction,
  Params,
  matchRoutes,
} from "react-router-dom";

abstract class Navigation {
  private static navigateHook: NavigateFunction;
  private static location: Location;
  private static params: Params;

  public static setNavigateHook(navigateHook: NavigateFunction): void {
    this.navigateHook = navigateHook;
  }

  public static setLocation(location: Location): void {
    this.location = location;
  }

  public static setParams(params: Params): void {
    this.params = params;
  }

  public static getParams(): Params {
    return this.params;
  }

  public static getCurrentPath(): Route {
    return new Route(window.location.pathname);
  }

  public static getBreadcrumbRoute(level: number): Route {
    const paths: Array<string> = this.location.pathname.split("/");
    // +2 because we want to include the first 2 paths which are empty, dashboard and project id
    const indexToSplice: number = level + 2;
    return new Route(paths.splice(0, indexToSplice).join("/"));
  }

  public static getRoutePath(routes: Array<{ path: string }>): string {
    const pathes: AgnosticRouteMatch[] | null = matchRoutes(
      routes,
      this.location.pathname,
    );
    return pathes?.[0]?.route.path || "";
  }

  public static getQueryStringByName(paramName: string): string | null {
    const urlSearchParams: URLSearchParams = new URLSearchParams(
      window.location.search,
    );
    const params: Dictionary<string> = Object.fromEntries(
      urlSearchParams.entries(),
    );
    if (params && params[paramName]) {
      return params[paramName] as string;
    }

    return null;
  }

  public static getParamByName(
    paramName: string,
    routeTemplate: Route,
  ): string | null {
    const currentPath: Array<string> = this.location.pathname.split("/");

    if (!paramName.startsWith(":")) {
      paramName = ":" + paramName;
    }

    const routeParamTemplateIndex: number = routeTemplate
      .toString()
      .split("/")
      .indexOf(paramName);

    if (routeParamTemplateIndex === -1) {
      throw new BadDataException(
        `Param ${paramName} not found in template ${routeTemplate.toString()}`,
      );
    }

    if (currentPath[routeParamTemplateIndex]) {
      return currentPath[routeParamTemplateIndex] as string;
    }

    return null;
  }

  public static getLastParam(getFromLastRoute?: number): Route | null {
    return URL.fromString(window.location.href).getLastRoute(getFromLastRoute);
  }

  public static getFirstParam(getFromFirstRoute?: number): string | undefined {
    const pathname: string = window.location.pathname;

    return pathname.split("/")[getFromFirstRoute || 1];
  }

  public static getLastParamAsString(getFromLastRoute?: number): string {
    const param: Route | null = URL.fromString(
      window.location.href,
    ).getLastRoute(getFromLastRoute);

    return param?.toString().replace("/", "") || "";
  }

  public static getLastParamAsObjectID(getFromLastRoute?: number): ObjectID {
    return new ObjectID(this.getLastParamAsString(getFromLastRoute));
  }

  public static getCurrentRoute(): Route {
    return new Route(this.location.pathname);
  }

  public static getHostname(): Hostname {
    return new Hostname(window.location.hostname);
  }

  public static getCurrentURL(): URL {
    return URL.fromString(window.location.href);
  }

  public static reload(): void {
    window.location.reload();
  }

  public static containsInPath(text: string): boolean {
    return window.location.pathname.includes(text);
  }

  public static isStartWith(route: Route): boolean {
    const current: Route = this.getCurrentRoute();
    const routeItems: Array<string> = route.toString().split("/");
    let anotherRouteItems: Array<string> = current.toString().split("/");

    if (routeItems.length > anotherRouteItems.length) {
      return false;
    }

    anotherRouteItems = anotherRouteItems.splice(0, routeItems.length);

    let start: number = 0;
    let startsWith: boolean = true;
    for (const item of anotherRouteItems) {
      if (routeItems[start]?.startsWith(":") && item) {
        start++;
        continue;
      }

      if (routeItems[start]?.toString() !== item.toString()) {
        startsWith = false;
        break;
      }

      start++;
    }
    return startsWith;
  }

  public static isOnThisPage(route: Route | URL): boolean {
    if (route instanceof Route) {
      const current: Route = this.getCurrentRoute();

      let isOnThisPage: boolean = true;

      const routeItems: Array<string> = route.toString().split("/");
      const currentPathItems: Array<string> = current.toString().split("/");
      if (routeItems.length !== currentPathItems.length) {
        return false;
      }

      let start: number = 0;
      for (const item of currentPathItems) {
        if (routeItems[start]?.startsWith(":") && item) {
          start++;
          continue;
        }

        if (routeItems[start]?.toString() !== item.toString()) {
          isOnThisPage = false;
          break;
        }

        start++;
      }

      return isOnThisPage;
    }

    if (route instanceof URL) {
      const current: URL = this.getCurrentURL();

      if (current.toString() === route.toString()) {
        return true;
      }

      return false;
    }

    return false;
  }

  public static goBack(): void {
    this.navigateHook(-1);
  }

  public static navigate(
    to: Route | URL,
    options?: {
      openInNewTab?: boolean | undefined;
      forceNavigate?: boolean | undefined;
    },
  ): void {
    const finalUrl: string = to.toString();

    // add query params if they exist.

    if (options?.openInNewTab) {
      // open in new tab
      window.open(finalUrl, "_blank");
      return;
    }

    if (options?.forceNavigate && to instanceof Route) {
      window.location.href = finalUrl;
    }

    if (this.navigateHook && to instanceof Route && !this.isOnThisPage(to)) {
      this.navigateHook(finalUrl);
    }

    // if its an external link outside of react.
    if (to instanceof URL) {
      window.location.href = finalUrl;
    }
  }
}

export default Navigation;
