type PageHandler = (
  outlet: HTMLElement,
  params: Record<string, string>,
) => void | Promise<void>;

interface Route {
  pattern: RegExp;
  keys: string[];
  handler: PageHandler;
  title: string;
}

const routes: Route[] = [];
let notFoundHandler: PageHandler | null = null;
let notFoundTitle = "404 - Jet Pham";

export function route(path: string, title: string, handler: PageHandler) {
  if (path === "*") {
    notFoundHandler = handler;
    notFoundTitle = title;
    return;
  }
  const keys: string[] = [];
  const pattern = path.replace(/:(\w+)/g, (_, key: string) => {
    keys.push(key);
    return "([^/]+)";
  });
  routes.push({ pattern: new RegExp(`^${pattern}$`), keys, handler, title });
}

export function navigate(path: string) {
  history.pushState(null, "", path);
  window.scrollTo({ top: 0, behavior: "auto" });
  void render();
}

function updateNavState(path: string) {
  const navLinks =
    document.querySelectorAll<HTMLAnchorElement>("[data-nav-link]");
  navLinks.forEach((link) => {
    if (link.pathname === path) {
      link.setAttribute("aria-current", "page");
    } else {
      link.removeAttribute("aria-current");
    }
  });
}

async function render() {
  const path = location.pathname;
  const outlet = document.getElementById("outlet")!;
  updateNavState(path);

  for (const r of routes) {
    const match = path.match(r.pattern);
    if (match) {
      const params: Record<string, string> = {};
      r.keys.forEach((key, i) => {
        params[key] = match[i + 1]!;
      });
      outlet.innerHTML = "";
      await r.handler(outlet, params);
      document.title = r.title;
      document.dispatchEvent(new Event("site:render"));
      return;
    }
  }

  outlet.innerHTML = "";
  if (notFoundHandler) {
    await notFoundHandler(outlet, {});
  }
  document.title = notFoundTitle;
  document.dispatchEvent(new Event("site:render"));
}

export function initRouter() {
  window.addEventListener("popstate", () => void render());

  document.addEventListener("click", (e) => {
    const anchor = (e.target as HTMLElement).closest("a");
    if (
      anchor?.origin === location.origin &&
      !anchor.hash &&
      !anchor.hasAttribute("data-native-link") &&
      !anchor.hasAttribute("download") &&
      !anchor.hasAttribute("target")
    ) {
      e.preventDefault();
      navigate(anchor.pathname);
    }
  });

  void render();
}
