/**
 * Layout Components
 * @countermeasure/web-components
 *
 * Shared layout utilities for sidebar, user menu, collapsibles, and sheets.
 */

import { sanitizeHtml } from '../utils/sanitize'
export { AppShell, createAppShell } from './app-shell'
export type { AppShellConfig } from './app-shell'
export { PageHeader, createPageHeader } from './page-header'
export type { PageHeaderConfig, PageHeaderMetadata } from './page-header'

// ============================================================================
// TYPES
// ============================================================================

export interface SidebarConfig {
  storageKey?: string
  desktopBreakpoint?: string
  collapsedClass?: string
  openClass?: string
}

export interface UserMenuConfig {
  triggerSelector?: string
  dropdownSelector?: string
  dropdownItemSelector?: string
}

export interface CollapsibleConfig {
  scrollIntoView?: boolean
  scrollBehavior?: ScrollBehavior
  hiddenClass?: string
}

export interface SheetConfig {
  key: string
  sheet: HTMLElement
  backdrop: HTMLElement
  content: HTMLElement
  fullPageLink?: HTMLAnchorElement | null | undefined
  expandLink?: HTMLAnchorElement | null | undefined
  focusSelector?: string | undefined
}

export interface SheetSetupConfig {
  openClass?: string
  hiddenClass?: string
  backdropVisibleClass?: string
  backdropHiddenClass?: string
  spinnerHtml?: string
  errorHtml?: string
  /** Sanitize fetched sheet HTML before inserting it into the DOM. */
  sanitizeFetchedHtml?: boolean
  /** Allow fetching sheet content from non-same-origin URLs. Default: false. */
  allowCrossOriginFetch?: boolean
}

// ============================================================================
// STORAGE HELPERS
// ============================================================================

const resolveStorage = (): Storage | null => {
  if (typeof window === 'undefined') {
    return null
  }
  try {
    return window.localStorage
  } catch {
    console.warn('Unable to access localStorage')
    return null
  }
}

const isSameOriginUrl = (url: string): boolean => {
  try {
    return new URL(url, window.location.href).origin === window.location.origin
  } catch {
    return false
  }
}

// ============================================================================
// SIDEBAR
// ============================================================================

const DEFAULT_SIDEBAR_CONFIG: Required<SidebarConfig> = {
  storageKey: 'cmm-sidebar-state',
  desktopBreakpoint: '(min-width: 1024px)',
  collapsedClass: 'cmm-shell--sidebar-collapsed',
  openClass: 'cmm-shell--sidebar-open',
}

/**
 * Setup responsive sidebar with collapse/expand functionality.
 */
export function setupSidebar(config: SidebarConfig = {}): () => void {
  const options = { ...DEFAULT_SIDEBAR_CONFIG, ...config }
  const layout = document.querySelector<HTMLElement>('[data-shell-layout]')
  const sidebar = document.querySelector<HTMLElement>('[data-shell-sidebar]')

  if (!layout || !sidebar) {
    return () => {
      /* noop */
    }
  }

  const storage = resolveStorage()
  const mediaQuery = window.matchMedia(options.desktopBreakpoint)
  const toggles = Array.from(document.querySelectorAll<HTMLElement>('[data-sidebar-toggle]'))
  const dismissors = Array.from(document.querySelectorAll<HTMLElement>('[data-sidebar-dismiss]'))

  const sidebarId = sidebar.id || 'shell-sidebar'
  sidebar.id = sidebarId

  const readStoredCollapsed = () => storage?.getItem(options.storageKey) === 'collapsed'

  const syncToggleAria = () => {
    const expanded = mediaQuery.matches
      ? !layout.classList.contains(options.collapsedClass)
      : layout.classList.contains(options.openClass)

    toggles.forEach(toggle => {
      toggle.setAttribute('aria-expanded', expanded.toString())
      toggle.setAttribute('aria-controls', sidebarId)
    })
  }

  const applyCollapsed = (collapsed: boolean, persist = true) => {
    layout.classList.toggle(options.collapsedClass, collapsed)
    if (persist && mediaQuery.matches) {
      storage?.setItem(options.storageKey, collapsed ? 'collapsed' : 'expanded')
    }
    syncToggleAria()
  }

  const setOpen = (open: boolean) => {
    layout.classList.toggle(options.openClass, open)
    syncToggleAria()
  }

  const syncForViewport = () => {
    if (mediaQuery.matches) {
      setOpen(false)
      applyCollapsed(readStoredCollapsed(), false)
    } else {
      setOpen(false)
      applyCollapsed(false, false)
    }
  }

  const handleToggleClick = () => {
    if (mediaQuery.matches) {
      const collapsed = layout.classList.contains(options.collapsedClass)
      applyCollapsed(!collapsed)
    } else {
      const open = layout.classList.contains(options.openClass)
      setOpen(!open)
    }
  }

  const handleDismissClick = () => {
    setOpen(false)
  }

  const handleKeydown = (event: KeyboardEvent) => {
    if (event.key === 'Escape' && !mediaQuery.matches) {
      setOpen(false)
    }
  }

  // Attach listeners
  toggles.forEach(toggle => {
    toggle.addEventListener('click', handleToggleClick)
  })
  dismissors.forEach(dismiss => {
    dismiss.addEventListener('click', handleDismissClick)
  })
  document.addEventListener('keydown', handleKeydown)
  mediaQuery.addEventListener('change', syncForViewport)

  // Initialize
  syncForViewport()

  // Return cleanup function
  return () => {
    toggles.forEach(toggle => {
      toggle.removeEventListener('click', handleToggleClick)
    })
    dismissors.forEach(dismiss => {
      dismiss.removeEventListener('click', handleDismissClick)
    })
    document.removeEventListener('keydown', handleKeydown)
    mediaQuery.removeEventListener('change', syncForViewport)
  }
}

// ============================================================================
// USER MENU
// ============================================================================

/**
 * Setup user menu dropdown with keyboard navigation.
 */
export function setupUserMenu(config: UserMenuConfig = {}): () => void {
  const triggerSelector = config.triggerSelector ?? '[data-user-trigger]'
  const dropdownSelector = config.dropdownSelector ?? '[data-user-dropdown]'

  const userMenu = document.querySelector<HTMLElement>('[data-user-menu]')
  const trigger = document.querySelector<HTMLButtonElement>(triggerSelector)
  const dropdown = document.querySelector<HTMLElement>(dropdownSelector)

  if (!userMenu || !trigger || !dropdown) {
    return () => {
      /* noop */
    }
  }

  const setExpanded = (expanded: boolean) => {
    trigger.setAttribute('aria-expanded', expanded.toString())
    userMenu.setAttribute('aria-expanded', expanded.toString())
  }

  const isExpanded = () => trigger.getAttribute('aria-expanded') === 'true'

  const handleTriggerClick = (event: Event) => {
    event.stopPropagation()
    setExpanded(!isExpanded())
  }

  const handleDocumentClick = (event: Event) => {
    if (!userMenu.contains(event.target as Node)) {
      setExpanded(false)
    }
  }

  const handleKeydown = (event: KeyboardEvent) => {
    if (event.key === 'Escape' && isExpanded()) {
      setExpanded(false)
      trigger.focus()
    }
  }

  const dropdownItemSelector = config.dropdownItemSelector ?? '.cmm-user-menu__dropdown-item'

  const handleDropdownKeydown = (event: KeyboardEvent) => {
    const items = Array.from(dropdown.querySelectorAll<HTMLAnchorElement>(dropdownItemSelector))
    const currentIndex = items.indexOf(document.activeElement as HTMLAnchorElement)

    if (event.key === 'ArrowDown') {
      event.preventDefault()
      const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0
      items[nextIndex]?.focus()
    } else if (event.key === 'ArrowUp') {
      event.preventDefault()
      const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1
      items[prevIndex]?.focus()
    } else if (event.key === 'Tab' && !event.shiftKey && currentIndex === items.length - 1) {
      setExpanded(false)
    } else if (event.key === 'Tab' && event.shiftKey && currentIndex === 0) {
      setExpanded(false)
    }
  }

  const handleTriggerKeydown = (event: KeyboardEvent) => {
    if (
      (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') &&
      !isExpanded()
    ) {
      event.preventDefault()
      setExpanded(true)
      setTimeout(() => {
        const firstItem = dropdown.querySelector<HTMLAnchorElement>(dropdownItemSelector)
        firstItem?.focus()
      }, 50)
    }
  }

  // Attach listeners
  trigger.addEventListener('click', handleTriggerClick)
  document.addEventListener('click', handleDocumentClick)
  document.addEventListener('keydown', handleKeydown)
  dropdown.addEventListener('keydown', handleDropdownKeydown)
  trigger.addEventListener('keydown', handleTriggerKeydown)

  // Return cleanup function
  return () => {
    trigger.removeEventListener('click', handleTriggerClick)
    document.removeEventListener('click', handleDocumentClick)
    document.removeEventListener('keydown', handleKeydown)
    dropdown.removeEventListener('keydown', handleDropdownKeydown)
    trigger.removeEventListener('keydown', handleTriggerKeydown)
  }
}

// ============================================================================
// COLLAPSIBLES
// ============================================================================

const isCollapsibleExpanded = (section: HTMLElement) =>
  section.dataset.collapsibleOpen === 'true' || !section.classList.contains('hidden')

let collapsibleHiddenClass = 'cmm-collapsible--hidden'

const updateCollapsibleState = (section: HTMLElement, expanded: boolean) => {
  section.classList.toggle('hidden', !expanded)
  section.classList.toggle(collapsibleHiddenClass, !expanded)
  section.dataset.collapsibleOpen = expanded.toString()
  section.setAttribute('aria-hidden', expanded ? 'false' : 'true')
}

const syncCollapsibleToggles = (selector: string, expanded: boolean) => {
  document.querySelectorAll<HTMLElement>(`[data-toggle-target='${selector}']`).forEach(toggle => {
    toggle.setAttribute('aria-expanded', expanded.toString())
  })
}

/**
 * Setup collapsible sections with toggle buttons.
 */
export function setupCollapsibles(config: CollapsibleConfig = {}): () => void {
  const {
    scrollIntoView = true,
    scrollBehavior = 'smooth',
    hiddenClass = 'cmm-collapsible--hidden',
  } = config
  collapsibleHiddenClass = hiddenClass

  // Initialize all collapsible sections
  document.querySelectorAll<HTMLElement>('[data-collapsible]').forEach(section => {
    const expanded =
      section.dataset.collapsibleOpen === 'true' || !section.classList.contains('hidden')
    updateCollapsibleState(section, expanded)
  })

  const handlers: { element: HTMLElement; handler: (e: Event) => void }[] = []

  // Setup toggle triggers
  document.querySelectorAll<HTMLElement>('[data-toggle-target]').forEach(toggle => {
    const selector = toggle.dataset.toggleTarget
    if (!selector) {
      return
    }

    const target = document.querySelector<HTMLElement>(selector)
    if (!target) {
      return
    }

    const ariaTarget = target.id || selector.replace(/^#/, '')
    toggle.setAttribute('aria-controls', ariaTarget)
    toggle.setAttribute('aria-expanded', isCollapsibleExpanded(target).toString())

    const handler = (event: Event) => {
      event.preventDefault()
      const expanded = !isCollapsibleExpanded(target)
      updateCollapsibleState(target, expanded)
      syncCollapsibleToggles(selector, expanded)

      if (expanded && scrollIntoView) {
        target.scrollIntoView({ behavior: scrollBehavior, block: 'start' })
      }
    }

    toggle.addEventListener('click', handler)
    handlers.push({ element: toggle, handler })
  })

  // Setup collapse triggers (close only)
  document.querySelectorAll<HTMLElement>('[data-collapse-target]').forEach(toggle => {
    const selector = toggle.dataset.collapseTarget
    if (!selector) {
      return
    }

    const target = document.querySelector<HTMLElement>(selector)
    if (!target) {
      return
    }

    const handler = (event: Event) => {
      event.preventDefault()
      updateCollapsibleState(target, false)
      syncCollapsibleToggles(selector, false)
    }

    toggle.addEventListener('click', handler)
    handlers.push({ element: toggle, handler })
  })

  // Handle URL hash for deep linking
  const hash = window.location.hash
  if (hash) {
    const target = document.querySelector<HTMLElement>(hash)
    if (target?.dataset.collapsible !== undefined) {
      updateCollapsibleState(target, true)
      syncCollapsibleToggles(hash, true)
    }
  }

  // Return cleanup function
  return () => {
    handlers.forEach(({ element, handler }) => {
      element.removeEventListener('click', handler)
    })
  }
}

// ============================================================================
// SHEETS / SIDE PANELS
// ============================================================================

const SHEET_SPINNER_HTML = `
  <div class="cmm-sheet__loading">
    <div class="cmm-spinner"></div>
  </div>
`

const SHEET_ERROR_HTML = `
  <div class="cmm-sheet__error">
    <p>Failed to load content. Please try the full page.</p>
  </div>
`

/**
 * Setup side sheet/drawer components.
 */
export function setupSheets(setupConfig: SheetSetupConfig = {}): () => void {
  const {
    openClass = 'cmm-sheet--open',
    hiddenClass = 'cmm-sheet--hidden',
    backdropVisibleClass = 'cmm-sheet__backdrop--visible',
    backdropHiddenClass = 'cmm-sheet__backdrop--hidden',
    spinnerHtml = SHEET_SPINNER_HTML,
    errorHtml = SHEET_ERROR_HTML,
    sanitizeFetchedHtml = false,
    allowCrossOriginFetch = false,
  } = setupConfig

  const sheetMap = new Map<string, SheetConfig>()

  document.querySelectorAll<HTMLElement>('[data-sheet]').forEach(sheet => {
    const key = sheet.dataset.sheet
    if (!key) {
      return
    }

    const backdrop = document.querySelector<HTMLElement>(`[data-sheet-backdrop='${key}']`)
    const content = sheet.querySelector<HTMLElement>('[data-sheet-content]')
    if (!backdrop || !content) {
      return
    }

    sheetMap.set(key, {
      key,
      sheet,
      backdrop,
      content,
      fullPageLink: sheet.querySelector<HTMLAnchorElement>('[data-sheet-fullpage]'),
      expandLink: sheet.querySelector<HTMLAnchorElement>('[data-sheet-expand]'),
      focusSelector: sheet.dataset.sheetFocus,
    })
  })

  if (sheetMap.size === 0) {
    return () => {
      /* noop */
    }
  }

  let activeSheet: SheetConfig | null = null

  const setOpenState = (config: SheetConfig, open: boolean) => {
    config.sheet.classList.toggle(openClass, open)
    config.sheet.classList.toggle(hiddenClass, !open)
    config.backdrop.classList.toggle(backdropVisibleClass, open)
    config.backdrop.classList.toggle(backdropHiddenClass, !open)
    config.sheet.setAttribute('aria-hidden', open ? 'false' : 'true')
  }

  const closeSheet = (config: SheetConfig) => {
    if (activeSheet?.key === config.key) {
      activeSheet = null
    }
    setOpenState(config, false)
  }

  const openSheet = async (config: SheetConfig, trigger?: HTMLElement) => {
    if (activeSheet && activeSheet.key !== config.key) {
      closeSheet(activeSheet)
    }

    activeSheet = config
    setOpenState(config, true)

    const url = trigger?.dataset.sheetUrl ?? config.sheet.dataset.sheetUrl
    const focusSelector = trigger?.dataset.sheetFocus ?? config.focusSelector
    const fullPageHref = trigger?.dataset.sheetFullpage ?? config.sheet.dataset.sheetFullpage
    const expandHref = trigger?.dataset.sheetExpand ?? config.sheet.dataset.sheetExpand

    if (config.fullPageLink && fullPageHref) {
      config.fullPageLink.href = fullPageHref
    }

    if (config.expandLink && expandHref) {
      config.expandLink.href = expandHref
    }

    if (!url || (!allowCrossOriginFetch && !isSameOriginUrl(url))) {
      config.content.innerHTML = errorHtml
      return
    }

    config.content.innerHTML = spinnerHtml

    try {
      const response = await fetch(url)
      if (response.ok) {
        const html = await response.text()
        config.content.innerHTML = sanitizeFetchedHtml
          ? sanitizeHtml(html, { allowSvg: true })
          : html
        if (focusSelector) {
          const focusTarget = config.content.querySelector<HTMLElement>(focusSelector)
          focusTarget?.focus()
        }
      } else if (fullPageHref) {
        window.location.href = fullPageHref
      } else {
        config.content.innerHTML = errorHtml
      }
    } catch {
      if (fullPageHref) {
        window.location.href = fullPageHref
      } else {
        config.content.innerHTML = errorHtml
      }
    }
  }

  const handlers: { element: HTMLElement; event: string; handler: (e: Event) => void }[] = []

  sheetMap.forEach(config => {
    const closeHandler = (event: Event) => {
      const target = event.target as HTMLElement
      if (target.closest('[data-sheet-close]')) {
        event.preventDefault()
        closeSheet(config)
      }
    }

    const backdropHandler = () => {
      closeSheet(config)
    }

    const expandHandler = () => {
      closeSheet(config)
    }

    config.sheet.addEventListener('click', closeHandler)
    config.backdrop.addEventListener('click', backdropHandler)
    handlers.push({ element: config.sheet, event: 'click', handler: closeHandler })
    handlers.push({ element: config.backdrop, event: 'click', handler: backdropHandler })

    if (config.expandLink) {
      config.expandLink.addEventListener('click', expandHandler)
      handlers.push({ element: config.expandLink, event: 'click', handler: expandHandler })
    }
  })

  // Setup triggers
  document.querySelectorAll<HTMLElement>('[data-sheet-trigger]').forEach(trigger => {
    const key = trigger.dataset.sheetTrigger
    if (!key) {
      return
    }

    const config = sheetMap.get(key)
    if (!config) {
      return
    }

    const handler = (event: Event) => {
      event.preventDefault()
      void openSheet(config, trigger)
    }

    trigger.addEventListener('click', handler)
    handlers.push({ element: trigger, event: 'click', handler })
  })

  // Escape key handler
  const escapeHandler = (event: KeyboardEvent) => {
    if (event.key === 'Escape' && activeSheet) {
      closeSheet(activeSheet)
    }
  }

  document.addEventListener('keydown', escapeHandler)

  // Return cleanup function
  return () => {
    handlers.forEach(({ element, event, handler }) => {
      element.removeEventListener(event, handler)
    })
    document.removeEventListener('keydown', escapeHandler)
  }
}

// ============================================================================
// NAVIGATION KEYBOARD SUPPORT
// ============================================================================

/**
 * Setup keyboard navigation for sidebar nav items.
 */
export function setupNavKeyboard(): () => void {
  const navList = document.querySelector<HTMLElement>('[data-nav-list]')
  if (!navList) {
    return () => {
      /* noop */
    }
  }

  const getNavItems = () =>
    Array.from(navList.querySelectorAll<HTMLAnchorElement>('[data-nav-item]'))

  const handler = (event: KeyboardEvent) => {
    const items = getNavItems()
    const currentIndex = items.indexOf(document.activeElement as HTMLAnchorElement)

    if (event.key === 'ArrowDown') {
      event.preventDefault()
      const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0
      items[nextIndex]?.focus()
    } else if (event.key === 'ArrowUp') {
      event.preventDefault()
      const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1
      items[prevIndex]?.focus()
    } else if (event.key === 'Home') {
      event.preventDefault()
      items[0]?.focus()
    } else if (event.key === 'End') {
      event.preventDefault()
      items[items.length - 1]?.focus()
    }
  }

  navList.addEventListener('keydown', handler)

  return () => {
    navList.removeEventListener('keydown', handler)
  }
}

// ============================================================================
// BOOTSTRAP
// ============================================================================

/**
 * Initialize all layout components.
 */
export function initLayout(
  config: {
    sidebar?: SidebarConfig
    userMenu?: UserMenuConfig
    collapsibles?: CollapsibleConfig
    sheets?: SheetSetupConfig
  } = {}
): () => void {
  const cleanups = [
    setupSidebar(config.sidebar),
    setupUserMenu(config.userMenu),
    setupCollapsibles(config.collapsibles),
    setupSheets(config.sheets),
    setupNavKeyboard(),
  ]

  return () => {
    cleanups.forEach(cleanup => {
      cleanup()
    })
  }
}
