import { describe, it, expect } from 'vitest'
import { parseExpr, evaluateExpr, evaluateVisibleWhen } from './exprEvaluator'

describe('parseExpr', () => {
  it('parses equality expression', () => {
    const ast = parseExpr("status == 'active'")
    expect(ast).toEqual({
      type: 'eq',
      field: 'status',
      value: 'active',
    })
  })

  it('parses inequality expression', () => {
    const ast = parseExpr("type != 'internal'")
    expect(ast).toEqual({
      type: 'neq',
      field: 'type',
      value: 'internal',
    })
  })

  it('parses AND expression', () => {
    const ast = parseExpr("status == 'active' AND role == 'admin'")
    expect(ast).toEqual({
      type: 'and',
      left: { type: 'eq', field: 'status', value: 'active' },
      right: { type: 'eq', field: 'role', value: 'admin' },
    })
  })

  it('parses OR expression', () => {
    const ast = parseExpr("status == 'active' OR status == 'pending'")
    expect(ast).toEqual({
      type: 'or',
      left: { type: 'eq', field: 'status', value: 'active' },
      right: { type: 'eq', field: 'status', value: 'pending' },
    })
  })

  it('parses NOT expression', () => {
    const ast = parseExpr("NOT status == 'deleted'")
    expect(ast).toEqual({
      type: 'not',
      operand: { type: 'eq', field: 'status', value: 'deleted' },
    })
  })

  it('parses parenthesized expression', () => {
    const ast = parseExpr("(status == 'a' OR status == 'b') AND role == 'admin'")
    expect(ast?.type).toBe('and')
    expect(ast?.left?.type).toBe('or')
  })

  it('returns null for empty input', () => {
    expect(parseExpr('')).toBeNull()
  })

  it('parses && and || operators', () => {
    const ast = parseExpr("a == '1' && b == '2' || c == '3'")
    expect(ast).toBeDefined()
    expect(ast?.type).toBe('or')
  })
})

describe('evaluateExpr', () => {
  it('evaluates equality true', () => {
    const ast = parseExpr("status == 'active'")!
    expect(evaluateExpr(ast, { status: 'active' })).toBe(true)
  })

  it('evaluates equality false', () => {
    const ast = parseExpr("status == 'active'")!
    expect(evaluateExpr(ast, { status: 'inactive' })).toBe(false)
  })

  it('evaluates inequality', () => {
    const ast = parseExpr("status != 'deleted'")!
    expect(evaluateExpr(ast, { status: 'active' })).toBe(true)
    expect(evaluateExpr(ast, { status: 'deleted' })).toBe(false)
  })

  it('evaluates AND', () => {
    const ast = parseExpr("a == '1' AND b == '2'")!
    expect(evaluateExpr(ast, { a: '1', b: '2' })).toBe(true)
    expect(evaluateExpr(ast, { a: '1', b: '3' })).toBe(false)
    expect(evaluateExpr(ast, { a: '0', b: '2' })).toBe(false)
  })

  it('evaluates OR', () => {
    const ast = parseExpr("a == '1' OR b == '2'")!
    expect(evaluateExpr(ast, { a: '1', b: 'x' })).toBe(true)
    expect(evaluateExpr(ast, { a: 'x', b: '2' })).toBe(true)
    expect(evaluateExpr(ast, { a: 'x', b: 'x' })).toBe(false)
  })

  it('evaluates NOT', () => {
    const ast = parseExpr("NOT a == '1'")!
    expect(evaluateExpr(ast, { a: '1' })).toBe(false)
    expect(evaluateExpr(ast, { a: '2' })).toBe(true)
  })

  it('handles missing field as empty string', () => {
    const ast = parseExpr("status == ''")!
    expect(evaluateExpr(ast, {})).toBe(true)
  })

  it('converts non-string values to string', () => {
    const ast = parseExpr("count == '5'")!
    expect(evaluateExpr(ast, { count: 5 })).toBe(true)
  })
})

describe('evaluateVisibleWhen', () => {
  it('returns true for undefined expression', () => {
    expect(evaluateVisibleWhen(undefined, {})).toBe(true)
  })

  it('returns true for empty string expression', () => {
    expect(evaluateVisibleWhen('', {})).toBe(true)
  })

  it('evaluates complex expression', () => {
    expect(
      evaluateVisibleWhen("type == 'doctor' AND status == 'active'", {
        type: 'doctor',
        status: 'active',
      }),
    ).toBe(true)

    expect(
      evaluateVisibleWhen("type == 'doctor' AND status == 'active'", {
        type: 'doctor',
        status: 'inactive',
      }),
    ).toBe(false)
  })
})
