export type AuthLevel = "public" | "code" | "user" | "admin";

export type Route = {
  path: string;
  auth: AuthLevel;
  exact?: boolean;
  view: () => Promise<HTMLElement> | HTMLElement;
};

export type AuthGuard = {
  canActivate: (auth: AuthLevel) => Promise<boolean>;
  onFail: (auth: AuthLevel, navigate: (path: string) => void) => void;
};

export class Router {
  private routes: Route[];
  private outlet: HTMLElement;
  private guard: AuthGuard;
  onRouteChange?: (path: string) => void;

  constructor(outlet: HTMLElement, routes: Route[], guard: AuthGuard) {
    this.routes = routes;
    this.outlet = outlet;
    this.guard = guard;
    window.addEventListener("popstate", () => this.resolve());
  }

  navigate(path: string) {
    window.history.pushState({}, "", path);
    this.resolve();
  }

  async resolve() {
    const path = window.location.pathname;

    const match = this.routes.find((r) =>
      r.exact
        ? r.path === path
        : path === r.path || path.startsWith(r.path + "/"),
    );

    const route = match ?? { auth: "public" as AuthLevel, view: null };

    const allowed = await this.guard.canActivate(route.auth);
    if (!allowed) {
      this.guard.onFail(route.auth, this.navigate.bind(this));
      return;
    }

    this.outlet.innerHTML = "";

    if (match) {
      const view = await match.view();
      this.outlet.append(view);
    } else {
      this.outlet.append(document.createElement("not-found-page"));
    }

    this.onRouteChange?.(path);
  }
}
