// Unit tests for the I18N-01 regression gate. The gate's whole value is its
// narrowness — it must fire on "the contract already has a key for this string
// but the component writes it out" and stay silent on everything else, so the
// must-not-fire cases below carry as much weight as the must-fire ones.
import { describe, it, expect } from 'vitest'
import { findHardcodedStrings, readLocaleDictionary } from '../scripts/audit-no-hardcoded-ui-strings.mjs'

const DICT = { paginationOf: 'of', selectPlaceholder: 'Select...', cancel: 'Cancel' }

describe('findHardcodedStrings', () => {
  it('fires on a locale-covered literal inside a template text node', () => {
    const src = `<template>\n  <span>Select...</span>\n</template>`
    const hits = findHardcodedStrings(src, 'src/canonical/Foo.vue', DICT)
    expect(hits).toHaveLength(1)
    expect(hits[0].line).toBe(2)
    expect(hits[0].suggestedKey).toBe('selectPlaceholder')
  })

  // Regression for the gate's own first version, which only matched `>lit<` and
  // therefore returned exit 0 on the exact shape it exists to catch (F1 fault
  // injection caught it). `of` here sits between two mustaches, not tags.
  it('fires on a literal sitting between interpolations, not just between tags', () => {
    const src = [
      '<template>',
      '  <span class="pg-total">',
      '    {{ start }}-{{ end }} of {{ total.toLocaleString() }}',
      '  </span>',
      '</template>',
    ].join('\n')
    const hits = findHardcodedStrings(src, 'src/components/Pagination/Pagination.vue', DICT)
    expect(hits).toHaveLength(1)
    expect(hits[0].line).toBe(3)
    expect(hits[0].suggestedKey).toBe('paginationOf')
  })

  it('does NOT fire on the literal appearing inside a longer word (must-not-fire)', () => {
    // "profile" and "Software" both contain the substring "of".
    const src = `<template>\n  <span>profile Software</span>\n</template>`
    expect(findHardcodedStrings(src, 'src/canonical/Foo.vue', DICT)).toHaveLength(0)
  })

  it('fires on a locale-covered literal used as a prop default', () => {
    const src = `<script setup>\nconst p = withDefaults(defineProps(), { cancelText: 'Cancel' })\n</script>`
    const hits = findHardcodedStrings(src, 'src/components/Bar/Bar.vue', DICT)
    expect(hits.map(h => h.suggestedKey)).toContain('cancel')
  })

  it('does NOT fire when the string already goes through locale (must-not-fire)', () => {
    const src = `<template>\n  <span>{{ locale.selectPlaceholder }}</span>\n</template>`
    expect(findHardcodedStrings(src, 'src/canonical/Foo.vue', DICT)).toHaveLength(0)
  })

  it('does NOT fire on class names, attribute values or CSS (must-not-fire)', () => {
    const src = [
      '<template>',
      '  <span class="of-something" data-x="Cancel" aria-hidden="true"></span>',
      '</template>',
      '<style scoped>',
      '.x { content: "Cancel"; }',
      '</style>',
    ].join('\n')
    expect(findHardcodedStrings(src, 'src/canonical/Foo.vue', DICT)).toHaveLength(0)
  })

  it('does NOT fire on a literal that no locale key covers (must-not-fire)', () => {
    const src = `<template>\n  <span>Totally bespoke product copy</span>\n</template>`
    expect(findHardcodedStrings(src, 'src/canonical/Foo.vue', DICT)).toHaveLength(0)
  })

  it('keeps line numbers correct after blanking a <style> block (must-not-shift)', () => {
    // Style blocks are blanked, not deleted — deleting them would shift every
    // reported line number below the block.
    const src = [
      '<style scoped>',
      '.x { color: red; }',
      '</style>',
      '<template>',
      '  <span>Select...</span>',
      '</template>',
    ].join('\n')
    const hits = findHardcodedStrings(src, 'src/canonical/Foo.vue', DICT)
    expect(hits).toHaveLength(1)
    expect(hits[0].line).toBe(5)
  })
})

describe('readLocaleDictionary', () => {
  it('reads the real defaultLocale so the gate denominator follows the contract', () => {
    const dict = readLocaleDictionary()
    // Byte-equal to the literals this task just replaced — if someone edits the
    // contract, the gate's coverage moves with it rather than needing an update.
    expect(dict.paginationOf).toBe('of')
    expect(dict.paginationJumpTo).toBe('Go to')
    expect(dict.selectPlaceholder).toBe('Select...')
    expect(dict.confirm).toBe('Confirm')
    expect(dict.cancel).toBe('Cancel')
  })

  it('does not pick up TvuLocale interface members (they have no literal)', () => {
    const dict = readLocaleDictionary()
    for (const value of Object.values(dict)) expect(value).not.toBe('string')
  })
})
