/**
 * Drawer - Slide-in side panel with overlay
 * @countermeasure/web-components/navigation/drawer
 */

import { uniqueId } from '../../primitives/utils'
import DOMPurify from 'dompurify'
import { type DrawerConfig, type DrawerSide, type DrawerSize } from './types'

export type { DrawerConfig, DrawerSide, DrawerSize } from './types'

/** Query all focusable elements within a container */
function getFocusableElements(container: HTMLElement): HTMLElement[] {
  const selector = [
    'a[href]',
    'button:not([disabled])',
    'input:not([disabled])',
    'select:not([disabled])',
    'textarea:not([disabled])',
    '[tabindex]:not([tabindex="-1"])',
  ].join(', ')

  return Array.from(container.querySelectorAll<HTMLElement>(selector))
}

/** Size values in pixels */
const SIZE_MAP: Record<DrawerSize, string> = {
  sm: '320px',
  md: '480px',
  lg: '640px',
  full: '100%',
}

export class Drawer {
  private readonly side: DrawerSide
  private readonly size: DrawerSize
  private readonly title: string | undefined
  private readonly showOverlay: boolean
  private readonly closeOnEscape: boolean
  private readonly closeOnOverlay: boolean
  private readonly className: string | undefined
  private readonly onOpenCallback: (() => void) | undefined
  private readonly onCloseCallback: (() => void) | undefined
  private readonly drawerId: string

  private overlayEl: HTMLElement | null = null
  private drawerEl: HTMLElement | null = null
  private contentEl: HTMLElement | null = null
  private opened = false
  private destroyed = false
  private previouslyFocused: HTMLElement | null = null

  /** Bound event handlers for cleanup */
  private readonly handleKeyDown: (e: KeyboardEvent) => void

  constructor(config: DrawerConfig) {
    this.side = config.side ?? 'left'
    this.size = config.size ?? 'md'
    this.title = config.title
    this.showOverlay = config.showOverlay !== false
    this.closeOnEscape = config.closeOnEscape !== false
    this.closeOnOverlay = config.closeOnOverlay !== false
    this.className = config.className
    this.onOpenCallback = config.onOpen
    this.onCloseCallback = config.onClose
    this.drawerId = uniqueId('cmm-drawer')

    this.handleKeyDown = (e: KeyboardEvent) => {
      this.onKeyDown(e)
    }
  }

  /** Check whether the drawer is currently open */
  isOpen(): boolean {
    return this.opened
  }

  /** Open the drawer with the given content */
  open(content?: HTMLElement | string): void {
    if (this.destroyed || this.opened) {
      return
    }

    this.opened = true
    this.previouslyFocused = document.activeElement as HTMLElement | null

    this.createDrawerElement(content)

    // Force reflow before adding open class for transition
    if (this.drawerEl) {
      void this.drawerEl.offsetHeight
    }

    // Animate open
    requestAnimationFrame(() => {
      if (this.overlayEl) {
        this.overlayEl.classList.add('cmm-drawer-overlay--open')
      }
      if (this.drawerEl) {
        this.drawerEl.classList.add('cmm-drawer--open')
      }
    })

    // Register keyboard listener
    if (this.closeOnEscape) {
      document.addEventListener('keydown', this.handleKeyDown)
    }

    // Prevent body scroll
    document.body.style.overflow = 'hidden'

    // Focus the drawer
    if (this.drawerEl) {
      const focusable = getFocusableElements(this.drawerEl)
      const first = focusable[0]
      if (first) {
        first.focus()
      } else {
        this.drawerEl.setAttribute('tabindex', '-1')
        this.drawerEl.focus()
      }
    }

    this.onOpenCallback?.()
  }

  /** Close the drawer */
  close(): void {
    if (!this.opened) {
      return
    }

    this.opened = false

    document.removeEventListener('keydown', this.handleKeyDown)

    // Animate close
    if (this.overlayEl) {
      this.overlayEl.classList.remove('cmm-drawer-overlay--open')
    }
    if (this.drawerEl) {
      this.drawerEl.classList.remove('cmm-drawer--open')
    }

    // Remove from DOM after transition
    const overlay = this.overlayEl
    const drawer = this.drawerEl

    setTimeout(() => {
      if (overlay) {
        overlay.remove()
      }
      if (drawer) {
        drawer.remove()
      }
    }, 300)

    this.overlayEl = null
    this.drawerEl = null
    this.contentEl = null

    // Restore body scroll
    document.body.style.overflow = ''

    // Restore focus
    if (this.previouslyFocused) {
      this.previouslyFocused.focus()
      this.previouslyFocused = null
    }

    this.onCloseCallback?.()
  }

  /** Toggle the drawer open/closed */
  toggle(content?: HTMLElement | string): void {
    if (this.opened) {
      this.close()
    } else {
      this.open(content)
    }
  }

  /** Update the drawer content while open */
  setContent(content: HTMLElement | string): void {
    if (!this.contentEl) {
      return
    }

    this.contentEl.innerHTML = ''

    if (typeof content === 'string') {
      this.contentEl.innerHTML = DOMPurify.sanitize(content)
    } else {
      this.contentEl.appendChild(content)
    }
  }

  /** Remove all listeners and clean up */
  destroy(): void {
    if (this.destroyed) {
      return
    }
    this.destroyed = true

    this.close()
    document.removeEventListener('keydown', this.handleKeyDown)
  }

  /** Create the drawer DOM elements */
  private createDrawerElement(content?: HTMLElement | string): void {
    // Create overlay
    if (this.showOverlay) {
      const overlay = document.createElement('div')
      overlay.className = 'cmm-drawer-overlay'

      if (this.closeOnOverlay) {
        overlay.addEventListener('click', () => {
          this.close()
        })
      }

      document.body.appendChild(overlay)
      this.overlayEl = overlay
    }

    // Create drawer panel
    const drawer = document.createElement('div')
    drawer.className = `cmm-drawer cmm-drawer--${this.side} cmm-drawer--${this.size}`
    drawer.id = this.drawerId
    drawer.setAttribute('role', 'dialog')
    drawer.setAttribute('aria-modal', 'true')

    if (this.className) {
      drawer.classList.add(this.className)
    }

    // Apply size dimensions
    const sizeValue = SIZE_MAP[this.size]
    if (this.side === 'left' || this.side === 'right') {
      drawer.style.width = sizeValue
    } else {
      drawer.style.height = sizeValue
    }

    // Header
    if (this.title !== undefined) {
      const header = document.createElement('div')
      header.className = 'cmm-drawer__header'

      const titleEl = document.createElement('h2')
      titleEl.className = 'cmm-drawer__title'
      titleEl.id = uniqueId('cmm-drawer-title')
      titleEl.textContent = this.title
      drawer.setAttribute('aria-labelledby', titleEl.id)
      header.appendChild(titleEl)

      const closeBtn = document.createElement('button')
      closeBtn.className = 'cmm-drawer__close'
      closeBtn.type = 'button'
      closeBtn.setAttribute('aria-label', 'Close')
      closeBtn.textContent = '\u00D7'
      closeBtn.addEventListener('click', () => {
        this.close()
      })
      header.appendChild(closeBtn)

      drawer.appendChild(header)
    }

    // Content area
    const contentEl = document.createElement('div')
    contentEl.className = 'cmm-drawer__content'

    if (content !== undefined) {
      if (typeof content === 'string') {
        contentEl.innerHTML = DOMPurify.sanitize(content)
      } else {
        contentEl.appendChild(content)
      }
    }

    drawer.appendChild(contentEl)
    document.body.appendChild(drawer)

    this.drawerEl = drawer
    this.contentEl = contentEl
  }

  /** Handle keyboard events for escape and focus trapping */
  private onKeyDown(e: KeyboardEvent): void {
    if (e.key === 'Escape') {
      e.preventDefault()
      this.close()
      return
    }

    // Focus trap: Tab key cycles within the drawer
    if (e.key === 'Tab' && this.drawerEl) {
      const focusable = getFocusableElements(this.drawerEl)
      if (focusable.length === 0) {
        e.preventDefault()
        return
      }

      const first = focusable[0]
      const last = focusable[focusable.length - 1]

      if (first === undefined || last === undefined) {
        e.preventDefault()
        return
      }

      if (e.shiftKey) {
        if (document.activeElement === first) {
          e.preventDefault()
          last.focus()
        }
      } else {
        if (document.activeElement === last) {
          e.preventDefault()
          first.focus()
        }
      }
    }
  }
}
