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

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

export type { ModalConfig, ModalVariant, ModalSize } 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')
      this.overlayEl.setAttribute('data-state', 'closed')
      this.dialogEl?.setAttribute('data-state', 'closed')

      // 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') {
      setSafeHtml(bodyEl as HTMLElement, content)
    } else {
      bodyEl.replaceChildren()
      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'
    overlay.setAttribute('data-slot', 'modal')
    overlay.setAttribute('data-state', 'closed')

    // Panel width preset (mirrors the React twin's max-w-* surface)
    const size = this.config.size ?? 'md'
    overlay.classList.add(`cmm-modal--${size}`)
    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('data-slot', 'modal-content')
    dialog.setAttribute('data-state', 'closed')
    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'
    header.setAttribute('data-slot', 'modal-header')

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

    const showCloseButton = this.config.showCloseButton !== false
    if (showCloseButton) {
      const closeBtn = document.createElement('button')
      closeBtn.type = 'button'
      closeBtn.className = 'cmm-modal__close'
      closeBtn.setAttribute('data-slot', 'modal-close')
      closeBtn.setAttribute('aria-label', 'Close')
      closeBtn.appendChild(createCloseIcon())

      const srLabel = document.createElement('span')
      srLabel.className = 'cmm-sr-only'
      srLabel.textContent = 'Close'
      closeBtn.appendChild(srLabel)

      closeBtn.addEventListener('click', () => {
        this.close()
      })
      header.appendChild(closeBtn)
    }

    dialog.appendChild(header)

    // Body
    const body = document.createElement('div')
    body.className = 'cmm-modal__body'
    body.setAttribute('data-slot', 'modal-body')

    if (this.config.body !== undefined) {
      if (typeof this.config.body === 'string') {
        setSafeHtml(body, this.config.body)
      } else {
        body.appendChild(this.config.body)
      }
    }

    dialog.appendChild(body)

    // Footer
    const footer = document.createElement('div')
    footer.className = 'cmm-modal__footer'
    footer.setAttribute('data-slot', '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 =
        this.config.destructive === true
          ? 'cmm-modal__btn cmm-modal__btn--destructive'
          : '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')
      overlay.setAttribute('data-state', 'open')
      dialog.setAttribute('data-state', '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()
    }
  }
}

const SVG_NS = 'http://www.w3.org/2000/svg'

/** Build the lucide `X` close icon (matches the React twin's `<XIcon className="size-4" />`). */
function createCloseIcon(): SVGSVGElement {
  const svg = document.createElementNS(SVG_NS, 'svg')
  svg.setAttribute('class', 'cmm-modal__close-icon')
  svg.setAttribute('xmlns', SVG_NS)
  svg.setAttribute('viewBox', '0 0 24 24')
  svg.setAttribute('fill', 'none')
  svg.setAttribute('stroke', 'currentColor')
  svg.setAttribute('stroke-width', '2')
  svg.setAttribute('stroke-linecap', 'round')
  svg.setAttribute('stroke-linejoin', 'round')
  svg.setAttribute('aria-hidden', 'true')

  const line1 = document.createElementNS(SVG_NS, 'path')
  line1.setAttribute('d', 'M18 6 6 18')
  svg.appendChild(line1)

  const line2 = document.createElementNS(SVG_NS, 'path')
  line2.setAttribute('d', 'm6 6 12 12')
  svg.appendChild(line2)

  return svg
}
