// DocsLayout — Fumadocs-style three-pane shell for documentation.
//
//   ┌─────────────────────────────────────────────────────┐
//   │ TopBar (sticky)                                     │  ← brand · topNav · search · theme
//   ├──────────┬───────────────────────────────────────┬──┤
//   │ Sidebar  │ Main content                          │T │  ← TOC pane (right)
//   │ (sticky) │   - breadcrumbs                       │O │
//   │   nav    │   - title + description               │C │
//   │   groups │   - children                          │  │
//   │          │   - footer pager (prev / next)        │  │
//   │          │   - edit-on-github                    │  │
//   └──────────┴───────────────────────────────────────┴──┘
//
// The shell is dumb data-in / JSX-out — apps pass nav, brand, current
// page metadata, and Link from their router. All visual primitives
// (callouts, code blocks, steps) are siblings of this file and
// composable in any combination.

import { useEffect, useState, type ComponentType, type ReactNode } from 'react'
import { cn } from '../cn'
import type { ShellLinkProps } from './docShell'
import { ChevronLeftIcon, ChevronRightIcon } from '../primitives/docIcons'
import { PageHeader } from './pageHeader'

// ---------- Public data types ----------

export interface DocsNavItem {
  readonly label: string
  readonly href: string
  /** Visual hint shown next to the label (e.g. "new", "beta"). */
  readonly badge?: string
}

/** A collapsible sub-heading nested one level under a section. Its
 *  entries render indented; the chevron + open/close affordance mirror
 *  the top-level section. */
export interface DocsNavSubGroup {
  readonly label: string
  readonly entries: ReadonlyArray<DocsNavItem>
  readonly defaultOpen?: boolean
}

export interface DocsNavGroup {
  readonly section: string
  /** Entries shown directly under the section header, ABOVE the first
   *  sub-group. Use for a section's 1-2 intro pages. May be empty. */
  readonly entries: ReadonlyArray<DocsNavItem>
  /** Optional nested sub-groups. When present they render as
   *  collapsible sub-headings below `entries`. Sections with no
   *  meaningful taxonomy leave this empty and render flat. */
  readonly subgroups?: ReadonlyArray<DocsNavSubGroup>
  /** Flat entries rendered AFTER the sub-groups — a section's trailing
   *  ungrouped tail (e.g. plugins not assigned to a sub-group). Keeps
   *  them visible + in order without forcing every page into a group. */
  readonly trailingEntries?: ReadonlyArray<DocsNavItem>
  /** Collapse the group by default. Open groups stay open across
   *  navigations because the layout keeps its state in React. */
  readonly defaultOpen?: boolean
  /** Render the contents WITHOUT the section header (promoted one level),
   *  always expanded. Use when the sidebar is already scoped to this one
   *  section by an external switcher, so repeating the section name as a
   *  header would be redundant. */
  readonly headerless?: boolean
}

export interface DocsTocEntry {
  readonly id: string
  readonly text: string
  readonly level: 2 | 3
}

export interface DocsBreadcrumb {
  readonly label: string
  readonly href?: string
}

/** User-facing strings for the layout's own chrome. Defaults are English
 *  so the layout works unconfigured; pass catalog values to localize. */
export interface DocsLayoutLabels {
  /** `aria-label` of the mobile menu-open button. Default `'Toggle menu'`. */
  readonly toggleMenu?: string
  /** `aria-label` of the mobile drawer backdrop. Default `'Close menu'`. */
  readonly closeMenu?: string
  /** `aria-label` of the drawer close button. Default `'Close'`. */
  readonly close?: string
  /** Heading of the mobile drawer. Default `'Menu'`. */
  readonly menu?: string
  /** Heading above the table of contents. Default `'On this page'`. */
  readonly onThisPage?: string
  /** Edit-on-GitHub link text. Default `'Edit this page on GitHub →'`. */
  readonly editPage?: string
  /** Prefix before the last-updated date. Default `'Updated'`. */
  readonly updated?: string
  /** "Previous page" pager caption. Default `'Previous'`. */
  readonly previous?: string
  /** "Next page" pager caption. Default `'Next'`. */
  readonly next?: string
  /** `aria-label` of the prev/next pager nav. Default `'Pagination'`. */
  readonly pagination?: string
  /** `aria-label` of the breadcrumb nav. Default `'Breadcrumb'`. */
  readonly breadcrumb?: string
}

const DEFAULT_DOCS_LABELS: Required<DocsLayoutLabels> = {
  toggleMenu: 'Toggle menu',
  closeMenu: 'Close menu',
  close: 'Close',
  menu: 'Menu',
  onThisPage: 'On this page',
  editPage: 'Edit this page on GitHub →',
  updated: 'Updated',
  previous: 'Previous',
  next: 'Next',
  pagination: 'Pagination',
  breadcrumb: 'Breadcrumb',
}

// ---------- Layout props ----------

interface DocsLayoutProps {
  readonly brand: ReactNode
  readonly nav: ReadonlyArray<DocsNavGroup>
  readonly children: ReactNode
  /** Current pathname — used to highlight the active nav entry +
   *  decide which group opens by default. */
  readonly currentPath?: string

  /** Top-bar slots — render between brand and the right-hand actions. */
  readonly topNav?: ReactNode
  /** Right-hand actions in the top bar (theme toggle, github link, …). */
  readonly topRight?: ReactNode

  /** Optional search slot in the top bar. Drop in `<SearchTrigger
   *  onClick={…} />` to wire up the kit's cmd-k modal, or any custom
   *  search button you want. */
  readonly search?: ReactNode

  /** Per-page metadata — when set, the layout renders the breadcrumbs,
   *  title, description, TOC, prev/next pager, and edit-on-github
   *  link. Drop this for landing-style index pages. */
  readonly page?: {
    readonly title: string
    readonly description?: string
    readonly section?: string
    readonly breadcrumbs?: ReadonlyArray<DocsBreadcrumb>
    readonly toc?: ReadonlyArray<DocsTocEntry>
    readonly prev?: { readonly href: string; readonly label: string; readonly description?: string }
    readonly next?: { readonly href: string; readonly label: string; readonly description?: string }
    readonly editUrl?: string
    readonly lastUpdated?: string
  }

  /** Optional client-router Link. Same contract as DocShell. */
  readonly LinkComponent?: ComponentType<ShellLinkProps>

  readonly className?: string
  /** Override the layout's own chrome strings. Defaults are English. */
  readonly labels?: DocsLayoutLabels
  /** Optional footer node — rendered at the very bottom inside the
   *  content column. */
  readonly footer?: ReactNode
  /** Optional node pinned to the TOP of the sidebar (above the nav
   *  groups), in BOTH the desktop aside and the mobile drawer. Use for
   *  a section/category switcher ("root toggle"). Omitted → the sidebar
   *  renders exactly as before. */
  readonly sidebarHeader?: ReactNode
}

const PlainLink = ({ to, className, children }: ShellLinkProps): ReactNode => (
  <a href={to} className={className}>{children}</a>
)

const isActiveHref = (href: string, currentPath: string | undefined): boolean => {
  if (!currentPath) return false
  if (currentPath === href) return true
  // Treat /docs/foo/ as a match for /docs/foo (trailing slash tolerance).
  if (currentPath.replace(/\/$/, '') === href.replace(/\/$/, '')) return true
  return false
}

// ---------- Sidebar nav group (collapsible) ----------

// Shared row styling — leaf links AND collapsible sub-group triggers
// render identically (same size, colour, padding, left-rail indicator);
// a sub-group only adds a chevron on the right. Keeps the sidebar one
// uniform list instead of two visually distinct tiers.
const NAV_ROW = 'relative w-full py-1.5 pl-4 pr-2 -ml-px text-sm text-left transition-colors border-l-2'
const navRowColor = (active: boolean): string =>
  active
    ? 'text-foreground font-medium border-primary'
    : 'text-muted-foreground hover:text-foreground border-transparent hover:border-border'

// One leaf link row.
const NavLeaf = ({
  entry, currentPath, Link,
}: {
  readonly entry: DocsNavItem
  readonly currentPath?: string
  readonly Link: ComponentType<ShellLinkProps>
}): ReactNode => {
  const active = isActiveHref(entry.href, currentPath)
  return (
    <li>
      <Link to={entry.href} prefetch className={cn(NAV_ROW, 'block', navRowColor(active))}>
        <span className="inline-flex items-center gap-2">
          {entry.label}
          {entry.badge ? (
            <span className="text-[0.6rem] uppercase tracking-wider px-1.5 py-0.5 rounded bg-primary/10 text-primary font-semibold">
              {entry.badge}
            </span>
          ) : null}
        </span>
      </Link>
    </li>
  )
}

// A collapsible sub-group. Its trigger row is styled EXACTLY like a leaf
// (NAV_ROW) plus a right-aligned chevron; children render in a further-
// indented nested rail when open. Expanded by default when it holds the
// active page.
const SidebarSubGroup = ({
  subgroup, currentPath, Link,
}: {
  readonly subgroup: DocsNavSubGroup
  readonly currentPath?: string
  readonly Link: ComponentType<ShellLinkProps>
}): ReactNode => {
  const containsActive = subgroup.entries.some((e) => isActiveHref(e.href, currentPath))
  const [open, setOpen] = useState(subgroup.defaultOpen ?? containsActive)
  useEffect(() => {
    if (containsActive) setOpen(true)
  }, [containsActive])

  return (
    <li>
      <button
        type="button"
        onClick={() => setOpen(!open)}
        aria-expanded={open}
        className={cn(NAV_ROW, 'flex items-center justify-between gap-2', navRowColor(false))}
      >
        <span>{subgroup.label}</span>
        <svg
          width="10" height="10" viewBox="0 0 10 10" fill="none"
          className={cn('shrink-0 transition-transform', open ? 'rotate-90' : '')}
          aria-hidden="true"
        >
          <path d="M3 1.5 L6.5 5 L3 8.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
        </svg>
      </button>
      {open ? (
        <ul className="border-l border-border ml-4">
          {subgroup.entries.map((e) => (
            <NavLeaf key={e.href} entry={e} {...(currentPath ? { currentPath } : {})} Link={Link} />
          ))}
        </ul>
      ) : null}
    </li>
  )
}

const SidebarGroup = ({
  group, currentPath, Link,
}: {
  readonly group: DocsNavGroup
  readonly currentPath?: string
  readonly Link: ComponentType<ShellLinkProps>
}): ReactNode => {
  // A section is "open" if explicitly defaultOpen OR contains the active
  // entry (directly or in any sub-group). Local state lets the user
  // toggle either way after that.
  const subgroups = group.subgroups ?? []
  const trailingEntries = group.trailingEntries ?? []
  const headerless = group.headerless ?? false
  const containsActive =
    group.entries.some((e) => isActiveHref(e.href, currentPath)) ||
    subgroups.some((sg) => sg.entries.some((e) => isActiveHref(e.href, currentPath))) ||
    trailingEntries.some((e) => isActiveHref(e.href, currentPath))
  const [open, setOpen] = useState(group.defaultOpen ?? containsActive ?? true)
  useEffect(() => {
    if (containsActive) setOpen(true)
  }, [containsActive])

  return (
    <div className="mb-4">
      {headerless ? null : (
        <button
          type="button"
          onClick={() => setOpen(!open)}
          className="flex w-full items-center justify-between text-[0.7rem] uppercase tracking-[0.08em] text-muted-foreground font-semibold py-1.5 hover:text-foreground transition-colors"
          aria-expanded={open}
        >
          <span>{group.section}</span>
          <svg
            width="10" height="10" viewBox="0 0 10 10" fill="none"
            className={cn('transition-transform', open ? 'rotate-90' : '')}
            aria-hidden="true"
          >
            <path d="M3 1.5 L6.5 5 L3 8.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
          </svg>
        </button>
      )}
      {(headerless || open) && (group.entries.length > 0 || subgroups.length > 0 || trailingEntries.length > 0) ? (
        <ul className="mt-1 border-l border-border ml-[3px]">
          {group.entries.map((e) => (
            <NavLeaf key={e.href} entry={e} {...(currentPath ? { currentPath } : {})} Link={Link} />
          ))}
          {subgroups.map((sg) => (
            <SidebarSubGroup key={sg.label} subgroup={sg} {...(currentPath ? { currentPath } : {})} Link={Link} />
          ))}
          {trailingEntries.map((e) => (
            <NavLeaf key={e.href} entry={e} {...(currentPath ? { currentPath } : {})} Link={Link} />
          ))}
        </ul>
      ) : null}
    </div>
  )
}

// ---------- TOC aside (right column) ----------

const TocAside = ({ toc, editUrl, lastUpdated, t }: {
  readonly toc: ReadonlyArray<DocsTocEntry>
  readonly editUrl?: string
  readonly lastUpdated?: string
  readonly t: Required<DocsLayoutLabels>
}): ReactNode => (
  <aside className="hidden xl:block w-56 shrink-0 pl-8">
    <div className="sticky top-24">
      {toc.length > 0 ? (
        <>
          <div className="text-[0.7rem] uppercase tracking-[0.08em] text-muted-foreground font-semibold mb-3">
            {t.onThisPage}
          </div>
          <ul className="space-y-1.5 text-sm border-l border-border">
            {toc.map((item) => (
              <li key={item.id}>
                <a
                  href={`#${item.id}`}
                  className={cn(
                    'block py-0.5 -ml-px border-l-2 border-transparent text-muted-foreground hover:text-foreground hover:border-border transition-colors data-[active=true]:border-primary data-[active=true]:text-foreground data-[active=true]:font-medium',
                    item.level === 3 ? 'pl-6' : 'pl-3',
                  )}
                  data-toc-link={item.id}
                >
                  {item.text}
                </a>
              </li>
            ))}
          </ul>
        </>
      ) : null}
      {(editUrl || lastUpdated) ? (
        <div className={cn('text-xs text-muted-foreground space-y-1.5', toc.length > 0 ? 'mt-6 pt-6 border-t border-border' : '')}>
          {editUrl ? (
            <a
              href={editUrl}
              target="_blank"
              rel="noopener noreferrer"
              className="block hover:text-foreground transition-colors"
            >
              {t.editPage}
            </a>
          ) : null}
          {lastUpdated ? <div>{t.updated} {lastUpdated}</div> : null}
        </div>
      ) : null}
    </div>
  </aside>
)

// ---------- Footer pager ----------

const FooterPager = ({
  prev, next, Link, t,
}: {
  readonly prev?: { readonly href: string; readonly label: string; readonly description?: string }
  readonly next?: { readonly href: string; readonly label: string; readonly description?: string }
  readonly Link: ComponentType<ShellLinkProps>
  readonly t: Required<DocsLayoutLabels>
}): ReactNode => {
  if (!prev && !next) return null
  return (
    <nav aria-label={t.pagination} className="mt-16 pt-8 border-t border-border grid grid-cols-1 md:grid-cols-2 gap-4">
      <div>
        {prev ? (
          <Link
            to={prev.href}
            prefetch
            className="group block p-4 border border-border rounded-lg hover:border-primary/50 hover:bg-card/40 transition-colors h-full"
          >
            <div className="text-xs text-muted-foreground mb-1 inline-flex items-center gap-1">
              <ChevronLeftIcon className="w-3 h-3 transition-transform group-hover:-translate-x-0.5" />
              {t.previous}
            </div>
            <div className="font-medium group-hover:text-primary transition-colors">{prev.label}</div>
            {prev.description ? (
              <div className="text-xs text-muted-foreground mt-1 line-clamp-1">{prev.description}</div>
            ) : null}
          </Link>
        ) : null}
      </div>
      <div className="md:text-right">
        {next ? (
          <Link
            to={next.href}
            prefetch
            className="group block p-4 border border-border rounded-lg hover:border-primary/50 hover:bg-card/40 transition-colors h-full"
          >
            <div className="text-xs text-muted-foreground mb-1 inline-flex items-center gap-1 md:justify-end">
              {t.next}
              <ChevronRightIcon className="w-3 h-3 transition-transform group-hover:translate-x-0.5" />
            </div>
            <div className="font-medium group-hover:text-primary transition-colors">{next.label}</div>
            {next.description ? (
              <div className="text-xs text-muted-foreground mt-1 line-clamp-1">{next.description}</div>
            ) : null}
          </Link>
        ) : null}
      </div>
    </nav>
  )
}

// ---------- Breadcrumbs ----------

const Breadcrumbs = ({
  items, Link, ariaLabel,
}: {
  readonly items: ReadonlyArray<DocsBreadcrumb>
  readonly Link: ComponentType<ShellLinkProps>
  readonly ariaLabel: string
}): ReactNode => (
  <nav aria-label={ariaLabel} className="text-xs text-muted-foreground mb-3 flex items-center gap-1.5 flex-wrap">
    {items.map((c, i) => {
      const isLast = i === items.length - 1
      return (
        <span key={i} className="inline-flex items-center gap-1.5">
          {c.href && !isLast ? (
            <Link to={c.href} className="hover:text-foreground transition-colors">{c.label}</Link>
          ) : (
            <span className={isLast ? 'text-foreground' : ''}>{c.label}</span>
          )}
          {!isLast ? <span className="text-muted-foreground/50" aria-hidden="true">/</span> : null}
        </span>
      )
    })}
  </nav>
)

// ---------- The layout itself ----------

export const DocsLayout = ({
  brand, nav, children, currentPath, topNav, topRight, search,
  page, LinkComponent = PlainLink, className, labels, footer, sidebarHeader,
}: DocsLayoutProps): ReactNode => {
  const Link = LinkComponent
  const t = { ...DEFAULT_DOCS_LABELS, ...labels }
  // Mobile drawer state — only the inline state, no portal/escape
  // handling here; the search-trigger button can call setMobileOpen
  // via a parent island when full cmd-k lands.
  const [mobileOpen, setMobileOpen] = useState(false)

  return (
    <div className={cn('min-h-screen bg-background text-foreground', className)}>
      {/* Top bar — shared <PageHeader> chrome. The PageHeader owns
       * sticky / max-width / h-16 / glass-on-scroll fade. The
       * children below define the docs-specific row: mobile menu,
       * brand, optional topNav, search, topRight slots. */}
      <PageHeader>
        <div className="flex items-center gap-4 min-w-0 flex-1">
          <button
            type="button"
            onClick={() => setMobileOpen(!mobileOpen)}
            className="md:hidden inline-flex items-center justify-center w-8 h-8 rounded-md hover:bg-card transition-colors"
            aria-label={t.toggleMenu}
            aria-expanded={mobileOpen}
          >
            <svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true">
              <path d="M3 5 H15 M3 9 H15 M3 13 H15" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
            </svg>
          </button>
          <div className="flex items-center gap-2 font-semibold">{brand}</div>
          {topNav ? <div className="hidden md:flex items-center gap-1 ml-4">{topNav}</div> : null}
        </div>
        <div className="flex items-center gap-3">
          {search ? <div className="hidden md:block">{search}</div> : null}
          {topRight ? <div className="flex items-center gap-1">{topRight}</div> : null}
        </div>
      </PageHeader>

      <div className="mx-auto max-w-7xl flex">
        {/* Sidebar (desktop) */}
        <aside className="hidden md:block w-64 shrink-0 border-r border-border">
          <div className="sticky top-16 max-h-[calc(100vh-4rem)] overflow-y-auto px-6 py-8">
            {sidebarHeader ? <div className="mb-6">{sidebarHeader}</div> : null}
            {nav.map((group) => (
              <SidebarGroup key={group.section} group={group} {...(currentPath ? { currentPath } : {})} Link={Link} />
            ))}
          </div>
        </aside>

        {/* Sidebar (mobile drawer) */}
        {mobileOpen ? (
          <div className="md:hidden fixed inset-0 z-40 flex">
            {/* Backdrop */}
            <div
              role="button"
              aria-label={t.closeMenu}
              className="flex-1 bg-background/70 backdrop-blur-sm"
              onClick={() => setMobileOpen(false)}
            />
            <aside className="w-72 max-w-[80vw] bg-card border-l border-border overflow-y-auto px-6 py-6">
              <div className="mb-4 flex items-center justify-between">
                <span className="font-semibold">{t.menu}</span>
                <button
                  type="button"
                  onClick={() => setMobileOpen(false)}
                  className="inline-flex items-center justify-center w-7 h-7 rounded-md hover:bg-background transition-colors"
                  aria-label={t.close}
                >
                  <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
                    <path d="M3 3 L11 11 M11 3 L3 11" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
                  </svg>
                </button>
              </div>
              {sidebarHeader ? <div className="mb-4">{sidebarHeader}</div> : null}
              {nav.map((group) => (
                <SidebarGroup key={group.section} group={group} {...(currentPath ? { currentPath } : {})} Link={Link} />
              ))}
            </aside>
          </div>
        ) : null}

        {/* Content + TOC */}
        <div className="flex-1 min-w-0 flex">
          <main className="flex-1 min-w-0 px-6 md:px-10 py-10 max-w-3xl">
            {page?.breadcrumbs && page.breadcrumbs.length > 0 ? (
              <Breadcrumbs items={page.breadcrumbs} Link={Link} ariaLabel={t.breadcrumb} />
            ) : null}
            {page?.title ? (
              <h1 className="text-3xl md:text-4xl font-bold tracking-tight text-foreground mb-3">
                {page.title}
              </h1>
            ) : null}
            {page?.description ? (
              <p className="text-base md:text-lg text-muted-foreground mb-8 leading-relaxed">
                {page.description}
              </p>
            ) : null}

            {children}

            <FooterPager
              {...(page?.prev ? { prev: page.prev } : {})}
              {...(page?.next ? { next: page.next } : {})}
              Link={Link}
              t={t}
            />

            {footer ? <div className="mt-16 pt-8 border-t border-border">{footer}</div> : null}
          </main>

          {page?.toc !== undefined ? (
            <TocAside
              toc={page.toc}
              {...(page.editUrl ? { editUrl: page.editUrl } : {})}
              {...(page.lastUpdated ? { lastUpdated: page.lastUpdated } : {})}
              t={t}
            />
          ) : null}
        </div>
      </div>
    </div>
  )
}
