/**
 * Modal - Dialog overlay component with focus trapping
 * @countermeasure/web-components/components/modal
 */

import DOMPurify from 'dompurify'
import { type ModalConfig } from './types'
import { createFocusTrap, type FocusTrap } from '../../utils/a11y'
import { uniqueId } from '../../primitives/utils'

export type { ModalConfig, ModalVariant } from './types'

export class Modal {
  private overlayEl: HTMLElement | null = null
  private dialogEl: HTMLElement | null = null
  private readonly config: ModalConfig
  private readonly titleId: string
  private isLoading = false
  private focusTrap: FocusTrap | null = null

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

  private constructor(config: ModalConfig) {
    this.config = config
    this.titleId = uniqueId('cmm-modal-title')
    this.handleKeyDown = (e: KeyboardEvent) => {
      this.onKeyDown(e)
    }
  }

  /** Factory method: create and show a modal */
  static open(config: ModalConfig): Modal {
    const modal = new Modal(config)
    modal.show()
    return modal
  }

  /** Close the modal and remove from DOM */
  close(): void {
    document.removeEventListener('keydown', this.handleKeyDown)
    this.focusTrap?.deactivate()
    this.focusTrap = null

    if (this.overlayEl) {
      this.overlayEl.classList.remove('cmm-modal--open')

      // Remove after transition
      const overlay = this.overlayEl
      setTimeout(() => {
        overlay.remove()
      }, 200)

      this.overlayEl = null
      this.dialogEl = null
    }

    this.config.onCancel?.()
  }

  /** Update the modal body content */
  setBody(content: string | HTMLElement): void {
    if (!this.dialogEl) {
      return
    }

    const bodyEl = this.dialogEl.querySelector('.cmm-modal__body')
    if (!bodyEl) {
      return
    }

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

  /** Set loading state: disables buttons during async confirm */
  setLoading(loading: boolean): void {
    this.isLoading = loading

    if (!this.dialogEl) {
      return
    }

    const buttons = this.dialogEl.querySelectorAll<HTMLButtonElement>('.cmm-modal__footer button')
    buttons.forEach(btn => {
      btn.disabled = loading
    })
  }

  /** Build and show the modal DOM */
  private show(): void {
    // Overlay
    const overlay = document.createElement('div')
    overlay.className = 'cmm-modal'
    this.overlayEl = overlay

    // Backdrop click
    const closeOnBackdrop = this.config.closeOnBackdrop !== false
    if (closeOnBackdrop) {
      overlay.addEventListener('click', (e: MouseEvent) => {
        if (e.target === overlay) {
          this.close()
        }
      })
    }

    // Dialog
    const dialog = document.createElement('div')
    dialog.className = 'cmm-modal__dialog'
    dialog.setAttribute('role', 'dialog')
    dialog.setAttribute('aria-modal', 'true')
    dialog.setAttribute('aria-labelledby', this.titleId)
    this.dialogEl = dialog

    // Header
    const header = document.createElement('div')
    header.className = 'cmm-modal__header'

    const title = document.createElement('h2')
    title.className = 'cmm-modal__title'
    title.id = this.titleId
    title.textContent = this.config.title
    header.appendChild(title)

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

    dialog.appendChild(header)

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

    if (this.config.body !== undefined) {
      if (typeof this.config.body === 'string') {
        body.innerHTML = DOMPurify.sanitize(this.config.body)
      } else {
        body.appendChild(this.config.body)
      }
    }

    dialog.appendChild(body)

    // Footer
    const footer = document.createElement('div')
    footer.className = 'cmm-modal__footer'

    const variant = this.config.variant ?? 'confirm'

    if (variant === 'alert') {
      const okBtn = document.createElement('button')
      okBtn.className = 'cmm-modal__btn cmm-modal__btn--primary'
      okBtn.textContent = this.config.confirmLabel ?? 'OK'
      okBtn.addEventListener('click', () => {
        this.close()
      })
      footer.appendChild(okBtn)
    } else {
      // confirm or form: show cancel + confirm
      const cancelBtn = document.createElement('button')
      cancelBtn.className = 'cmm-modal__btn cmm-modal__btn--secondary'
      cancelBtn.textContent = this.config.cancelLabel ?? 'Cancel'
      cancelBtn.addEventListener('click', () => {
        this.close()
      })
      footer.appendChild(cancelBtn)

      const confirmBtn = document.createElement('button')
      confirmBtn.className = 'cmm-modal__btn cmm-modal__btn--primary'
      confirmBtn.textContent = this.config.confirmLabel ?? 'Confirm'
      confirmBtn.addEventListener('click', () => {
        void this.handleConfirm()
      })
      footer.appendChild(confirmBtn)
    }

    dialog.appendChild(footer)
    overlay.appendChild(dialog)
    document.body.appendChild(overlay)

    // Show with transition
    requestAnimationFrame(() => {
      overlay.classList.add('cmm-modal--open')
    })

    // Keyboard handler (Escape to close)
    const closeOnEscape = this.config.closeOnEscape !== false
    if (closeOnEscape) {
      document.addEventListener('keydown', this.handleKeyDown)
    }

    // Make dialog focusable as fallback and activate focus trap
    dialog.setAttribute('tabindex', '-1')
    this.focusTrap = createFocusTrap(dialog)
    this.focusTrap.activate()
  }

  /** Handle the confirm button click, supporting async callbacks */
  private async handleConfirm(): Promise<void> {
    if (this.isLoading) {
      return
    }

    if (this.config.onConfirm === undefined) {
      this.close()
      return
    }

    const result: void | Promise<void> = this.config.onConfirm()

    // Check if the result is a promise
    if (result !== undefined) {
      this.setLoading(true)
      try {
        await result
      } finally {
        this.setLoading(false)
      }
    }

    this.close()
  }

  /** Handle keyboard events for escape (focus trapping delegated to shared createFocusTrap) */
  private onKeyDown(e: KeyboardEvent): void {
    if (e.key === 'Escape') {
      this.close()
    }
  }
}
