import { describe, it, expect } from 'vitest'
import {
  APPROVE_PHRASES,
  REJECT_PHRASES,
  MIN_APPROVE_CONFIDENCE,
  NEGATION_PREFIXES,
  normalizeTranscript,
  startsWithNegation,
  matchCommand,
  type VoiceMatchContext,
} from '../public/voice-commands.js'

/** Default context: a held tool-gate permission (the common case under test). */
function pendingToolCtx(overrides: Partial<VoiceMatchContext> = {}): VoiceMatchContext {
  return { pendingApproval: true, gate: 'tool', ...overrides }
}

describe('normalizeTranscript', () => {
  it('lowercases', () => {
    expect(normalizeTranscript('YES')).toBe('yes')
  })

  it('strips trailing ASCII punctuation', () => {
    expect(normalizeTranscript('confirm.')).toBe('confirm')
  })

  it('strips CJK punctuation/quotes', () => {
    expect(normalizeTranscript('「确认」')).toBe('确认')
  })

  it('strips apostrophes so contractions collapse (dont)', () => {
    expect(normalizeTranscript("don't")).toBe('dont')
  })

  it('collapses whitespace and trims', () => {
    expect(normalizeTranscript('   ')).toBe('')
    expect(normalizeTranscript('  go   ahead  ')).toBe('go ahead')
  })

  it('is idempotent (already-normalized input passes through)', () => {
    expect(normalizeTranscript('go ahead')).toBe('go ahead')
    expect(normalizeTranscript('确认')).toBe('确认')
  })
})

describe('startsWithNegation', () => {
  it('detects CJK negation prefixes', () => {
    expect(startsWithNegation('不可以')).toBe(true)
    expect(startsWithNegation('别继续')).toBe(true)
    expect(startsWithNegation('没关系')).toBe(true)
    expect(startsWithNegation('未确认')).toBe(true)
    expect(startsWithNegation('勿动')).toBe(true)
  })

  it('detects English negation prefixes', () => {
    expect(startsWithNegation('no way')).toBe(true)
    expect(startsWithNegation('not now')).toBe(true)
    expect(startsWithNegation('dont approve')).toBe(true)
    expect(startsWithNegation('cannot proceed')).toBe(true)
    expect(startsWithNegation('wont continue')).toBe(true)
    expect(startsWithNegation('never mind')).toBe(true)
  })

  it('does not flag ordinary phrases', () => {
    expect(startsWithNegation('确认')).toBe(false)
    expect(startsWithNegation('yes')).toBe(false)
    expect(startsWithNegation('now')).toBe(false)
  })
})

describe('matchCommand — context gating (decision 1)', () => {
  it('returns text when pendingApproval is false, even for a command word', () => {
    expect(matchCommand('yes', { pendingApproval: false, gate: null })).toBe('text')
    expect(matchCommand('确认', { pendingApproval: false, gate: null })).toBe('text')
  })

  it('returns text for an empty/whitespace transcript', () => {
    expect(matchCommand('   ', pendingToolCtx())).toBe('text')
    expect(matchCommand('', pendingToolCtx())).toBe('text')
  })
})

describe('matchCommand — plan gate (decision A)', () => {
  it('falls through to dictation when gate is plan, not tool', () => {
    expect(matchCommand('confirm', { pendingApproval: true, gate: 'plan' })).toBe('text')
    expect(matchCommand('stop', { pendingApproval: true, gate: 'plan' })).toBe('text')
  })
})

describe('matchCommand — held tool approve', () => {
  const approvePhrases = [
    '确认', '批准', '同意', '好', '好的', '好吧', '行', '可以',
    'yes', 'ok', 'confirm', 'approve', 'go ahead',
  ]
  for (const phrase of approvePhrases) {
    it(`approves "${phrase}"`, () => {
      expect(matchCommand(phrase, pendingToolCtx())).toBe('approve')
    })
  }
})

describe('matchCommand — held tool reject', () => {
  const rejectPhrases = ['拒绝', '取消', '不行', 'no', 'nope', 'cancel', 'deny', 'abort', 'stop']
  for (const phrase of rejectPhrases) {
    it(`rejects "${phrase}"`, () => {
      expect(matchCommand(phrase, pendingToolCtx())).toBe('reject')
    })
  }
})

describe('matchCommand — negation/filler regression (MUST NOT approve)', () => {
  it('不可以 rejects (negated approve phrase is a listed reject phrase)', () => {
    expect(matchCommand('不可以', pendingToolCtx())).toBe('reject')
  })

  it('不允许/不通过/不同意/不批准 reject', () => {
    expect(matchCommand('不允许', pendingToolCtx())).toBe('reject')
    expect(matchCommand('不通过', pendingToolCtx())).toBe('reject')
    expect(matchCommand('不同意', pendingToolCtx())).toBe('reject')
    expect(matchCommand('不批准', pendingToolCtx())).toBe('reject')
  })

  it('不继续 is negated but not a listed reject phrase -> text', () => {
    expect(matchCommand('不继续', pendingToolCtx())).toBe('text')
  })

  it('English negated phrases fall through to text', () => {
    expect(matchCommand("don't approve", pendingToolCtx())).toBe('text')
    expect(matchCommand('do not confirm', pendingToolCtx())).toBe('text')
    expect(matchCommand('cannot proceed', pendingToolCtx())).toBe('text')
    expect(matchCommand("won't continue", pendingToolCtx())).toBe('text')
  })

  it('"ok let\'s not do that" is not a whole-utterance match -> text', () => {
    expect(matchCommand("ok let's not do that", pendingToolCtx())).toBe('text')
  })

  it('"ok can you also check the logs" is not a whole-utterance match -> text', () => {
    expect(matchCommand('ok can you also check the logs', pendingToolCtx())).toBe('text')
  })

  it('我觉得可以先看看 is not a whole-utterance match -> text', () => {
    expect(matchCommand('我觉得可以先看看', pendingToolCtx())).toBe('text')
  })

  it('请继续 is not a whole-utterance match (unequal to 继续) -> text', () => {
    expect(matchCommand('请继续', pendingToolCtx())).toBe('text')
  })

  it('now is not a listed phrase (unequal to no) -> text', () => {
    expect(matchCommand('now', pendingToolCtx())).toBe('text')
  })

  it('我不知道 is not a whole-utterance match -> text', () => {
    expect(matchCommand('我不知道', pendingToolCtx())).toBe('text')
  })
})

describe('matchCommand — confidence gate (decision D)', () => {
  it('low-confidence approve falls through to text', () => {
    expect(matchCommand('confirm', pendingToolCtx({ confidence: 0.4 }))).toBe('text')
  })

  it('high-confidence approve succeeds', () => {
    expect(matchCommand('confirm', pendingToolCtx({ confidence: 0.9 }))).toBe('approve')
  })

  it('confidence at exactly MIN_APPROVE_CONFIDENCE is sufficient', () => {
    expect(matchCommand('confirm', pendingToolCtx({ confidence: MIN_APPROVE_CONFIDENCE }))).toBe(
      'approve',
    )
  })

  it('reject is unaffected by low confidence', () => {
    expect(matchCommand('no', pendingToolCtx({ confidence: 0.2 }))).toBe('reject')
  })

  it('approve with no confidence supplied succeeds (confidence optional)', () => {
    expect(matchCommand('confirm', pendingToolCtx())).toBe('approve')
  })
})

describe('phrase-set integrity invariants', () => {
  it('every phrase is already normalized (normalize(p) === p)', () => {
    for (const p of [...APPROVE_PHRASES, ...REJECT_PHRASES]) {
      expect(normalizeTranscript(p)).toBe(p)
    }
  })

  it('APPROVE_PHRASES and REJECT_PHRASES are disjoint', () => {
    const rejectSet = new Set(REJECT_PHRASES)
    const overlap = APPROVE_PHRASES.filter((p) => rejectSet.has(p))
    expect(overlap).toEqual([])
  })

  it('no APPROVE_PHRASES entry starts with a negation prefix', () => {
    for (const p of APPROVE_PHRASES) {
      expect(startsWithNegation(p)).toBe(false)
    }
  })

  it('NEGATION_PREFIXES is non-empty and covers both CJK and English', () => {
    expect(NEGATION_PREFIXES.length).toBeGreaterThan(0)
    expect(NEGATION_PREFIXES).toContain('不')
    expect(NEGATION_PREFIXES).toContain('no')
  })
})
