/**
 * UI Utilities
 *
 * Shared UI functionality for CounterMeasure applications.
 * Includes theme management, auto-dismiss, and form helpers.
 */

export type Theme = 'light' | 'dark' | 'monokai' | 'glass' | 'system'
export type ResolvedTheme = 'light' | 'dark' | 'monokai' | 'glass'

export interface ThemeConfig {
  storageKey?: string
  defaultTheme?: Theme
  themes?: Theme[]
  attribute?: string
}

const DEFAULT_CONFIG: Required<ThemeConfig> = {
  storageKey: 'theme',
  defaultTheme: 'system',
  themes: ['light', 'dark', 'monokai', 'glass', 'system'],
  attribute: 'data-theme',
}

let currentConfig: Required<ThemeConfig> = { ...DEFAULT_CONFIG }

/**
 * Configure theme settings.
 */
export function configureTheme(config: ThemeConfig): void {
  currentConfig = { ...DEFAULT_CONFIG, ...config }
}

/**
 * Check if a value is a valid theme.
 */
export function isValidTheme(value: string | null | undefined): value is Theme {
  if (!value) {
    return false
  }
  return currentConfig.themes.includes(value as Theme)
}

/**
 * Get human-readable theme label.
 */
export function getThemeLabel(theme: Theme): string {
  switch (theme) {
    case 'monokai':
      return 'Monokai'
    case 'glass':
      return 'Glass'
    case 'dark':
      return 'Dark'
    case 'light':
      return 'Light'
    case 'system':
      return 'System'
    default:
      return theme
  }
}

/**
 * Get the user's preferred theme from localStorage or default.
 */
export function getPreferredTheme(): Theme {
  const stored = localStorage.getItem(currentConfig.storageKey)
  if (isValidTheme(stored)) {
    return stored
  }
  return currentConfig.defaultTheme
}

/**
 * Get the resolved theme (light, dark, or monokai) based on preference.
 */
export function getResolvedTheme(preference: Theme = getPreferredTheme()): ResolvedTheme {
  if (preference === 'system') {
    return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
  }
  return preference as ResolvedTheme
}

/**
 * Get the current theme from the document.
 */
export function getCurrentTheme(): Theme {
  const current = document.documentElement.getAttribute(currentConfig.attribute)
  if (isValidTheme(current)) {
    return current
  }
  return getPreferredTheme()
}

/**
 * Apply theme to the document.
 */
export function applyTheme(theme: Theme = getPreferredTheme()): void {
  const resolved = getResolvedTheme(theme)
  document.documentElement.classList.remove('light', 'dark', 'monokai', 'glass')
  document.documentElement.classList.add(resolved)
  document.documentElement.setAttribute(currentConfig.attribute, resolved)
}

/**
 * Save the theme preference to localStorage.
 */
export function saveTheme(theme: Theme): void {
  localStorage.setItem(currentConfig.storageKey, theme)
}

/**
 * Set and persist theme preference.
 */
export function setTheme(theme: Theme): void {
  saveTheme(theme)
  applyTheme(theme)
  syncThemeUI(theme)
}

/**
 * Get the next theme in the toggle cycle.
 */
export function getNextTheme(current: Theme): Theme {
  const themes = currentConfig.themes.filter((t): t is ResolvedTheme => t !== 'system')
  const index = themes.indexOf(current as ResolvedTheme)
  if (index >= 0 && themes.length > 0) {
    return themes[(index + 1) % themes.length] ?? 'light'
  }
  return 'dark'
}

/**
 * Toggle to the next theme in the cycle.
 */
export function toggleTheme(): Theme {
  const current = getCurrentTheme()
  const next = getNextTheme(current)
  setTheme(next)
  return next
}

/**
 * Update the theme toggle cycle without changing the rest of the theme config.
 */
export function updateToggleCycle(themes: ResolvedTheme[]): void {
  currentConfig = { ...currentConfig, themes }
}

/**
 * Update toggle button UI to reflect current theme.
 */
function updateToggleButton(button: HTMLButtonElement, theme: Theme): void {
  const nextTheme = getNextTheme(theme)
  const label = `Switch to ${getThemeLabel(nextTheme)} mode`
  button.setAttribute('aria-label', label)
  button.setAttribute('title', label)

  const labelEl = button.querySelector<HTMLElement>('[data-theme-label]')
  if (labelEl) {
    labelEl.textContent = `${getThemeLabel(theme)} mode`
  }
}

/**
 * Update theme option buttons to reflect current theme.
 */
function updateThemeOptions(theme: Theme): void {
  const optionButtons = document.querySelectorAll<HTMLButtonElement>('[data-theme-option]')
  optionButtons.forEach(button => {
    const option = button.dataset.themeOption
    if (!isValidTheme(option)) {
      return
    }
    const active = option === theme
    button.setAttribute('aria-pressed', active ? 'true' : 'false')
    button.dataset.active = active ? 'true' : 'false'
  })
}

/**
 * Sync all theme UI elements with current theme.
 */
function syncThemeUI(theme: Theme): void {
  const resolved = getResolvedTheme(theme)
  const toggleButtons = document.querySelectorAll<HTMLButtonElement>('[data-theme-toggle]')
  toggleButtons.forEach(button => {
    updateToggleButton(button, resolved)
  })
  updateThemeOptions(resolved)
}

/**
 * Setup theme toggle button.
 */
export function setupThemeToggle(buttonSelector?: string): void {
  const selector = buttonSelector ?? '[data-theme-toggle]'
  const buttons = document.querySelectorAll<HTMLButtonElement>(selector)
  buttons.forEach(button => {
    button.addEventListener('click', () => toggleTheme())
  })
}

/**
 * Setup theme option buttons.
 */
export function setupThemeOptions(): void {
  const optionButtons = document.querySelectorAll<HTMLButtonElement>('[data-theme-option]')
  optionButtons.forEach(button => {
    const option = button.dataset.themeOption
    if (!isValidTheme(option)) {
      return
    }
    button.addEventListener('click', () => {
      setTheme(option)
    })
  })
}

/**
 * Watch for system theme changes and update accordingly.
 */
export function watchSystemTheme(callback?: (theme: ResolvedTheme) => void): () => void {
  const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')

  const handler = (e: MediaQueryListEvent) => {
    const stored = localStorage.getItem(currentConfig.storageKey)
    const hasStoredPreference = stored !== null && stored !== ''
    const shouldFollowSystem =
      stored === 'system' || (!hasStoredPreference && currentConfig.defaultTheme === 'system')
    if (!shouldFollowSystem) {
      return
    }
    const systemTheme: ResolvedTheme = e.matches ? 'dark' : 'light'
    applyTheme('system')
    syncThemeUI(systemTheme)
    callback?.(systemTheme)
  }

  mediaQuery.addEventListener('change', handler)
  return () => {
    mediaQuery.removeEventListener('change', handler)
  }
}

/**
 * Initialize theme on page load.
 */
export function initTheme(config?: ThemeConfig): void {
  if (config) {
    configureTheme(config)
  }
  const preferredTheme = getPreferredTheme()
  applyTheme(preferredTheme)
  syncThemeUI(preferredTheme)
  setupThemeToggle()
  setupThemeOptions()
  watchSystemTheme()
}

// Auto-dismiss functionality

/**
 * Setup auto-dismiss for elements with data-auto-dismiss attribute.
 */
export function setupAutoDismiss(defaultDuration = 5000): void {
  const elements = document.querySelectorAll<HTMLElement>('[data-auto-dismiss]')

  elements.forEach(element => {
    const parsed = parseInt(element.dataset.autoDismiss ?? '', 10)
    const duration = Number.isNaN(parsed) || parsed === 0 ? defaultDuration : parsed

    setTimeout(() => {
      element.classList.add('opacity-0', 'transition-opacity', 'duration-300')
      setTimeout(() => {
        element.remove()
      }, 300)
    }, duration)
  })
}

// Form helpers

export interface FormFieldError {
  field: string
  message: string
}

/**
 * Display form validation errors.
 */
export function displayFormErrors(formSelector: string, errors: FormFieldError[]): void {
  const form = document.querySelector(formSelector)
  if (!form) {
    return
  }

  // Clear existing errors
  form.querySelectorAll('.field-error').forEach(el => {
    el.remove()
  })
  form.querySelectorAll('.input-error').forEach(el => {
    el.classList.remove('input-error', 'border-red-500')
  })

  // Display new errors
  errors.forEach(({ field, message }) => {
    const input = form.querySelector(`[name="${field}"]`)
    if (input) {
      input.classList.add('input-error', 'border-red-500')

      const errorEl = document.createElement('p')
      errorEl.className = 'field-error text-red-500 text-sm mt-1'
      errorEl.textContent = message
      input.parentNode?.appendChild(errorEl)
    }
  })
}

/**
 * Clear all form errors.
 */
export function clearFormErrors(formSelector: string): void {
  const form = document.querySelector(formSelector)
  if (!form) {
    return
  }

  form.querySelectorAll('.field-error').forEach(el => {
    el.remove()
  })
  form.querySelectorAll('.input-error').forEach(el => {
    el.classList.remove('input-error', 'border-red-500')
  })
}

/**
 * Submit form with fetch and handle response.
 */
export async function submitForm(
  formSelector: string,
  options: {
    method?: string
    headers?: Record<string, string>
    transform?: (data: FormData) => unknown
  } = {}
): Promise<{ ok: boolean; data?: unknown; errors?: FormFieldError[] }> {
  const form = document.querySelector<HTMLFormElement>(formSelector)
  if (!form) {
    return { ok: false, errors: [{ field: '', message: 'Form not found' }] }
  }

  const formData = new FormData(form)
  const action = form.action !== '' ? form.action : window.location.href
  const method = options.method ?? (form.method !== '' ? form.method : 'POST')

  let body: string | FormData
  const headers: Record<string, string> = { ...options.headers }

  if (options.transform) {
    body = JSON.stringify(options.transform(formData))
    headers['Content-Type'] = 'application/json'
  } else {
    body = formData
  }

  try {
    const response = await fetch(action, {
      method,
      headers,
      body,
    })

    const data: unknown = await response.json()

    if (!response.ok) {
      const errorData = data as { errors?: FormFieldError[]; message?: string }
      const errors: FormFieldError[] = errorData.errors ?? [
        { field: '', message: errorData.message ?? 'Request failed' },
      ]
      return { ok: false, errors }
    }

    return { ok: true, data }
  } catch (error) {
    return {
      ok: false,
      errors: [{ field: '', message: error instanceof Error ? error.message : 'Network error' }],
    }
  }
}

// Loading state helpers

/**
 * Show loading state on a button.
 */
export function setButtonLoading(button: HTMLButtonElement, loading: boolean): void {
  if (loading) {
    button.disabled = true
    button.dataset.originalText = button.textContent || ''
    button.innerHTML = `
      <svg class="animate-spin -ml-1 mr-2 h-4 w-4 inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
        <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
        <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
      </svg>
      Loading...
    `
  } else {
    button.disabled = false
    button.textContent = button.dataset.originalText ?? 'Submit'
  }
}
