/**
 * Icons Module Tests
 * @countermeasure/web-components
 *
 * Tests for the shared icon sets and SVG retrieval logic.
 */

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

import {
  ICON_SETS,
  LUCIDE_ICON_NAMES,
  SEMANTIC_ICON_ALIASES,
  createIcon,
  getIconSvgInner,
  getLucideIconPath,
  renderIcon,
  resolveLucideIconName,
} from './index'
import type { IconSet } from './index'

const ALL_ICON_SETS: IconSet[] = ['lucide', 'heroicons', 'phosphor', 'material']

const EXPECTED_SEMANTIC_ICON_NAMES = [
  'dashboard',
  'visibility',
  'detections',
  'intel',
  'threat-actors',
  'opportunities',
  'threat-library',
  'connectors',
  'jobs',
  'settings',
  'logout',
  'search',
  'help',
  'user',
  'sound',
  'appearance',
  'profile',
  'organization',
  'about',
]

describe('Icons Module', () => {
  // ===========================================================================
  // ICON_SETS structure
  // ===========================================================================

  describe('ICON_SETS', () => {
    it('has exactly 4 sets: lucide, heroicons, phosphor, material', () => {
      const setNames = Object.keys(ICON_SETS)
      expect(setNames).toHaveLength(4)
      expect(setNames).toContain('lucide')
      expect(setNames).toContain('heroicons')
      expect(setNames).toContain('phosphor')
      expect(setNames).toContain('material')
    })

    it('keeps the legacy semantic icon names in every icon set', () => {
      const heroiconsKeys = Object.keys(ICON_SETS.heroicons).sort()
      const phosphorKeys = Object.keys(ICON_SETS.phosphor).sort()
      const materialKeys = Object.keys(ICON_SETS.material).sort()

      for (const iconName of EXPECTED_SEMANTIC_ICON_NAMES) {
        expect(ICON_SETS.lucide).toHaveProperty(iconName, expect.any(String))
        expect(heroiconsKeys).toContain(iconName)
        expect(phosphorKeys).toContain(iconName)
        expect(materialKeys).toContain(iconName)
      }
    })

    it('exposes every canonical Lucide icon name in the lucide set', () => {
      for (const iconName of LUCIDE_ICON_NAMES) {
        expect(ICON_SETS.lucide).toHaveProperty(iconName, expect.any(String))
      }
    })
  })

  // ===========================================================================
  // getIconSvgInner
  // ===========================================================================

  describe('getIconSvgInner', () => {
    it('returns an SVG string for a known icon in the lucide set', () => {
      const svg = getIconSvgInner('lucide', 'dashboard')
      expect(svg).toBeDefined()
      expect(typeof svg).toBe('string')
      expect(svg?.length ?? 0).toBeGreaterThan(0)
    })

    it('returns canonical Lucide names as SVG inner markup', () => {
      const svg = getIconSvgInner('lucide', 'Shield')
      expect(svg).toBe('<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>')
    })

    it('maps semantic lucide aliases to canonical lucide names', () => {
      expect(resolveLucideIconName('detections')).toBe('Shield')
      expect(resolveLucideIconName('threat-library')).toBe('BookOpen')
      expect(resolveLucideIconName('intel')).toBe('Radar')
      expect(getIconSvgInner('lucide', 'detections')).toBe(getIconSvgInner('lucide', 'Shield'))
    })

    it('keeps semantic aliases backed by existing canonical lucide paths', () => {
      for (const [alias, canonicalName] of Object.entries(SEMANTIC_ICON_ALIASES)) {
        expect(getLucideIconPath(canonicalName), `${alias} maps to ${canonicalName}`).toBeDefined()
        expect(getIconSvgInner('lucide', alias)).toBe(getIconSvgInner('lucide', canonicalName))
      }
    })

    it('returns an SVG string for a known icon in the heroicons set', () => {
      const svg = getIconSvgInner('heroicons', 'settings')
      expect(svg).toBeDefined()
      expect(typeof svg).toBe('string')
      expect(svg?.length ?? 0).toBeGreaterThan(0)
    })

    it('returns an SVG string for a known icon in the phosphor set', () => {
      const svg = getIconSvgInner('phosphor', 'search')
      expect(svg).toBeDefined()
      expect(typeof svg).toBe('string')
      expect(svg?.length ?? 0).toBeGreaterThan(0)
    })

    it('returns an SVG string for a known icon in the material set', () => {
      const svg = getIconSvgInner('material', 'user')
      expect(svg).toBeDefined()
      expect(typeof svg).toBe('string')
      expect(svg?.length ?? 0).toBeGreaterThan(0)
    })

    it('returns undefined for an unknown icon name', () => {
      const svg = getIconSvgInner('lucide', 'nonexistent-icon-xyz')
      expect(svg).toBeUndefined()
    })

    it('returns undefined for unknown icon name in each set', () => {
      for (const setName of ALL_ICON_SETS) {
        const svg = getIconSvgInner(setName, 'completely-made-up-icon')
        expect(svg).toBeUndefined()
      }
    })

    it('returns SVG markup containing valid SVG elements for each set', () => {
      const svgElementPattern = /<(path|circle|rect|line|polyline|polygon|ellipse|g|svg)\b/

      for (const setName of ALL_ICON_SETS) {
        const svg = getIconSvgInner(setName, 'dashboard')
        expect(svg).toBeDefined()
        expect(svg).toMatch(svgElementPattern)
      }
    })

    it('every icon in every set contains valid SVG markup', () => {
      const svgElementPattern = /<(path|circle|rect|line|polyline|polygon|ellipse|g|svg)\b/

      for (const setName of ALL_ICON_SETS) {
        const iconMap = ICON_SETS[setName]
        for (const [iconName, svgContent] of Object.entries(iconMap)) {
          expect(svgContent, `${setName}/${iconName} should contain valid SVG elements`).toMatch(
            svgElementPattern
          )
        }
      }
    })
  })

  describe('Icon helpers', () => {
    it('renders a hidden lucide SVG string by default', () => {
      const svg = renderIcon({ name: 'Shield', size: 20, className: 'icon-20' })

      expect(svg).toContain('width="20"')
      expect(svg).toContain('height="20"')
      expect(svg).toContain('fill="none"')
      expect(svg).toContain('stroke-width="2"')
      expect(svg).toContain('stroke-linecap="round"')
      expect(svg).toContain('stroke-linejoin="round"')
      expect(svg).toContain('aria-hidden="true"')
      expect(svg).toContain('class="cmm-icon icon-20"')
    })

    it('renders title and aria label for accessible SVG strings', () => {
      const svg = renderIcon({ name: 'detections', ariaLabel: 'Detections', title: 'Open <x>' })

      expect(svg).toContain('role="img"')
      expect(svg).toContain('aria-label="Detections"')
      expect(svg).toContain('<title>Open &lt;x&gt;</title>')
      expect(svg).not.toContain('aria-hidden="true"')
    })

    it('creates a DOM SVG element with canonical lucide data', () => {
      const icon = createIcon({ name: 'detections', size: 18, ariaLabel: 'Detections' })

      expect(icon.tagName.toLowerCase()).toBe('svg')
      expect(icon.getAttribute('data-icon')).toBe('detections')
      expect(icon.getAttribute('data-lucide')).toBe('Shield')
      expect(icon.getAttribute('width')).toBe('18')
      expect(icon.getAttribute('height')).toBe('18')
      expect(icon.getAttribute('fill')).toBe('none')
      expect(icon.getAttribute('stroke-width')).toBe('2')
      expect(icon.getAttribute('stroke-linecap')).toBe('round')
      expect(icon.getAttribute('stroke-linejoin')).toBe('round')
      expect(icon.getAttribute('role')).toBe('img')
      expect(icon.getAttribute('aria-label')).toBe('Detections')
      expect(icon.querySelector('path')).not.toBeNull()
    })
  })
})
