import { Type } from 'typebox'
import { Fail, Ok } from './~validate.ts'
import { Assert } from 'test'

const Test = Assert.Context('Value.Check.Array')

Test('Should validate an array of any', () => {
  const T = Type.Array(Type.Any())
  Ok(T, [0, true, 'hello', {}])
})
Test('Should not validate varying array when item is number', () => {
  const T = Type.Array(Type.Number())
  Fail(T, [1, 2, 3, 'hello'])
})
Test('Should validate for an array of unions', () => {
  const T = Type.Array(Type.Union([Type.Number(), Type.String()]))
  Ok(T, [1, 'hello', 3, 'world'])
})
Test('Should not validate for an array of unions where item is not in union.', () => {
  const T = Type.Array(Type.Union([Type.Number(), Type.String()]))
  Fail(T, [1, 'hello', 3, 'world', true])
})
Test('Should validate for an empty array', () => {
  const T = Type.Array(Type.Union([Type.Number(), Type.String()]))
  Ok(T, [])
})
Test('Should validate for an array of intersection types', () => {
  const A = Type.Object({ a: Type.String() })
  const B = Type.Object({ b: Type.String() })
  const C = Type.Intersect([A, B])
  const T = Type.Array(C)
  Ok(T, [
    { a: 'hello', b: 'hello' },
    { a: 'hello', b: 'hello' },
    { a: 'hello', b: 'hello' }
  ])
})
Test('Should validate an array of tuples', () => {
  const A = Type.String()
  const B = Type.Number()
  const C = Type.Tuple([A, B])
  const T = Type.Array(C)
  Ok(T, [
    ['hello', 1],
    ['hello', 1],
    ['hello', 1]
  ])
})
Test('Should not validate an array of tuples when tuple values are incorrect', () => {
  const A = Type.String()
  const B = Type.Number()
  const C = Type.Tuple([A, B])
  const T = Type.Array(C)
  Fail(T, [
    [1, 'hello'],
    [1, 'hello'],
    [1, 'hello']
  ])
})
Test('Should not validate array with failed minItems', () => {
  const T = Type.Array(Type.Number(), { minItems: 3 })
  Fail(T, [0, 1])
})
Test('Should not validate array with failed maxItems', () => {
  const T = Type.Array(Type.Number(), { maxItems: 3 })
  Fail(T, [0, 1, 2, 3])
})
// ---------------------------------------------------------
// Unique Items
// ---------------------------------------------------------
Test('Should validate array with uniqueItems when items are distinct objects', () => {
  const T = Type.Array(Type.Object({ x: Type.Number(), y: Type.Number() }), { uniqueItems: true })
  Ok(T, [
    { x: 0, y: 1 },
    { x: 1, y: 0 }
  ])
})
Test('Should not validate array with uniqueItems when items are not distinct objects', () => {
  const T = Type.Array(Type.Object({ x: Type.Number(), y: Type.Number() }), { uniqueItems: true })
  Fail(T, [
    { x: 1, y: 0 },
    { x: 1, y: 0 }
  ])
})
Test('Should not validate array with non uniqueItems', () => {
  const T = Type.Array(Type.Number(), { uniqueItems: true })
  Fail(T, [0, 0])
})
// ---------------------------------------------------------
// Contains
// ---------------------------------------------------------
Test('Should validate for contains', () => {
  const T = Type.Array(Type.Number(), { contains: Type.Literal(1) })
  Ok(T, [1])
  Ok(T, [1, 2])
  Fail(T, [])
  Fail(T, [2])
})
Test('Should validate for minContains', () => {
  const T = Type.Array(Type.Number(), { contains: Type.Literal(1), minContains: 3 })
  Ok(T, [1, 1, 1, 2])
  Ok(T, [2, 1, 1, 1, 2])
  Ok(T, [1, 1, 1])
  Fail(T, [])
  Fail(T, [1, 1])
  Fail(T, [2])
})
Test('Should validate for maxContains', () => {
  const T = Type.Array(Type.Number(), { contains: Type.Literal(1), maxContains: 3 })
  Ok(T, [1, 1, 1])
  Ok(T, [1, 1])
  Ok(T, [2, 2, 2, 2, 1, 1, 1])
  Fail(T, [1, 1, 1, 1])
})
Test('Should validate for minContains and maxContains', () => {
  const T = Type.Array(Type.Number(), { contains: Type.Literal(1), minContains: 3, maxContains: 5 })
  Fail(T, [1, 1])
  Ok(T, [1, 1, 1])
  Ok(T, [1, 1, 1, 1])
  Ok(T, [1, 1, 1, 1, 1])
  Fail(T, [1, 1, 1, 1, 1, 1])
})
Test('Should validate minContains or maxContains when contains is unspecified', () => {
  const T = Type.Array(Type.Number(), { minContains: 3, maxContains: 5 })
  Ok(T, [1, 1])
  Ok(T, [1, 1, 1])
  Ok(T, [1, 1, 1, 1])
  Ok(T, [1, 1, 1, 1, 1])
  Ok(T, [1, 1, 1, 1, 1, 1])
})
Test('Should produce illogical schema when contains is not sub type of items', () => {
  const T = Type.Array(Type.Number(), { contains: Type.String(), minContains: 3, maxContains: 5 })
  Fail(T, [1, 1])
  Fail(T, [1, 1, 1])
  Fail(T, [1, 1, 1, 1])
  Fail(T, [1, 1, 1, 1, 1])
  Fail(T, [1, 1, 1, 1, 1, 1])
})
// ----------------------------------------------------------------
// Contains: https://github.com/sinclairzx81/typebox/pull/1655
// ----------------------------------------------------------------
Test('Should treat contains-matched items as evaluated for unevaluatedItems', () => {
  const T = Type.Unsafe({ type: 'array', contains: Type.String(), unevaluatedItems: false })
  Ok(T, ['foo'])
  Ok(T, ['foo', 'bar'])
  Fail(T, ['foo', 1])
  Fail(T, [1])
})
// ----------------------------------------------------------------
// Issue: https://github.com/sinclairzx81/typebox/discussions/607
// ----------------------------------------------------------------
Test('Should correctly handle undefined array properties', () => {
  const Answer = Type.Object({
    text: Type.String(),
    isCorrect: Type.Boolean()
  })
  const Question = Type.Object({
    text: Type.String(),
    options: Type.Array(Answer, {
      minContains: 1,
      maxContains: 1,
      contains: Type.Object({
        text: Type.String(),
        isCorrect: Type.Literal(true)
      })
    })
  })
  Fail(Question, { text: 'A' })
  Fail(Question, { text: 'A', options: [] })
  Ok(Question, { text: 'A', options: [{ text: 'A', isCorrect: true }] })
  Ok(Question, {
    text: 'A',
    options: [
      { text: 'A', isCorrect: true },
      { text: 'B', isCorrect: false }
    ]
  })
  Fail(Question, { text: 'A', options: [{ text: 'A', isCorrect: false }] })
  Fail(Question, {
    text: 'A',
    options: [
      { text: 'A', isCorrect: true },
      { text: 'B', isCorrect: true }
    ]
  })
})
