// applyThemeTokens — writing the server-resolved theme onto the document, and
// (the part with real logic) taking back exactly what it wrote last time.
//
// Pure module, exercised against a fake CSSStyleDeclaration: node --test has no
// DOM, and the function only ever needs setProperty/removeProperty.
import { test } from 'node:test'
import assert from 'node:assert/strict'

import { applyThemeTokens } from '../src/lib/themeVars.js'

// Minimal stand-in for element.style, plus a log of the calls so a test can
// assert that a property was *removed* rather than merely absent.
function fakeStyle() {
  const props = new Map()
  const removed = []
  return {
    props,
    removed,
    setProperty: (name, value) => props.set(name, value),
    removeProperty: (name) => {
      props.delete(name)
      removed.push(name)
    },
    get: (name) => props.get(name),
  }
}

test('writes each token and reports the keys it applied', () => {
  const style = fakeStyle()
  const applied = applyThemeTokens(style, { '--accent': '#c9973f', '--bg': '#1a120b' })
  assert.equal(style.get('--accent'), '#c9973f')
  assert.equal(style.get('--bg'), '#1a120b')
  assert.deepEqual(applied.sort(), ['--accent', '--bg'])
})

// The untouched-instance case: no theme block means the stylesheet's :root
// stands and nothing is written at all.
test('no theme writes nothing', () => {
  for (const empty of [null, undefined, {}]) {
    const style = fakeStyle()
    const applied = applyThemeTokens(style, empty)
    assert.equal(style.props.size, 0)
    assert.deepEqual(applied, [])
  }
})

test('removes a token that is no longer in the theme', () => {
  const style = fakeStyle()
  const first = applyThemeTokens(style, { '--accent': '#c9973f', '--bg': '#1a120b' })
  const second = applyThemeTokens(style, { '--accent': '#c9973f' }, first)
  assert.equal(style.get('--accent'), '#c9973f')
  assert.equal(style.get('--bg'), undefined)
  assert.deepEqual(style.removed, ['--bg'])
  assert.deepEqual(second, ['--accent'])
})

// "Reset to defaults" — the case that would look broken without the removal
// half: the payload stops mentioning the variables, and the inline values have
// to come off for :root to show through again.
test('resetting to no theme clears everything previously applied', () => {
  const style = fakeStyle()
  const first = applyThemeTokens(style, { '--accent': '#c9973f', '--radius-card': '2px' })
  const second = applyThemeTokens(style, null, first)
  assert.equal(style.props.size, 0)
  assert.deepEqual(style.removed.sort(), ['--accent', '--radius-card'])
  assert.deepEqual(second, [])
})

// Only ever clears its own keys. SiteContext writes --accent itself from
// brand.accent, and a future feature may write others; those are not ours.
test('never removes a property it did not apply', () => {
  const style = fakeStyle()
  style.setProperty('--accent', '#ff0000') // someone else's write
  applyThemeTokens(style, { '--bg': '#000000' }, [])
  assert.equal(style.get('--accent'), '#ff0000')
  assert.deepEqual(style.removed, [])
})

test('ignores anything that is not a custom property', () => {
  const style = fakeStyle()
  const applied = applyThemeTokens(style, { background: 'url(http://evil.example/x)', '--bg': '#000000' })
  assert.equal(style.get('background'), undefined)
  assert.deepEqual(applied, ['--bg'])
})

test('ignores non-string and empty values', () => {
  const style = fakeStyle()
  const applied = applyThemeTokens(style, { '--a': 4, '--b': null, '--c': '', '--d': '#fff' })
  assert.deepEqual(applied, ['--d'])
})

// A stale key list must not survive a call that could not write: the next call
// still has to know what is actually on the element.
test('a token dropped as invalid is removed if it was applied before', () => {
  const style = fakeStyle()
  const first = applyThemeTokens(style, { '--bg': '#000000' })
  const second = applyThemeTokens(style, { '--bg': '' }, first)
  assert.equal(style.get('--bg'), undefined)
  assert.deepEqual(second, [])
})
