/**
 * SidebarComponent - Programmatic sidebar with sections, badges, and footer
 * @countermeasure/web-components/sidebar/component
 *
 * Builds the sidebar DOM from a config object and owns the common app-shell
 * behavior expected by CounterMeasure product surfaces: active route state,
 * persisted collapse, mobile drawer state, keyboard navigation, tenant/scope
 * selection, badges, and footer account chrome.
 */

import { createBrandLockupElement } from '../components/brand'
import { getIconSvgInner, type IconSet } from '../icons/index'
import { resolveContainer } from '../primitives/utils'
import { setSafeIconHtml } from '../utils/sanitize'
import {
  type SidebarBadgeConfig,
  type SidebarBadgeTone,
  type SidebarComponentConfig,
  type SidebarNavItem,
  type SidebarSection,
} from './types'

const DEFAULT_MOBILE_BREAKPOINT = 900
const FALLBACK_URL_BASE = 'http://localhost/'

interface NormalizedBadge {
  label: string
  title?: string
  tone: SidebarBadgeTone
  visible: boolean
  dot: boolean
}

function toSidebarVariantClassName(variant: string): string {
  const normalized = variant
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '')
  return `sidebar--${normalized || 'app'}`
}

function toInitials(name: string): string {
  const words = name.trim().split(/\s+/).filter(Boolean)

  if (words.length >= 2) {
    return `${words[0]?.charAt(0) ?? ''}${words[words.length - 1]?.charAt(0) ?? ''}`.toUpperCase()
  }

  return name.slice(0, 2).toUpperCase()
}

function canUseStorage(): boolean {
  try {
    return typeof window !== 'undefined' && window.localStorage !== undefined
  } catch {
    return false
  }
}

function toPathname(value: string): string {
  try {
    return new URL(value, FALLBACK_URL_BASE).pathname
  } catch {
    return value.split(/[?#]/, 1)[0] ?? value
  }
}

function isMobileViewport(breakpoint: number): boolean {
  return typeof window !== 'undefined' && window.innerWidth <= breakpoint
}

function normalizeBadge(
  badge: number | SidebarBadgeConfig | undefined,
  fallbackTone: SidebarBadgeTone | undefined
): NormalizedBadge | null {
  if (badge === undefined) {
    return null
  }

  if (typeof badge === 'number') {
    return {
      label: badge > 0 ? String(badge) : '',
      tone: fallbackTone ?? 'primary',
      visible: badge > 0,
      dot: badge > 0,
    }
  }

  const label = badge.label
  const normalized: NormalizedBadge = {
    label,
    tone: badge.tone ?? fallbackTone ?? 'primary',
    visible: label.length > 0 || badge.dot === true,
    dot: badge.dot ?? label.length > 0,
  }
  if (badge.title !== undefined) {
    normalized.title = badge.title
  }
  return normalized
}

/** Create an SVG element populated with icon content from the registry. */
function createIconSvg(
  iconSet: IconSet,
  name: string,
  className = 'sidebar__item-icon'
): SVGElement {
  const svgNs = 'http://www.w3.org/2000/svg'
  const svg = document.createElementNS(svgNs, 'svg')
  svg.setAttribute('xmlns', svgNs)
  svg.setAttribute('width', '16')
  svg.setAttribute('height', '16')
  svg.setAttribute('viewBox', '0 0 24 24')
  svg.setAttribute('fill', 'none')
  svg.setAttribute('stroke', 'currentColor')
  svg.setAttribute('stroke-width', '2')
  svg.setAttribute('stroke-linecap', 'round')
  svg.setAttribute('stroke-linejoin', 'round')
  svg.classList.add(className)
  svg.setAttribute('data-icon', name)
  svg.setAttribute('aria-hidden', 'true')

  const inner = getIconSvgInner(iconSet, name)
  if (inner) {
    setSafeIconHtml(svg, inner)
  }

  return svg
}

function appendElements(
  parent: HTMLElement,
  elementOrElements?: HTMLElement | HTMLElement[]
): void {
  if (elementOrElements === undefined) {
    return
  }

  const elements = Array.isArray(elementOrElements) ? elementOrElements : [elementOrElements]
  for (const element of elements) {
    parent.appendChild(element)
  }
}

export class SidebarComponent {
  private readonly container: HTMLElement
  private readonly aside: HTMLElement
  private readonly config: SidebarComponentConfig
  private readonly iconSet: IconSet
  private readonly variant: string
  private readonly cleanupFns: (() => void)[] = []
  private collapseButton: HTMLButtonElement | null = null
  private backdrop: HTMLElement | null = null

  constructor(config: SidebarComponentConfig) {
    this.config = config
    this.container = resolveContainer(config.container)
    this.iconSet = config.iconSet ?? 'lucide'
    this.variant = config.variant ?? 'app'

    this.aside = document.createElement('aside')
    this.aside.classList.add('sidebar', toSidebarVariantClassName(this.variant))
    if (this.variant === 'threat-library' || this.variant === 'cm') {
      this.aside.classList.add('sidebar--product')
    }
    this.aside.setAttribute('data-sidebar', this.variant)
    this.aside.setAttribute('data-open', String(config.open === true))
    this.aside.setAttribute('aria-label', 'Primary navigation')

    if (this.resolveInitialCollapsed()) {
      this.aside.classList.add('sidebar--collapsed')
    }

    this.render()
    this.container.appendChild(this.aside)

    if (config.mobileBackdrop === true) {
      this.renderBackdrop()
    }

    this.setupBehavior()
  }

  private render(): void {
    if (this.config.collapsible === true) {
      this.renderCollapseButton()
    }

    this.renderHeader()
    this.renderScope()

    const body = document.createElement('nav')
    body.classList.add('sidebar__body')
    body.setAttribute('aria-label', 'Primary')

    appendElements(body, this.config.beforeNav)

    for (const section of this.config.sections) {
      body.appendChild(this.buildSection(section))
    }

    appendElements(body, this.config.afterNav)

    this.aside.appendChild(body)
    this.renderFooter()

    if (this.config.activeItemId !== undefined) {
      this.setActive(this.config.activeItemId)
    }
  }

  private buildSection(section: SidebarSection): HTMLElement {
    const sectionEl = document.createElement('div')
    sectionEl.classList.add('sidebar__section')
    sectionEl.setAttribute('data-section-id', section.id)

    const collapsible = section.collapsible === true
    const expanded = section.defaultExpanded !== false

    if (collapsible && section.label) {
      sectionEl.classList.add('sidebar__section--collapsible')
      if (expanded) sectionEl.classList.add('sidebar__section--expanded')

      const header = document.createElement('button')
      header.type = 'button'
      header.classList.add('sidebar__group-header')
      header.setAttribute('aria-expanded', String(expanded))
      header.setAttribute('aria-controls', `sidebar-group-${section.id}`)

      if (section.icon) {
        header.appendChild(createIconSvg(this.iconSet, section.icon))
      }

      const labelSpan = document.createElement('span')
      labelSpan.classList.add('sidebar__group-label')
      labelSpan.textContent = section.label
      header.appendChild(labelSpan)

      const chevron = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
      chevron.setAttribute('class', 'sidebar__group-chevron')
      chevron.setAttribute('viewBox', '0 0 24 24')
      chevron.setAttribute('width', '14')
      chevron.setAttribute('height', '14')
      chevron.setAttribute('fill', 'none')
      chevron.setAttribute('stroke', 'currentColor')
      chevron.setAttribute('stroke-width', '2')
      chevron.setAttribute('stroke-linecap', 'round')
      chevron.setAttribute('stroke-linejoin', 'round')
      chevron.setAttribute('aria-hidden', 'true')
      const chevronPath = document.createElementNS('http://www.w3.org/2000/svg', 'path')
      chevronPath.setAttribute('d', 'm6 9 6 6 6-6')
      chevron.appendChild(chevronPath)
      header.appendChild(chevron)

      header.addEventListener('click', () => {
        const nowExpanded = sectionEl.classList.toggle('sidebar__section--expanded')
        header.setAttribute('aria-expanded', String(nowExpanded))
      })

      sectionEl.appendChild(header)
    } else if (section.label) {
      const labelEl = document.createElement('div')
      labelEl.classList.add('sidebar__section-label')
      labelEl.textContent = section.label
      sectionEl.appendChild(labelEl)
    }

    const itemsWrap = document.createElement('div')
    itemsWrap.classList.add('sidebar__items')
    itemsWrap.id = `sidebar-group-${section.id}`

    for (const item of section.items) {
      itemsWrap.appendChild(this.buildItem(item))
    }

    sectionEl.appendChild(itemsWrap)
    return sectionEl
  }

  private buildItem(item: SidebarNavItem): HTMLElement {
    const disabled = item.disabled === true
    const element =
      item.href !== undefined && !disabled
        ? document.createElement('a')
        : disabled
          ? document.createElement('span')
          : document.createElement('button')

    element.classList.add('sidebar__item')
    element.setAttribute('data-item-id', item.id)
    element.setAttribute('data-nav-item', '')
    element.setAttribute('title', item.tooltip ?? item.label)

    if (item.ariaLabel !== undefined) {
      element.setAttribute('aria-label', item.ariaLabel)
    }

    if (disabled) {
      element.setAttribute('aria-disabled', 'true')
      element.setAttribute('role', 'link')
      element.setAttribute('tabindex', '-1')
      element.classList.add('sidebar__item--disabled')
    } else if (element instanceof HTMLAnchorElement) {
      element.href = item.href ?? '#'
      if (item.target !== undefined) {
        element.target = item.target
      }
      if (item.rel !== undefined) {
        element.rel = item.rel
      } else if (item.target === '_blank') {
        element.rel = 'noreferrer noopener'
      }
    } else if (element instanceof HTMLButtonElement) {
      element.type = 'button'
    }

    if (this.isItemActive(item)) {
      element.classList.add('sidebar__item--active')
      element.setAttribute('aria-current', 'page')
    }

    if (item.icon) {
      element.appendChild(createIconSvg(this.iconSet, item.icon))
    }

    const label = document.createElement('span')
    label.classList.add('sidebar__item-label')
    label.textContent = item.label
    element.appendChild(label)

    if (item.tag !== undefined) {
      const tag = document.createElement('span')
      tag.classList.add('sidebar__tag')
      tag.textContent = item.tag
      element.appendChild(tag)
    }

    const badge = normalizeBadge(item.badge, item.badgeTone)
    if (badge?.visible === true) {
      this.applyBadge(element, item.id, badge)
    }

    if (!disabled) {
      element.addEventListener('click', event => {
        this.config.onNavigate?.(item)
        item.onClick?.(event)

        if (this.config.closeOnNavigate !== false && this.isOpen()) {
          this.setOpen(false)
        }
      })
    }

    return element
  }

  private renderHeader(): void {
    const brand = this.config.brand
    if (brand === undefined) {
      return
    }

    const header = document.createElement('div')
    header.classList.add('sidebar__header')

    const row = document.createElement('div')
    row.classList.add('sidebar__header-row')

    const brandEl =
      brand.href !== undefined
        ? document.createElement('a')
        : brand.onClick !== undefined
          ? document.createElement('button')
          : document.createElement('span')

    brandEl.classList.add('sidebar__brand')

    if (brandEl instanceof HTMLAnchorElement) {
      brandEl.href = brand.href ?? '#'
      brandEl.setAttribute('aria-label', brand.label ?? 'CounterMeasure')
    }

    if (brandEl instanceof HTMLButtonElement) {
      brandEl.type = 'button'
      brandEl.setAttribute('aria-label', brand.label ?? 'CounterMeasure')
    }

    if (brand.onClick !== undefined) {
      brandEl.addEventListener('click', brand.onClick)
    }

    brandEl.appendChild(
      createBrandLockupElement({
        label: brand.label ?? 'CounterMeasure',
        markSize: brand.markSize ?? 22,
        showWordmark: brand.showWordmark ?? true,
        decorative: brand.decorative ?? false,
      })
    )

    row.appendChild(brandEl)
    header.appendChild(row)
    this.aside.appendChild(header)
  }

  private renderScope(): void {
    const scope = this.config.scope
    if (scope === undefined || scope.options.length === 0) {
      return
    }

    const activeOption =
      scope.options.find(option => option.id === scope.value) ?? scope.options[0] ?? null
    const root = document.createElement('div')
    root.classList.add('sidebar__scope')

    const trigger = document.createElement('button')
    trigger.type = 'button'
    trigger.classList.add('sidebar__scope-trigger')
    trigger.setAttribute('aria-haspopup', 'listbox')
    trigger.setAttribute('aria-expanded', 'false')
    trigger.setAttribute('title', activeOption?.label ?? scope.placeholder ?? 'Select scope')

    if (scope.icon !== undefined) {
      trigger.appendChild(createIconSvg(this.iconSet, scope.icon, 'sidebar__scope-icon'))
    }

    const copy = document.createElement('span')
    copy.classList.add('sidebar__scope-copy')

    if (scope.label !== undefined) {
      const eyebrow = document.createElement('span')
      eyebrow.classList.add('sidebar__scope-label')
      eyebrow.textContent = scope.label
      copy.appendChild(eyebrow)
    }

    const current = document.createElement('span')
    current.classList.add('sidebar__scope-current')
    current.textContent = activeOption?.label ?? scope.placeholder ?? 'Select scope'
    copy.appendChild(current)
    trigger.appendChild(copy)

    const chevron = createIconSvg(this.iconSet, 'chevron-down', 'sidebar__scope-chevron')
    trigger.appendChild(chevron)

    const menu = document.createElement('div')
    menu.classList.add('sidebar__scope-menu')
    menu.setAttribute('role', 'listbox')
    menu.hidden = true

    const setOpen = (open: boolean): void => {
      trigger.setAttribute('aria-expanded', String(open))
      menu.hidden = !open
    }

    for (const option of scope.options) {
      const optionButton = document.createElement('button')
      optionButton.type = 'button'
      optionButton.classList.add('sidebar__scope-option')
      optionButton.setAttribute('role', 'option')
      optionButton.setAttribute('data-option-id', option.id)
      optionButton.setAttribute('aria-selected', String(option.id === activeOption?.id))

      if (option.disabled === true) {
        optionButton.disabled = true
      }

      if (option.status !== undefined) {
        const dot = document.createElement('span')
        dot.classList.add('sidebar__scope-dot', `sidebar__scope-dot--${option.status}`)
        optionButton.appendChild(dot)
      }

      const label = document.createElement('span')
      label.classList.add('sidebar__scope-option-label')
      label.textContent = option.label
      optionButton.appendChild(label)

      const check = createIconSvg(this.iconSet, 'check', 'sidebar__scope-check')
      optionButton.appendChild(check)

      optionButton.addEventListener('click', () => {
        current.textContent = option.label
        trigger.setAttribute('title', option.label)
        menu.querySelectorAll<HTMLElement>('[role="option"]').forEach(item => {
          item.setAttribute(
            'aria-selected',
            String(item.getAttribute('data-option-id') === option.id)
          )
        })
        setOpen(false)
        scope.onChange?.(option)
      })

      menu.appendChild(optionButton)
    }

    trigger.addEventListener('click', () => {
      setOpen(menu.hidden === true)
    })

    const closeOnOutsidePointer = (event: PointerEvent): void => {
      if (!root.contains(event.target as Node)) {
        setOpen(false)
      }
    }
    document.addEventListener('pointerdown', closeOnOutsidePointer)
    this.cleanupFns.push(() => {
      document.removeEventListener('pointerdown', closeOnOutsidePointer)
    })

    root.appendChild(trigger)
    root.appendChild(menu)
    this.aside.appendChild(root)
  }

  private renderCollapseButton(): void {
    const button = document.createElement('button')
    button.type = 'button'
    button.classList.add('sidebar__collapse-btn', 'sidebar__collapse-btn--edge')
    button.setAttribute('data-sidebar-toggle', '')

    if (this.config.collapseButtonVisible === true) {
      button.setAttribute('data-visible', 'true')
    }

    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
    svg.setAttribute('aria-hidden', 'true')
    svg.setAttribute('viewBox', '0 0 24 24')
    svg.setAttribute('fill', 'none')
    svg.setAttribute('stroke', 'currentColor')
    svg.setAttribute('stroke-width', '2')
    svg.setAttribute('stroke-linecap', 'round')
    svg.setAttribute('stroke-linejoin', 'round')

    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
    svg.appendChild(path)
    button.appendChild(svg)

    button.addEventListener('click', () => {
      this.toggleCollapse()
    })

    this.collapseButton = button
    this.updateCollapseButton()
    this.aside.appendChild(button)
  }

  private renderFooter(): void {
    const hasUser = this.config.user !== undefined
    const hasActions =
      this.config.footerActions !== undefined && this.config.footerActions.length > 0

    if (!hasUser && !hasActions) {
      return
    }

    const footer = document.createElement('div')
    footer.classList.add('sidebar__footer')

    const footerBar = document.createElement('div')
    footerBar.classList.add('sidebar__footer-bar')

    if (hasUser && this.config.user) {
      const user = this.config.user
      const userEl =
        user.onClick !== undefined
          ? document.createElement('button')
          : document.createElement('span')
      userEl.classList.add('sidebar__user-trigger', 'sidebar__footer-user')

      const userClick = user.onClick
      if (userEl instanceof HTMLButtonElement && userClick !== undefined) {
        userEl.type = 'button'
        userEl.setAttribute('aria-label', user.label ?? `User menu for ${user.name}`)
        userEl.addEventListener('click', userClick)
      }

      const avatar = document.createElement('span')
      avatar.classList.add('sidebar__avatar', 'sidebar__footer-avatar')

      if (user.avatarUrl !== undefined) {
        const image = document.createElement('img')
        image.src = user.avatarUrl
        image.alt = ''
        avatar.appendChild(image)
      } else {
        avatar.textContent = user.initials ?? toInitials(user.name)
      }

      if (user.presence !== undefined && user.presence !== 'offline') {
        const presence = document.createElement('span')
        presence.classList.add('sidebar__presence', `sidebar__presence--${user.presence}`)
        presence.setAttribute('aria-hidden', 'true')
        avatar.appendChild(presence)
      }

      const details = document.createElement('span')
      details.classList.add('sidebar__user-details')

      const nameSpan = document.createElement('span')
      nameSpan.classList.add('sidebar__user-name', 'sidebar__footer-name')
      nameSpan.textContent = user.name
      details.appendChild(nameSpan)

      if (user.detail !== undefined) {
        const detail = document.createElement('span')
        detail.classList.add('sidebar__user-status')
        detail.textContent = user.detail
        details.appendChild(detail)
      }

      userEl.appendChild(avatar)
      userEl.appendChild(details)
      footerBar.appendChild(userEl)
    }

    if (hasActions && this.config.footerActions) {
      const iconsSpan = document.createElement('span')
      iconsSpan.classList.add('sidebar__footer-icons')

      for (const action of this.config.footerActions) {
        const button = document.createElement('button')
        button.type = 'button'
        button.classList.add('sidebar__footer-icon')
        if (action.danger === true) {
          button.classList.add('sidebar__footer-icon--danger')
        }
        button.setAttribute('aria-label', action.label)
        button.setAttribute('title', action.label)

        const icon = createIconSvg(this.iconSet, action.icon)
        button.appendChild(icon)

        if (action.onClick) {
          const handler = action.onClick
          button.addEventListener('click', () => {
            handler()
          })
        }

        iconsSpan.appendChild(button)
      }

      footerBar.appendChild(iconsSpan)
    }

    footer.appendChild(footerBar)
    this.aside.appendChild(footer)
  }

  private renderBackdrop(): void {
    const backdrop = document.createElement('button')
    backdrop.type = 'button'
    backdrop.classList.add('sidebar__backdrop')
    backdrop.setAttribute('data-sidebar-backdrop', this.variant)
    backdrop.setAttribute('data-visible', String(this.isOpen()))
    backdrop.setAttribute('aria-label', 'Close sidebar')
    backdrop.addEventListener('click', () => {
      this.setOpen(false)
    })
    this.backdrop = backdrop
    this.container.appendChild(backdrop)
  }

  private setupBehavior(): void {
    this.setupKeyboardNavigation()
    this.setupMobileToggles()

    const handleEscape = (event: KeyboardEvent): void => {
      if (event.key === 'Escape') {
        if (this.isOpen()) {
          this.setOpen(false)
        }
        this.closeScopeMenus()
      }
    }
    document.addEventListener('keydown', handleEscape)
    this.cleanupFns.push(() => {
      document.removeEventListener('keydown', handleEscape)
    })

    const handleResize = (): void => {
      if (isMobileViewport(this.getMobileBreakpoint())) {
        this.setOpen(false)
      }
    }
    window.addEventListener('resize', handleResize)
    this.cleanupFns.push(() => {
      window.removeEventListener('resize', handleResize)
    })
  }

  private setupKeyboardNavigation(): void {
    const handler = (event: KeyboardEvent): void => {
      if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp') return

      const items = Array.from(
        this.aside.querySelectorAll<HTMLElement>('[data-nav-item]:not([aria-disabled="true"])')
      ).filter(item => !item.hidden && item.offsetParent !== null)

      if (items.length === 0) return

      const currentIndex = items.indexOf(document.activeElement as HTMLElement)
      if (currentIndex === -1) return

      event.preventDefault()
      const nextIndex =
        event.key === 'ArrowDown'
          ? (currentIndex + 1) % items.length
          : (currentIndex - 1 + items.length) % items.length
      items[nextIndex]?.focus()
    }

    this.aside.addEventListener('keydown', handler)
    this.cleanupFns.push(() => {
      this.aside.removeEventListener('keydown', handler)
    })
  }

  private setupMobileToggles(): void {
    const toggles = Array.from(
      document.querySelectorAll<HTMLButtonElement>('[data-sidebar-mobile-toggle]')
    ).filter(toggle => {
      const target = toggle.getAttribute('data-sidebar-mobile-toggle')
      return target === null || target === '' || target === this.variant
    })

    for (const toggle of toggles) {
      const handler = (): void => {
        this.setOpen(!this.isOpen())
      }
      toggle.addEventListener('click', handler)
      this.cleanupFns.push(() => {
        toggle.removeEventListener('click', handler)
      })
    }
  }

  /** Mark a nav item as active, removing active state from all others. */
  setActive(itemId: string): void {
    this.config.activeItemId = itemId
    this.aside.querySelectorAll<HTMLElement>('.sidebar__item').forEach(el => {
      el.classList.remove('sidebar__item--active')
      el.removeAttribute('aria-current')
    })

    const target = Array.from(this.aside.querySelectorAll<HTMLElement>('[data-item-id]')).find(
      item => item.getAttribute('data-item-id') === itemId
    )
    if (target) {
      target.classList.add('sidebar__item--active')
      target.setAttribute('aria-current', 'page')
    }
  }

  /** Recompute active route state from a supplied pathname or the current location. */
  setPathname(pathname: string | undefined): void {
    if (pathname === undefined) {
      delete this.config.pathname
    } else {
      this.config.pathname = pathname
    }
    delete this.config.activeItemId
    this.syncActiveRouteState()
  }

  /** Update the badge count for a nav item (empty string when count is 0). */
  updateBadge(itemId: string, count: number, tone?: SidebarBadgeTone): void {
    const item = Array.from(this.aside.querySelectorAll<HTMLElement>('[data-item-id]')).find(
      element => element.getAttribute('data-item-id') === itemId
    )

    if (!item) {
      return
    }

    this.applyBadge(
      item,
      itemId,
      normalizeBadge(
        count,
        tone ?? (item.getAttribute('data-badge-tone') as SidebarBadgeTone | null) ?? undefined
      )
    )
  }

  /** Toggle the sidebar between collapsed and expanded states. */
  toggleCollapse(): void {
    this.setCollapsed(!this.isCollapsed())
  }

  /** Collapse the sidebar. */
  collapse(): void {
    this.setCollapsed(true)
  }

  /** Expand the sidebar. */
  expand(): void {
    this.setCollapsed(false)
  }

  /** Set the mobile open state reflected by the shared CSS contract. */
  setOpen(open: boolean): void {
    const wasOpen = this.isOpen()
    this.aside.setAttribute('data-open', String(open))
    this.backdrop?.setAttribute('data-visible', String(open))

    if (wasOpen !== open) {
      this.config.onOpenChange?.(open)
    }
  }

  /** Remove the sidebar from the DOM. */
  destroy(): void {
    for (const cleanup of this.cleanupFns.splice(0)) {
      cleanup()
    }
    this.backdrop?.remove()
    this.aside.remove()
  }

  private applyBadge(item: HTMLElement, itemId: string, badge: NormalizedBadge | null): void {
    if (badge === null) {
      return
    }

    let badgeEl = item.querySelector<HTMLElement>(`[data-badge="${itemId}"]`)
    if (badgeEl === null) {
      badgeEl = document.createElement('span')
      badgeEl.classList.add('sidebar__badge')
      badgeEl.setAttribute('data-badge', itemId)
      item.appendChild(badgeEl)
    }

    badgeEl.className = `sidebar__badge sidebar__badge--${badge.tone}`
    badgeEl.textContent = badge.label
    if (badge.title !== undefined) {
      badgeEl.setAttribute('title', badge.title)
    } else {
      badgeEl.removeAttribute('title')
    }

    item.setAttribute('data-badge-visible', String(badge.visible))
    item.setAttribute('data-badge-dot', String(badge.dot))
    item.setAttribute('data-badge-tone', badge.tone)
  }

  private setCollapsed(collapsed: boolean): void {
    const wasCollapsed = this.isCollapsed()
    this.aside.classList.toggle('sidebar--collapsed', collapsed)
    this.persistCollapsed(collapsed)
    this.updateCollapseButton()

    if (wasCollapsed !== collapsed) {
      this.config.onCollapse?.(collapsed)
    }
  }

  private isCollapsed(): boolean {
    return this.aside.classList.contains('sidebar--collapsed')
  }

  private isOpen(): boolean {
    return this.aside.getAttribute('data-open') === 'true'
  }

  private getMobileBreakpoint(): number {
    return this.config.mobileBreakpoint ?? DEFAULT_MOBILE_BREAKPOINT
  }

  private resolveInitialCollapsed(): boolean {
    if (this.config.storageKey && this.config.persistCollapsed !== false && canUseStorage()) {
      try {
        if (!isMobileViewport(this.getMobileBreakpoint())) {
          return window.localStorage.getItem(this.config.storageKey) === 'true'
        }
      } catch {
        return this.config.collapsed === true
      }
    }

    return this.config.collapsed === true
  }

  private persistCollapsed(collapsed: boolean): void {
    if (!this.config.storageKey || this.config.persistCollapsed === false || !canUseStorage()) {
      return
    }

    try {
      window.localStorage.setItem(this.config.storageKey, String(collapsed))
    } catch {
      // Collapse state remains applied in memory when storage is unavailable.
    }
  }

  private getCurrentPathname(): string | null {
    if (this.config.pathname !== undefined) {
      return toPathname(this.config.pathname)
    }

    if (typeof window === 'undefined') {
      return null
    }

    return window.location.pathname
  }

  private isItemActive(item: SidebarNavItem): boolean {
    if (item.active === true || item.id === this.config.activeItemId) {
      return true
    }

    const pathname = this.getCurrentPathname()
    if (pathname === null) {
      return false
    }

    const matcher = item.activeMatch

    if (typeof matcher === 'function') {
      return matcher(pathname, item)
    }

    if (matcher instanceof RegExp) {
      return matcher.test(pathname)
    }

    if (typeof matcher === 'string' && matcher !== 'exact' && matcher !== 'prefix') {
      return pathname === matcher || pathname.startsWith(`${matcher.replace(/\/$/, '')}/`)
    }

    if (item.href === undefined) {
      return false
    }

    try {
      const baseUrl = typeof window === 'undefined' ? FALLBACK_URL_BASE : window.location.href
      const hrefPathname = new URL(item.href, baseUrl).pathname
      if (matcher === 'exact' || item.end === true) {
        return pathname === hrefPathname
      }
      return pathname === hrefPathname || pathname.startsWith(`${hrefPathname.replace(/\/$/, '')}/`)
    } catch {
      return false
    }
  }

  private syncActiveRouteState(): void {
    this.aside.querySelectorAll<HTMLElement>('.sidebar__item').forEach(el => {
      el.classList.remove('sidebar__item--active')
      el.removeAttribute('aria-current')
    })

    const active = Array.from(this.aside.querySelectorAll<HTMLElement>('[data-item-id]')).find(
      element => {
        const itemId = element.getAttribute('data-item-id')
        const item = this.config.sections
          .flatMap(section => section.items)
          .find(candidate => candidate.id === itemId)
        return item === undefined ? false : this.isItemActive(item)
      }
    )

    if (active !== undefined) {
      active.classList.add('sidebar__item--active')
      active.setAttribute('aria-current', 'page')
    }
  }

  private closeScopeMenus(): void {
    this.aside.querySelectorAll<HTMLElement>('.sidebar__scope-menu').forEach(menu => {
      menu.hidden = true
    })
    this.aside.querySelectorAll<HTMLElement>('.sidebar__scope-trigger').forEach(trigger => {
      trigger.setAttribute('aria-expanded', 'false')
    })
  }

  private updateCollapseButton(): void {
    if (this.collapseButton === null) {
      return
    }

    const isCollapsed = this.isCollapsed()
    const label = isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'
    this.collapseButton.setAttribute('aria-label', label)
    this.collapseButton.setAttribute('aria-expanded', String(!isCollapsed))
    this.collapseButton.setAttribute('title', label)
    this.collapseButton.setAttribute('data-collapsed', String(isCollapsed))
    this.collapseButton
      .querySelector('path')
      ?.setAttribute('d', isCollapsed ? 'm9 18 6-6-6-6' : 'm15 18-6-6 6-6')
  }
}
