/**
 * ToastManager - Accessible notification toast component
 * @countermeasure/web-components/components/toast
 *
 * Implements ARIA live region pattern for screen reader announcements.
 * WCAG 2.1 AA compliant.
 */

import { type ToastConfig, type ToastLevel, type ToastOptions, type ToastPosition } from './types'
import { uniqueId } from '../../primitives/utils'

export type { ToastConfig, ToastLevel, ToastOptions, ToastPosition } from './types'

interface ActiveToast {
  id: string
  element: HTMLElement
  timeoutId: ReturnType<typeof setTimeout> | null
  isPaused: boolean
  remainingTime: number
  startTime: number
  duration: number
}

export class ToastManager {
  private readonly position: ToastPosition
  private readonly maxVisible: number
  private readonly defaultDuration: number
  private containerEl: HTMLElement | null = null
  private readonly toasts: ActiveToast[] = []
  private readonly containerId: string
  private readonly regionLabelId: string

  constructor(config?: ToastConfig) {
    this.position = config?.position ?? 'top-right'
    this.maxVisible = config?.maxVisible ?? 5
    this.defaultDuration = config?.defaultDuration ?? 5000
    this.containerId = uniqueId('cmm-toast-container')
    this.regionLabelId = uniqueId('cmm-toast-label')
  }

  /** Show a success toast */
  success(title: string, message?: string, options?: ToastOptions): string {
    return this.show('success', title, message, options)
  }

  /** Show an error toast */
  error(title: string, message?: string, options?: ToastOptions): string {
    return this.show('error', title, message, options)
  }

  /** Show a warning toast */
  warning(title: string, message?: string, options?: ToastOptions): string {
    return this.show('warning', title, message, options)
  }

  /** Show an info toast */
  info(title: string, message?: string, options?: ToastOptions): string {
    return this.show('info', title, message, options)
  }

  /** Dismiss a specific toast by id */
  dismiss(id: string): void {
    const index = this.toasts.findIndex(t => t.id === id)
    if (index < 0) {
      return
    }

    const toast = this.toasts[index]
    if (toast === undefined) {
      return
    }

    this.removeToast(toast)
  }

  /** Dismiss all toasts */
  dismissAll(): void {
    // Iterate in reverse to avoid index shifting issues
    const toastsCopy = [...this.toasts]
    for (const toast of toastsCopy) {
      this.removeToast(toast)
    }
  }

  /** Remove the container element and clean up all toasts */
  destroy(): void {
    this.dismissAll()
    if (this.containerEl) {
      this.containerEl.remove()
      this.containerEl = null
    }
  }

  /** Ensure the toast container element exists */
  private ensureContainer(): HTMLElement {
    if (this.containerEl) {
      return this.containerEl
    }

    // Create hidden label for screen readers
    const label = document.createElement('span')
    label.id = this.regionLabelId
    label.className = 'cmm-sr-only'
    label.textContent = 'Notifications'
    label.style.cssText = `
      position: absolute;
      width: 1px;
      height: 1px;
      padding: 0;
      margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
      border: 0;
    `

    const container = document.createElement('div')
    container.id = this.containerId
    container.className = `cmm-toast-container cmm-toast-container--${this.position}`

    // ARIA live region for notifications
    container.setAttribute('role', 'region')
    container.setAttribute('aria-labelledby', this.regionLabelId)
    container.setAttribute('aria-live', 'polite')
    container.setAttribute('aria-relevant', 'additions removals')

    // Position the container
    container.style.position = 'fixed'
    container.style.zIndex = '9999'
    container.style.display = 'flex'
    container.style.flexDirection = 'column'
    container.style.gap = '8px'
    container.style.pointerEvents = 'none'
    container.style.maxHeight = '100vh'
    container.style.overflow = 'hidden'

    switch (this.position) {
      case 'top-right': {
        container.style.top = '16px'
        container.style.right = '16px'
        break
      }
      case 'top-left': {
        container.style.top = '16px'
        container.style.left = '16px'
        break
      }
      case 'bottom-right': {
        container.style.bottom = '16px'
        container.style.right = '16px'
        break
      }
      case 'bottom-left': {
        container.style.bottom = '16px'
        container.style.left = '16px'
        break
      }
    }

    // Append label first for screen reader order
    document.body.appendChild(label)
    document.body.appendChild(container)
    this.containerEl = container
    return container
  }

  /** Get ARIA role for toast level */
  private getAriaRole(level: ToastLevel): 'alert' | 'status' {
    // Use 'alert' for errors and warnings as they require immediate attention
    // Use 'status' for info and success
    if (level === 'error' || level === 'warning') {
      return 'alert'
    }
    return 'status'
  }

  /** Get ARIA live politeness for toast level */
  private getAriaLive(level: ToastLevel): 'assertive' | 'polite' {
    // Errors should interrupt, others wait for idle
    if (level === 'error') {
      return 'assertive'
    }
    return 'polite'
  }

  /** Show a toast with the given level, title, and message */
  private show(level: ToastLevel, title: string, message?: string, options?: ToastOptions): string {
    const container = this.ensureContainer()

    const id = options?.id ?? uniqueId('cmm-toast')

    // Check if a toast with this id already exists
    const existingIndex = this.toasts.findIndex(t => t.id === id)
    if (existingIndex >= 0) {
      const existing = this.toasts[existingIndex]
      if (existing !== undefined) {
        this.removeToast(existing)
      }
    }

    const duration = options?.duration ?? this.defaultDuration

    // Build toast element
    const toastEl = document.createElement('div')
    toastEl.className = `cmm-toast cmm-toast--${level}`
    toastEl.id = id
    toastEl.dataset.toastId = id
    toastEl.style.pointerEvents = 'auto'

    // ARIA attributes
    toastEl.setAttribute('role', this.getAriaRole(level))
    toastEl.setAttribute('aria-live', this.getAriaLive(level))
    toastEl.setAttribute('aria-atomic', 'true')

    // Icon for visual level indication
    const iconEl = document.createElement('span')
    iconEl.className = 'cmm-toast__icon'
    iconEl.setAttribute('aria-hidden', 'true')
    iconEl.textContent = this.getLevelIcon(level)
    toastEl.appendChild(iconEl)

    // Content container
    const contentEl = document.createElement('div')
    contentEl.className = 'cmm-toast__content'

    // Title
    const titleEl = document.createElement('div')
    titleEl.className = 'cmm-toast__title'
    titleEl.textContent = title
    contentEl.appendChild(titleEl)

    // Message
    if (message !== undefined) {
      const msgEl = document.createElement('div')
      msgEl.className = 'cmm-toast__message'
      msgEl.textContent = message
      contentEl.appendChild(msgEl)
    }

    toastEl.appendChild(contentEl)

    // Close button
    const closeBtn = document.createElement('button')
    closeBtn.className = 'cmm-toast__close'
    closeBtn.type = 'button'
    closeBtn.setAttribute('aria-label', 'Dismiss notification')
    closeBtn.innerHTML = '<span aria-hidden="true">&times;</span>'
    closeBtn.addEventListener('click', () => {
      this.dismiss(id)
    })
    toastEl.appendChild(closeBtn)

    // Progress bar for auto-dismiss timing
    const persistent = options?.persistent === true
    if (!persistent) {
      const progressEl = document.createElement('div')
      progressEl.className = 'cmm-toast__progress'
      progressEl.setAttribute('role', 'progressbar')
      progressEl.setAttribute('aria-valuemin', '0')
      progressEl.setAttribute('aria-valuemax', '100')
      progressEl.setAttribute('aria-valuenow', '100')
      progressEl.setAttribute('aria-label', 'Time remaining before auto-dismiss')

      const progressBar = document.createElement('div')
      progressBar.className = 'cmm-toast__progress-bar'
      progressBar.style.animationDuration = `${String(duration)}ms`
      progressEl.appendChild(progressBar)

      toastEl.appendChild(progressEl)
    }

    // Create active toast record
    const activeToast: ActiveToast = {
      id,
      element: toastEl,
      timeoutId: null,
      isPaused: false,
      remainingTime: duration,
      startTime: Date.now(),
      duration,
    }

    // Pause timer on hover/focus (accessibility requirement)
    toastEl.addEventListener('mouseenter', () => {
      this.pauseToast(activeToast)
    })
    toastEl.addEventListener('mouseleave', () => {
      this.resumeToast(activeToast)
    })
    toastEl.addEventListener('focusin', () => {
      this.pauseToast(activeToast)
    })
    toastEl.addEventListener('focusout', e => {
      // Only resume if focus is leaving the toast entirely
      if (!toastEl.contains(e.relatedTarget as Node)) {
        this.resumeToast(activeToast)
      }
    })

    // Keyboard support
    toastEl.addEventListener('keydown', e => {
      if (e.key === 'Escape') {
        e.preventDefault()
        this.dismiss(id)
      }
    })

    // Auto-dismiss timeout
    if (!persistent) {
      activeToast.timeoutId = setTimeout(() => {
        this.dismiss(id)
      }, duration)
    }

    this.toasts.push(activeToast)
    container.appendChild(toastEl)

    // Enforce max visible: remove oldest if exceeded
    while (this.toasts.length > this.maxVisible) {
      const oldest = this.toasts[0]
      if (oldest !== undefined) {
        this.removeToast(oldest)
      } else {
        break
      }
    }

    // Focus the close button for keyboard users (unless there are many toasts)
    if (this.toasts.length === 1) {
      requestAnimationFrame(() => {
        closeBtn.focus()
      })
    }

    return id
  }

  /** Get icon character for level */
  private getLevelIcon(level: ToastLevel): string {
    switch (level) {
      case 'success':
        return '\u2713' // Checkmark
      case 'error':
        return '\u2717' // X mark
      case 'warning':
        return '\u26A0' // Warning triangle
      case 'info':
        return '\u2139' // Info circle
      default:
        return ''
    }
  }

  /** Pause auto-dismiss timer */
  private pauseToast(toast: ActiveToast): void {
    if (toast.isPaused || toast.timeoutId === null) {
      return
    }

    toast.isPaused = true
    clearTimeout(toast.timeoutId)
    toast.remainingTime = toast.remainingTime - (Date.now() - toast.startTime)

    // Pause progress bar animation
    const progressBar = toast.element.querySelector<HTMLElement>('.cmm-toast__progress-bar')
    if (progressBar !== null) {
      progressBar.style.animationPlayState = 'paused'
    }
  }

  /** Resume auto-dismiss timer */
  private resumeToast(toast: ActiveToast): void {
    if (!toast.isPaused || toast.remainingTime <= 0) {
      return
    }

    toast.isPaused = false
    toast.startTime = Date.now()
    toast.timeoutId = setTimeout(() => {
      this.dismiss(toast.id)
    }, toast.remainingTime)

    // Resume progress bar animation
    const progressBar = toast.element.querySelector<HTMLElement>('.cmm-toast__progress-bar')
    if (progressBar !== null) {
      progressBar.style.animationPlayState = 'running'
    }
  }

  /** Remove a toast from the DOM and internal list */
  private removeToast(toast: ActiveToast): void {
    if (toast.timeoutId !== null) {
      clearTimeout(toast.timeoutId)
    }

    toast.element.classList.add('cmm-toast--exiting')
    toast.element.setAttribute('aria-hidden', 'true')

    // Remove after exit animation (respect reduced motion)
    const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
    const animationDuration = prefersReducedMotion ? 0 : 300

    setTimeout(() => {
      toast.element.remove()
    }, animationDuration)

    const index = this.toasts.indexOf(toast)
    if (index >= 0) {
      this.toasts.splice(index, 1)
    }
  }
}
