/**
 * Sheet - Sliding content panel overlay
 * @countermeasure/web-components/overlays/sheet
 */

import { uniqueId } from '../../primitives/utils'
import DOMPurify from 'dompurify'
import { type SheetConfig, type SheetSide, type SheetSize } from './types'

export type { SheetConfig, SheetSide, SheetSize } 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))
}

export class Sheet {
  private readonly side: SheetSide
  private readonly size: SheetSize
  private readonly showOverlay: boolean
  private readonly shouldCloseOnEscape: boolean
  private readonly shouldCloseOnOverlay: boolean
  private readonly title: string | undefined
  private readonly description: string | undefined
  private readonly className: string | undefined
  private readonly onOpenCallback: (() => void) | undefined
  private readonly onCloseCallback: (() => void) | undefined
  private readonly titleId: string
  private readonly descId: string

  private overlayEl: HTMLElement | null = null
  private sheetEl: HTMLElement | null = null
  private opened = false
  private destroyed = false

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

  constructor(config?: SheetConfig) {
    this.side = config?.side ?? 'right'
    this.size = config?.size ?? 'md'
    this.showOverlay = config?.showOverlay !== false
    this.shouldCloseOnEscape = config?.closeOnEscape !== false
    this.shouldCloseOnOverlay = config?.closeOnOverlay !== false
    this.title = config?.title
    this.description = config?.description
    this.className = config?.className
    this.onOpenCallback = config?.onOpen
    this.onCloseCallback = config?.onClose
    this.titleId = uniqueId('cmm-sheet-title')
    this.descId = uniqueId('cmm-sheet-desc')

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

    this.handleOverlayClick = (e: MouseEvent) => {
      if (e.target === this.overlayEl) {
        this.close()
      }
    }
  }

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

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

    this.opened = true
    this.createElements(content)

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

    // Add open classes for transition
    requestAnimationFrame(() => {
      if (this.sheetEl) {
        this.sheetEl.classList.add('cmm-sheet--open')
      }
      if (this.overlayEl) {
        this.overlayEl.classList.add('cmm-sheet-overlay--open')
      }
    })

    // Keyboard handler
    if (this.shouldCloseOnEscape) {
      document.addEventListener('keydown', this.handleKeyDown)
    }

    // Focus the first focusable element inside the sheet, or the sheet itself
    requestAnimationFrame(() => {
      if (this.sheetEl) {
        const focusable = getFocusableElements(this.sheetEl)
        const first = focusable[0]
        if (first) {
          first.focus()
        } else {
          this.sheetEl.setAttribute('tabindex', '-1')
          this.sheetEl.focus()
        }
      }
    })

    this.onOpenCallback?.()
  }

  /** Close the sheet with exit animation */
  close(): void {
    if (!this.opened) {
      return
    }

    this.opened = false
    document.removeEventListener('keydown', this.handleKeyDown)

    if (this.sheetEl) {
      this.sheetEl.classList.remove('cmm-sheet--open')
    }

    if (this.overlayEl) {
      this.overlayEl.classList.remove('cmm-sheet-overlay--open')
    }

    // Remove elements after transition
    const sheet = this.sheetEl
    const overlay = this.overlayEl
    setTimeout(() => {
      if (sheet) {
        sheet.remove()
      }
      if (overlay) {
        overlay.remove()
      }
    }, 300)

    this.sheetEl = null
    this.overlayEl = null

    this.onCloseCallback?.()
  }

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

    this.close()
  }

  /** Create the sheet and overlay DOM elements */
  private createElements(content: string | HTMLElement): void {
    // Overlay backdrop
    if (this.showOverlay) {
      const overlay = document.createElement('div')
      overlay.className = 'cmm-sheet-overlay'
      this.overlayEl = overlay

      if (this.shouldCloseOnOverlay) {
        overlay.addEventListener('click', this.handleOverlayClick)
      }

      document.body.appendChild(overlay)
    }

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

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

    // Header
    if (this.title || this.description) {
      const header = document.createElement('div')
      header.className = 'cmm-sheet__header'

      const headerText = document.createElement('div')
      headerText.className = 'cmm-sheet__header-text'

      if (this.title) {
        const titleEl = document.createElement('h2')
        titleEl.className = 'cmm-sheet__title'
        titleEl.id = this.titleId
        titleEl.textContent = this.title
        headerText.appendChild(titleEl)
        sheet.setAttribute('aria-labelledby', this.titleId)
      }

      if (this.description) {
        const descEl = document.createElement('p')
        descEl.className = 'cmm-sheet__description'
        descEl.id = this.descId
        descEl.textContent = this.description
        headerText.appendChild(descEl)
        sheet.setAttribute('aria-describedby', this.descId)
      }

      header.appendChild(headerText)

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

      sheet.appendChild(header)
    } else {
      // Close button without header
      const closeBtn = document.createElement('button')
      closeBtn.className = 'cmm-sheet__close'
      closeBtn.type = 'button'
      closeBtn.setAttribute('aria-label', 'Close')
      closeBtn.textContent = '\u00D7'
      closeBtn.style.position = 'absolute'
      closeBtn.style.top = '0.75rem'
      closeBtn.style.right = '0.75rem'
      closeBtn.addEventListener('click', () => {
        this.close()
      })
      sheet.appendChild(closeBtn)
    }

    // Body
    const body = document.createElement('div')
    body.className = 'cmm-sheet__body'

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

    sheet.appendChild(body)
    this.sheetEl = sheet

    document.body.appendChild(sheet)
  }

  /** 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 sheet
    if (e.key === 'Tab' && this.sheetEl) {
      const focusable = getFocusableElements(this.sheetEl)
      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()
        }
      }
    }
  }
}
