import { describe, it, expect } from 'vitest'
import {
  getSubtypeId, resolveMarkerColor, markerRingColor, assetTypeColorsMap,
  UNSPECIFIED_COLOR
} from './mapColors'

// WCAG relative luminance / contrast, so the palette assertions below are
// computed rather than eyeballed.
function contrast(a, b) {
  const lum = hex => {
    const h = hex.replace('#', '')
    const [r, g, b2] = [0, 2, 4].map(i => parseInt(h.slice(i, i + 2), 16) / 255)
    const ch = c => (c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4)
    return 0.2126 * ch(r) + 0.7152 * ch(g) + 0.0722 * ch(b2)
  }
  const [hi, lo] = [lum(a), lum(b)].sort((x, y) => y - x)
  return (hi + 0.05) / (lo + 0.05)
}

// The two surfaces a marker has to survive: the light blueprint (white) and the
// dark blueprint over the dark card (--bg-card over --bg).
const WHITE = '#ffffff'
const DARK_CARD = '#060628'

// One case per asset type that carries a subtype. A missing branch here means
// the map subtype filter silently drops every asset of that type.
const cases = [
  ['Machine', { machinetypeid: 3 }],
  ['Computer', { computertypeid: 3 }],
  ['Network Device', { networkdevicetypeid: 3 }],
  ['network_device', { networkdevicetypeid: 3 }],
  ['Printer', { printertypeid: 3 }],
  ['Measuring Tool', { measuringtooltypeid: 3 }],
]

describe('getSubtypeId', () => {
  it.each(cases)('resolves the subtype id for %s', (assettype, typedata) => {
    expect(getSubtypeId({ assettype, typedata })).toBe(3)
  })

  it('returns null without typedata', () => {
    expect(getSubtypeId({ assettype: 'Machine' })).toBeNull()
  })

  it('returns null for an asset type with no subtype table', () => {
    expect(getSubtypeId({ assettype: 'Furniture', typedata: {} })).toBeNull()
  })
})

describe('marker legibility', () => {
  const palette = [...new Set(Object.values(assetTypeColorsMap)), UNSPECIFIED_COLOR]

  it.each(palette)('%s clears 3:1 on the white blueprint', (color) => {
    expect(contrast(color, WHITE)).toBeGreaterThanOrEqual(3)
  })

  it.each(palette)('%s clears 3:1 on the dark card', (color) => {
    expect(contrast(color, DARK_CARD)).toBeGreaterThanOrEqual(3)
  })

  it('rings by surface, not by fill', () => {
    // Uniform within a theme: every marker on the white blueprint wears the
    // dark ring, every marker on the dark one wears the light ring. Keying it
    // off the fill singled out the light colors with a black ring.
    expect(markerRingColor('light')).toBe('#1b1b1b')
    expect(markerRingColor('dark')).toBe('#ffffff')
  })

  it('gives each ring 3:1 against the surface it sits on', () => {
    expect(contrast(markerRingColor('light'), WHITE)).toBeGreaterThanOrEqual(3)
    expect(contrast(markerRingColor('dark'), DARK_CARD)).toBeGreaterThanOrEqual(3)
  })

  it('defaults to the light ring when the theme is missing', () => {
    expect(markerRingColor(undefined)).toBe('#ffffff')
  })
})

describe('resolveMarkerColor', () => {
  it('colors a measuring tool by its subtype when a type is selected', () => {
    const asset = { assettype: 'Measuring Tool', typedata: { measuringtooltypeid: 7 } }
    const color = resolveMarkerColor(asset, {
      selectedType: 'Measuring Tool',
      subtypeColors: { 7: '#00BCD4' },
    })
    expect(color).toBe('#00BCD4')
  })
})
