/**
 * AlertDialog - Blocking confirmation overlay with variant support
 * @countermeasure/web-components/overlays/alert-dialog
 */

import { uniqueId } from '../../primitives/utils'
import { type AlertDialogConfig, type AlertDialogVariant } from './types'

export type { AlertDialogConfig, AlertDialogVariant } 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 AlertDialog {
  private overlayEl: HTMLElement | null = null
  private dialogEl: HTMLElement | null = null
  private confirmBtn: HTMLButtonElement | null = null
  private cancelBtn: HTMLButtonElement | null = null
  private confirmInput: HTMLInputElement | null = null
  private readonly config: AlertDialogConfig
  private isLoading = false
  private readonly titleId: string
  private readonly descId: string

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

  private constructor(config: AlertDialogConfig) {
    this.config = config
    this.titleId = uniqueId('cmm-alert-title')
    this.descId = uniqueId('cmm-alert-desc')
    this.handleKeyDown = (e: KeyboardEvent) => {
      this.onKeyDown(e)
    }
  }

  /** Factory method: create and show an alert dialog */
  static open(config: AlertDialogConfig): AlertDialog {
    const dialog = new AlertDialog(config)
    dialog.show()
    return dialog
  }

  /** Close the dialog and remove from DOM */
  close(): void {
    document.removeEventListener('keydown', this.handleKeyDown)

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

      const overlay = this.overlayEl
      setTimeout(() => {
        overlay.remove()
      }, 200)

      this.overlayEl = null
      this.dialogEl = null
      this.confirmBtn = null
      this.cancelBtn = null
      this.confirmInput = null
    }
  }

  /** Build and show the alert dialog DOM */
  private show(): void {
    const variant: AlertDialogVariant = this.config.variant ?? 'info'

    // Overlay (not dismissible by click)
    const overlay = document.createElement('div')
    overlay.className = `cmm-alert-dialog cmm-alert-dialog--${variant}`
    this.overlayEl = overlay

    // Dialog
    const dialog = document.createElement('div')
    dialog.className = 'cmm-alert-dialog__dialog'
    dialog.setAttribute('role', 'alertdialog')
    dialog.setAttribute('aria-modal', 'true')
    dialog.setAttribute('aria-labelledby', this.titleId)
    dialog.setAttribute('aria-describedby', this.descId)
    dialog.setAttribute('tabindex', '-1')
    this.dialogEl = dialog

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

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

    const description = document.createElement('p')
    description.className = 'cmm-alert-dialog__description'
    description.id = this.descId
    description.textContent = this.config.description
    header.appendChild(description)

    dialog.appendChild(header)

    // Confirm text input for destructive variant
    if (variant === 'destructive' && this.config.confirmText) {
      const confirmGroup = document.createElement('div')
      confirmGroup.className = 'cmm-alert-dialog__confirm-group'

      const confirmLabel = document.createElement('label')
      confirmLabel.className = 'cmm-alert-dialog__confirm-label'
      const inputId = uniqueId('cmm-alert-confirm-input')
      confirmLabel.setAttribute('for', inputId)
      confirmLabel.textContent = `Type "${this.config.confirmText}" to confirm`
      confirmGroup.appendChild(confirmLabel)

      const confirmInput = document.createElement('input')
      confirmInput.className = 'cmm-alert-dialog__confirm-input'
      confirmInput.id = inputId
      confirmInput.type = 'text'
      confirmInput.autocomplete = 'off'
      confirmInput.spellcheck = false
      confirmInput.setAttribute('aria-required', 'true')
      confirmInput.addEventListener('input', () => {
        this.updateConfirmButtonState()
      })
      confirmGroup.appendChild(confirmInput)

      this.confirmInput = confirmInput
      dialog.appendChild(confirmGroup)
    }

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

    const cancelBtn = document.createElement('button')
    cancelBtn.className = 'cmm-alert-dialog__btn cmm-alert-dialog__btn--cancel'
    cancelBtn.type = 'button'
    cancelBtn.textContent = this.config.cancelLabel ?? 'Cancel'
    cancelBtn.addEventListener('click', () => {
      this.handleCancel()
    })
    footer.appendChild(cancelBtn)
    this.cancelBtn = cancelBtn

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

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

    // Disable confirm button if type-to-confirm is required
    this.updateConfirmButtonState()

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

    // Keyboard handler (no escape dismissal, only focus trap)
    document.addEventListener('keydown', this.handleKeyDown)

    // Focus the first interactive element
    if (this.confirmInput) {
      this.confirmInput.focus()
    } else {
      cancelBtn.focus()
    }
  }

  /** Check if the confirm input matches the required text */
  private isConfirmTextValid(): boolean {
    if (this.config.variant !== 'destructive' || !this.config.confirmText) {
      return true
    }
    if (!this.confirmInput) {
      return true
    }
    return this.confirmInput.value === this.config.confirmText
  }

  /** Enable or disable the confirm button based on input match */
  private updateConfirmButtonState(): void {
    if (!this.confirmBtn) {
      return
    }

    if (this.config.variant === 'destructive' && this.config.confirmText) {
      this.confirmBtn.disabled = !this.isConfirmTextValid() || this.isLoading
    } else {
      this.confirmBtn.disabled = this.isLoading
    }
  }

  /** Set loading state on both buttons */
  private setLoading(loading: boolean): void {
    this.isLoading = loading

    if (this.confirmBtn) {
      this.confirmBtn.disabled = loading
      if (loading) {
        const spinner = document.createElement('span')
        spinner.className = 'cmm-alert-dialog__spinner'
        this.confirmBtn.prepend(spinner)
      } else {
        const spinner = this.confirmBtn.querySelector('.cmm-alert-dialog__spinner')
        if (spinner) {
          spinner.remove()
        }
      }
    }

    if (this.cancelBtn) {
      this.cancelBtn.disabled = loading
    }

    if (this.confirmInput) {
      this.confirmInput.disabled = loading
    }
  }

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

    if (!this.isConfirmTextValid()) {
      return
    }

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

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

    if (result !== undefined && typeof result.then === 'function') {
      this.setLoading(true)
      try {
        await result
      } finally {
        this.setLoading(false)
      }
    }

    this.close()
  }

  /** Handle cancel button click */
  private handleCancel(): void {
    if (this.isLoading) {
      return
    }
    this.config.onCancel?.()
    this.close()
  }

  /** Handle keyboard events for focus trapping (no escape dismissal) */
  private onKeyDown(e: KeyboardEvent): void {
    // AlertDialog does NOT close on Escape - explicit action required
    if (e.key === 'Escape') {
      e.preventDefault()
      return
    }

    // Focus trap: Tab key cycles within the dialog
    if (e.key === 'Tab' && this.dialogEl) {
      const focusable = getFocusableElements(this.dialogEl)
      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()
        }
      }
    }
  }
}
