import { afterEach, beforeEach, describe, expect, it } from 'vitest'

import { FONT_STACKS, THEME_PRESETS, defaultTheme, ensureGoogleFont } from './index'
import {
  _resetCache,
  applyTheme,
  bootstrapTheme,
  findActivePresetId,
  getTheme,
  loadTheme,
  resetTheme,
  setThemeFull,
  setThemePartial,
  teardownTheme,
  THEME_LS_KEY,
} from './engine'
import type { Theme } from './types'

function resetDom(): void {
  document.documentElement.removeAttribute('data-theme')
  document.documentElement.removeAttribute('data-accent')
  document.documentElement.removeAttribute('data-density')
  document.documentElement.removeAttribute('data-icon-set')
  document.documentElement.removeAttribute('style')
  document.head.innerHTML = ''
  document.body.innerHTML = ''
}

describe('theme engine', () => {
  beforeEach(() => {
    localStorage.clear()
    teardownTheme()
    _resetCache()
    resetDom()
  })

  afterEach(() => {
    teardownTheme()
    _resetCache()
    localStorage.clear()
    resetDom()
  })

  it('loads the default preset and returns a detached clone', () => {
    const first = defaultTheme()
    const second = defaultTheme()

    expect(first).toEqual(THEME_PRESETS.find(preset => preset.id === 'mission-control')?.theme)
    expect(second).toEqual(first)
    expect(second).not.toBe(first)

    first.mode = 'glass'
    expect(defaultTheme().mode).toBe('dark')
  })

  it('migrates legacy preview keys when no persisted theme exists', () => {
    localStorage.setItem('preview:glass-level', '0.12')
    localStorage.setItem('preview:density', 'cozy')

    const migrated = loadTheme()

    expect(migrated.transparency).toBe(0.12)
    expect(migrated.density).toBe('cozy')
  })

  it('applies a theme, persists it, and emits a theme-change event', () => {
    const theme: Theme = {
      ...defaultTheme(),
      mode: 'glass',
      accent: 'violet',
      density: 'cozy',
      iconSet: 'countermeasure',
      radius: 12,
      transparency: 0.1,
      font: 'roboto',
    }
    const heard: Theme[] = []
    document.addEventListener('cm:theme-change', event => {
      heard.push((event as CustomEvent<Theme>).detail)
    })

    applyTheme(theme)

    expect(document.documentElement.getAttribute('data-theme')).toBe('glass')
    expect(document.documentElement.getAttribute('data-accent')).toBe('violet')
    expect(document.documentElement.getAttribute('data-density')).toBe('cozy')
    expect(document.documentElement.getAttribute('data-icon-set')).toBe('countermeasure')
    expect(document.documentElement.style.getPropertyValue('--brand')).toBeTruthy()
    expect(document.documentElement.style.getPropertyValue('--radius-md')).toBe('12px')
    expect(document.documentElement.style.getPropertyValue('--density-row')).toBe('42px')
    expect(document.documentElement.style.getPropertyValue('--font-display')).toContain('Roboto')
    expect(heard[heard.length - 1]).toEqual(theme)
  })

  it('merges partial updates, persists them, and can reset to the default preset', () => {
    setThemeFull(defaultTheme())

    const next = setThemePartial({ mode: 'light', accent: 'cyan', radius: 14 })
    const stored = JSON.parse(localStorage.getItem(THEME_LS_KEY) ?? '{}') as Theme

    expect(next.mode).toBe('light')
    expect(next.accent).toBe('cyan')
    expect(next.radius).toBe(14)
    expect(stored.mode).toBe('light')
    expect(stored.accent).toBe('cyan')
    expect(findActivePresetId(next)).toBe('custom')

    const reset = resetTheme()
    expect(reset).toEqual(defaultTheme())
    expect(findActivePresetId(reset)).toBe('mission-control')
  })

  it('bootstraps once and syncs cache when legacy code mutates data-theme', async () => {
    setThemeFull(defaultTheme())

    bootstrapTheme()
    bootstrapTheme()

    document.documentElement.setAttribute('data-theme', 'light')
    await new Promise(resolve => setTimeout(resolve, 0))

    expect(getTheme().mode).toBe('light')
  })

  it('injects Google font links only once per spec', () => {
    const spec = FONT_STACKS.roboto.google

    ensureGoogleFont(spec)
    ensureGoogleFont(spec)
    ensureGoogleFont(null)

    expect(document.getElementById(`cm-gfont-${spec}`)).toBeTruthy()
    expect(document.head.querySelectorAll('link')).toHaveLength(1)
  })
})
