---
import "../styles/global.css";
import { createHash } from "node:crypto";

type NavLink = {
  label: string;
  href: string;
  description?: string;
  external?: boolean;
  group?: string;
};

type NavItem =
  | NavLink
  | {
      label: string;
      items: NavLink[];
    };

type Props = {
  title?: string;
  description?: string;
  theme?: string;
  brandHref?: string;
  brandTitle?: string;
  brandSubtitle?: string;
  navItems?: NavItem[];
  accessPassword?: string;
};

const {
  title = "Events",
  description = "Events organized by Neeldhara Misra.",
  theme = "default",
  brandHref = "/",
  brandTitle = "Events",
  brandSubtitle = "organized events and local guides",
  navItems: suppliedNavItems,
  accessPassword,
} = Astro.props;

const pageTitle = title === "Events" ? "Events" : `${title} - Events`;
const defaultNavItems: NavItem[] = [
  { label: "Organized", href: "/" },
  { label: "Attended", href: "/attended/" },
];
const navItems = suppliedNavItems ?? defaultNavItems;
const navVariant = suppliedNavItems ? "standard" : "toggle";
const pathname = Astro.url.pathname;
const accessPasswordHash = accessPassword
  ? createHash("sha256").update(accessPassword).digest("hex")
  : undefined;
const accessKey = `event-access:${brandHref}`;
const rybbitScriptSrc = "https://analytics.neeldhara.cloud/api/script.js";
const rybbitSiteId = "12";
const isMenu = (item: NavItem): item is Extract<NavItem, { items: NavLink[] }> => "items" in item;
const isCurrent = (href: string) => pathname === href;
const getMenuGroups = (items: NavLink[]) => {
  const groups: { label?: string; items: NavLink[] }[] = [];

  items.forEach((child) => {
    const groupLabel = child.group;
    let group = groups.find((candidate) => candidate.label === groupLabel);

    if (!group) {
      group = { label: groupLabel, items: [] };
      groups.push(group);
    }

    group.items.push(child);
  });

  return groups;
};
const getMenuLayout = (item: Extract<NavItem, { items: NavLink[] }>) => {
  const itemCount = item.items.length;
  const hasDescriptions = item.items.some((child) => child.description);
  const groups = getMenuGroups(item.items);
  const hasGroups = groups.some((group) => group.label);
  const columns = hasGroups ? Math.min(groups.length, 3) : itemCount >= 16 ? 3 : itemCount >= 7 ? 2 : 1;
  const width = itemCount >= 16 ? "58rem" : columns === 2 ? "38rem" : hasDescriptions ? "26rem" : "18rem";
  const density = itemCount >= 10 ? "compact" : hasDescriptions ? "descriptive" : "simple";

  return {
    columns,
    width,
    density,
    groups,
    hasGroups,
  };
};
---

<!doctype html>
<html lang="en" data-theme={theme}>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="description" content={description} />
    <title>{pageTitle}</title>
    <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
    {import.meta.env.PROD && <script is:inline src={rybbitScriptSrc} data-site-id={rybbitSiteId} defer></script>}
  </head>
  <body class:list={[accessPassword && "is-locked"]}>
    {accessPassword && (
      <section
        class="access-gate"
        data-access-gate
        data-access-hash={accessPasswordHash}
        data-access-key={accessKey}
        aria-labelledby="access-title"
      >
        <div class="access-gate__card">
          <p class="eyebrow">Private event page</p>
          <h1 id="access-title">Enter the workshop password</h1>
          <p>This event information is shared with workshop participants.</p>
          <form data-access-form>
            <label for="event-password">Password</label>
            <div class="access-gate__controls">
              <input
                id="event-password"
                name="password"
                type="password"
                required
                autocomplete="off"
                spellcheck="false"
                aria-describedby="access-error"
                data-access-input
              />
              <button type="submit" data-access-submit>Unlock</button>
            </div>
            <p class="access-gate__error" id="access-error" aria-live="polite" data-access-error></p>
          </form>
        </div>
      </section>
    )}
    <div data-protected-shell hidden={Boolean(accessPassword)}>
    <a class="skip-link" href="#content">Skip to content</a>
    <header class="site-header">
      <div class="site-header__inner">
        <a class="brand" href={brandHref} aria-label={`${brandTitle} home`}>
          <span class="brand__mark" aria-hidden="true">E</span>
          <span>
            <strong>{brandTitle}</strong>
            {brandSubtitle && <small>{brandSubtitle}</small>}
          </span>
        </a>
        <nav class:list={["site-nav", navVariant === "toggle" && "site-nav--toggle"]} aria-label="Main navigation">
          {
            navItems.map((item) =>
              isMenu(item) ? (() => {
                const menuLayout = getMenuLayout(item);
                return (
                  <details
                    class="nav-menu"
                    data-active={item.items.some((child) => isCurrent(child.href)) ? "true" : undefined}
                    data-density={menuLayout.density}
                    style={`--nav-menu-columns: ${menuLayout.columns}; --nav-menu-width: ${menuLayout.width};`}
                  >
                    <summary>{item.label}</summary>
                    <div class="nav-menu__panel" data-grouped={menuLayout.hasGroups ? "true" : undefined}>
                      {menuLayout.hasGroups ? (
                        menuLayout.groups.map((group) => (
                          <div class="nav-menu__group">
                            {group.label && <p class="nav-menu__group-label">{group.label}</p>}
                            {group.items.map((child) => (
                              <a
                                href={child.href}
                                target={child.external ? "_blank" : undefined}
                                rel={child.external ? "noreferrer" : undefined}
                                aria-current={isCurrent(child.href) ? "page" : undefined}
                                aria-label={child.external ? `${child.label} (external website)` : undefined}
                              >
                                <span>
                                  {child.label}
                                  {child.external && <span class="external-mark" aria-hidden="true">↗</span>}
                                </span>
                                {child.description && <small>{child.description}</small>}
                              </a>
                            ))}
                          </div>
                        ))
                      ) : (
                        item.items.map((child) => (
                          <a
                            href={child.href}
                            target={child.external ? "_blank" : undefined}
                            rel={child.external ? "noreferrer" : undefined}
                            aria-current={isCurrent(child.href) ? "page" : undefined}
                            aria-label={child.external ? `${child.label} (external website)` : undefined}
                          >
                            <span>
                              {child.label}
                              {child.external && <span class="external-mark" aria-hidden="true">↗</span>}
                            </span>
                            {child.description && <small>{child.description}</small>}
                          </a>
                        ))
                      )}
                    </div>
                  </details>
                );
              })() : (
                <a
                  href={item.href}
                  target={item.external ? "_blank" : undefined}
                  rel={item.external ? "noreferrer" : undefined}
                  aria-current={isCurrent(item.href) ? "page" : undefined}
                  aria-label={item.external ? `${item.label} (external website)` : undefined}
                >
                  {item.label}
                  {item.external && <span class="external-mark" aria-hidden="true">↗</span>}
                </a>
              )
            )
          }
        </nav>
      </div>
    </header>
    <main id="content" tabindex="-1">
      <slot />
    </main>
    <footer class="site-footer">
      <a class="site-footer__home" href="/">
        <strong>Events</strong>
        <p>Go back to all events</p>
      </a>
      <div class="site-footer__links">
        <a href="https://www.neeldhara.com">Neeldhara Misra</a>
        <a href="https://github.com/neeldhara">GitHub</a>
      </div>
    </footer>
    </div>
    {accessPassword && (
      <script is:inline>
        const accessGate = document.querySelector("[data-access-gate]");

        if (accessGate) {
          const protectedShell = document.querySelector("[data-protected-shell]");
          const accessForm = accessGate.querySelector("[data-access-form]");
          const passwordInput = accessGate.querySelector("[data-access-input]");
          const submitButton = accessGate.querySelector("[data-access-submit]");
          const errorMessage = accessGate.querySelector("[data-access-error]");
          const expectedHash = accessGate.getAttribute("data-access-hash");
          const storageKey = accessGate.getAttribute("data-access-key");

          const readAccess = () => {
            try {
              return storageKey && sessionStorage.getItem(storageKey) === expectedHash;
            } catch {
              return false;
            }
          };

          const rememberAccess = () => {
            try {
              if (storageKey && expectedHash) sessionStorage.setItem(storageKey, expectedHash);
            } catch {
              // Access still works for this page even when storage is unavailable.
            }
          };

          const hashPassword = async (value) => {
            const bytes = new TextEncoder().encode(value);
            const digest = await crypto.subtle.digest("SHA-256", bytes);
            return Array.from(new Uint8Array(digest))
              .map((byte) => byte.toString(16).padStart(2, "0"))
              .join("");
          };

          const unlock = (moveFocus = false) => {
            accessGate.hidden = true;
            if (protectedShell) protectedShell.hidden = false;
            document.body.classList.remove("is-locked");
            if (moveFocus) document.querySelector("#content")?.focus();
          };

          if (readAccess()) {
            unlock();
          } else {
            accessGate.hidden = false;
            if (protectedShell) protectedShell.hidden = true;

            if (window.matchMedia("(hover: hover) and (pointer: fine)").matches) {
              passwordInput?.focus();
            }

            accessForm?.addEventListener("submit", async (event) => {
              event.preventDefault();
              if (!passwordInput || !submitButton || !expectedHash) return;

              submitButton.disabled = true;
              errorMessage.textContent = "";
              passwordInput.setAttribute("aria-invalid", "false");

              const submittedHash = await hashPassword(passwordInput.value);

              if (submittedHash === expectedHash) {
                rememberAccess();
                unlock(true);
              } else {
                errorMessage.textContent = "That password is not correct. Please try again.";
                passwordInput.setAttribute("aria-invalid", "true");
                passwordInput.select();
                submitButton.disabled = false;
              }
            });
          }
        }
      </script>
    )}
  </body>
</html>
