---
import Ask from "blume:ask";
import data from "blume:data";
import { withBase } from "../islands/base-path.ts";
import type { ComponentOverride } from "../../core/define-components.ts";
import { EN_UI } from "../../core/i18n-ui.ts";
import type { UIStrings } from "../../core/i18n-ui.ts";
import type {
  LocaleSwitchOption,
  Navigation,
  NavSelector as NavSelectorConfig,
} from "../../core/types.ts";
import { GITHUB_MARK } from "../github-mark.ts";
import Icon from "../Icon.astro";
import LanguageSwitcher from "./LanguageSwitcher.astro";
import Logo from "./Logo.astro";
import { currentTabForRoute } from "./nav-utils.ts";
import NavSelector from "./NavSelector.astro";
import { resolveSlot } from "./overrides.ts";
import Search from "./Search.astro";

interface Props {
  site: { title: string };
  logo?: {
    svg?: string;
    light?: string;
    dark?: string;
    alt: string;
    href: string;
    text?: string;
  } | null;
  navigation: Navigation;
  route: string;
  searchEnabled: boolean;
  /**
   * Whether the search modal offers an "Ask AI" hand-off. Defaults to whether
   * Ask AI is configured, so no page has to pass it; a layout can still opt a
   * shell out explicitly.
   */
  askEnabled?: boolean;
  /** Localized Ask AI strings for the active locale. */
  askStrings?: UIStrings["ask"];
  // The mobile menu button toggles the docs sidebar drawer; custom pages
  // without a sidebar (e.g. a landing page) pass `false` to hide it.
  hasSidebar?: boolean;
  /**
   * Whether the layout renders a drawer for the nav toggle to open. A shell
   * with no drawer at all (the Scalar reference layout) passes `false`, else
   * the hamburger would lock page scroll with nothing appearing.
   */
  hasDrawer?: boolean;
  searchStrings?: UIStrings["search"];
  switcherStrings?: UIStrings["languageSwitcher"];
  /** Localized chrome labels (nav toggle, sections, GitHub, theme toggle). */
  navStrings?: UIStrings["nav"];
  localeSwitch?: LocaleSwitchOption[];
  /**
   * Auto-populated version switcher, rendered ahead of the configured
   * selectors. `null`/absent when versioning is off — or when the user
   * declares their own `kind: "version"` selector, which then owns the UI.
   */
  versionSelector?: NavSelectorConfig | null;
  /** Active locale for per-language search filtering. */
  searchLocale?: string;
  /** Viewed docs version for search filtering (`""` = current; `null`/absent = off). */
  searchVersion?: string | null;
  /**
   * Layout-slot overrides forwarded from the root layout. The header honors
   * `Logo` and `Search` here so those pieces can be replaced without swapping
   * the whole header.
   */
  layout?: Record<string, ComponentOverride>;
}

const {
  site,
  logo,
  navigation,
  route,
  searchEnabled,
  // `config.ask` is null whenever Ask AI is off, so the header is the one place
  // that has to know — the Ask trigger below and the search modal's hand-off to
  // it both switch on this, and every page gets both for free.
  askEnabled = Boolean(data.config.ask),
  askStrings,
  hasSidebar = true,
  hasDrawer = true,
  searchStrings,
  switcherStrings,
  navStrings,
  localeSwitch,
  versionSelector,
  searchLocale,
  searchVersion = null,
  layout = {},
} = Astro.props;

// Merge over the English defaults so a label missing from a translation (or
// from a not-yet-regenerated snapshot) still renders instead of coming out
// blank — the PageActions pattern.
const n = { ...EN_UI.nav, ...navStrings };

const LogoSlot = resolveSlot(layout.Logo, Logo);
const SearchSlot = resolveSlot(layout.Search, Search);

// The mobile nav-toggle opens a drawer of navigation. On docs pages that drawer
// is the sidebar tree (`hasSidebar`); on chrome-only pages (a landing page via
// PageLayout) it's a tabs-only drawer the layout renders — so the button is also
// needed whenever there are tabs to reveal.
const showNavToggle = hasDrawer && (hasSidebar || navigation.tabs.length > 0);
const activeTab = currentTabForRoute(navigation.tabs, route, navigation.root);
// Where the header's inline tab bar appears. With a sidebar it shares the `md`
// breakpoint with the docs drawer; without one, the tabs-only drawer is the sole
// mobile nav below `lg`, so the inline tabs wait until `lg` to avoid duplicating
// the drawer's links between `md` and `lg`.
const tabsNavClass = hasSidebar
  ? "hidden gap-1 md:flex"
  : "hidden gap-1 lg:flex";

const iconButton =
  "inline-flex size-9 cursor-pointer items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground";

// Delegated handlers for the theme toggle, mobile nav drawer, and banner
// dismiss. Living with the header means every page that renders it — docs pages
// and custom pages alike — gets identical behavior from one source.
//
// Toggling the theme flips colors; without suppressing transitions the animated
// styles fade while the non-animated ones snap, so we disable all transitions
// for the swap (next-themes' disableTransitionOnChange trick), force a
// synchronous style flush, then restore them on the next tick.
//
// The mobile drawer sits below the sticky header, but the banner above the
// header has no fixed height (its text wraps), so the drawer's top offset can't
// be static: on open we measure the header's on-screen bottom edge into
// `--blume-drawer-top` (read by the drawer's `top`/`height`), lock page scroll
// so the offset can't drift while open, and re-measure when a banner dismiss or
// a resize/rotation moves the header. Crossing into the desktop breakpoint
// (64rem, Tailwind `lg`) closes the drawer so the scroll lock can't outlive it.
const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-blume-header]");if(h){document.documentElement.style.setProperty("--blume-drawer-top",h.getBoundingClientRect().bottom+"px");}};document.addEventListener("click",(e)=>{const t=e.target.closest("[data-blume-theme-toggle]");if(t){const d=document.documentElement;const next=d.dataset.theme==="dark"?"light":"dark";const s=document.createElement("style");s.appendChild(document.createTextNode("*,*::before,*::after{transition:none!important}"));document.head.appendChild(s);d.dataset.theme=next;localStorage.setItem("blume-theme",next);window.getComputedStyle(d).opacity;setTimeout(()=>document.head.removeChild(s),1);}const n=e.target.closest("[data-blume-nav-toggle]");if(n){const d=document.documentElement;const open=d.toggleAttribute("data-blume-nav-open");if(open){dr();}d.style.overflow=open?"hidden":"";}const b=e.target.closest("[data-blume-banner-dismiss]");if(b){const bar=b.closest("[data-blume-banner]");const k=bar&&bar.getAttribute("data-banner-key");if(k){localStorage.setItem("blume-banner:"+k,"1");}document.documentElement.setAttribute("data-blume-banner-hidden","");requestAnimationFrame(dr);}});addEventListener("resize",()=>{const d=document.documentElement;if(!d.hasAttribute("data-blume-nav-open")){return;}if(matchMedia("(min-width:64rem)").matches){d.removeAttribute("data-blume-nav-open");d.style.overflow="";}else{dr();}});})();`;
---

<header
  class="sticky top-0 z-40 flex h-16 items-center gap-3 bg-background/90 px-4 backdrop-blur md:px-6"
  data-blume-header
>
  {
    showNavToggle && (
      <button
        aria-label={n.toggleNavigation}
        class={`${iconButton} lg:hidden`}
        data-blume-nav-toggle
        type="button"
      >
        <Icon name="menu" size={20} />
      </button>
    )
  }
  <LogoSlot logo={logo} site={site} />
  {
    navigation.selectors.length > 0 && (
      <div class="flex items-center gap-1.5">
        {navigation.selectors.map((selector) => (
          <NavSelector route={route} selector={selector} />
        ))}
      </div>
    )
  }
  {
    navigation.tabs.length > 0 && (
      <nav aria-label={n.sections} class={tabsNavClass}>
        {navigation.tabs.map((tab) => (
          <a
            aria-current={tab === activeTab ? "page" : undefined}
            class="rounded-full px-3 py-1.5 font-medium text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground aria-[current=page]:text-foreground"
            href={withBase(tab.href ?? tab.path)}
          >
            {tab.label}
          </a>
        ))}
      </nav>
    )
  }
  <div class="flex-1"></div>
  {
    /* Right-aligned next to the language switcher: the spacer absorbs its
       width, so pages without a version selector (blog, generated references)
       keep the logo, tabs, and the rest of this cluster in place — no layout
       shift when crossing into the docs. */
    versionSelector && (
      <NavSelector align="end" route={route} selector={versionSelector} />
    )
  }
  {
    localeSwitch && localeSwitch.length > 1 && (
      <LanguageSwitcher
        label={switcherStrings?.label ?? "Language"}
        options={localeSwitch}
        untranslatedLabel={switcherStrings?.untranslated ?? "Not translated"}
      />
    )
  }
  {
    searchEnabled && (
      <SearchSlot
        askEnabled={askEnabled}
        locale={searchLocale}
        navigation={navigation}
        popularPages={data.config.search.popular}
        strings={searchStrings}
        version={searchVersion}
      />
    )
  }
  <div class="flex items-center gap-2">
    {
      navigation.repoUrl && (
        <a
          aria-label={n.githubRepository}
          class={iconButton}
          href={navigation.repoUrl}
          rel="noreferrer"
          target="_blank"
        >
          <svg
            aria-hidden="true"
            fill="currentColor"
            height="18"
            set:html={GITHUB_MARK}
            viewBox="0 0 16 16"
            width="18"
            xmlns="http://www.w3.org/2000/svg"
          />
        </a>
      )
    }
    <button
      aria-label={n.toggleTheme}
      class={iconButton}
      data-blume-theme-toggle
      type="button"
    >
      <span class="inline-flex dark:hidden"><Icon name="sun" size={18} /></span>
      <span class="hidden dark:inline-flex"><Icon name="moon" size={18} /></span>
    </button>
    {askEnabled && <Ask strings={askStrings} />}
  </div>
</header>
<script is:inline set:html={clickScript} />
