/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable no-sparse-arrays */
import { assert, test } from 'vitest'

import { cx } from '..'

test('comments', () => {
  assert.strictEqual(
    cx`
      underline
      /* multi
        line
        comment
      */
      hover:focus:!(
        sm:(italic why)
        lg:-(px)
        -mx-1
      )
      ${false} ${undefined} ${null} ${''}
      /* Position */
      !top-1 !-bottom-${2} mx-${0}
      text-(xl black)
    `,
    'underline hover:focus:sm:!italic hover:focus:sm:!why hover:focus:lg:!-px hover:focus:!-mx-1 !top-1 !-bottom-2 mx-0 text-xl text-black',
  )
})

test('nothing, null, undefined', () => {
  assert.strictEqual(cx(), '')
  assert.strictEqual(cx(null), '')
  assert.strictEqual(cx(undefined), '')
  assert.strictEqual(cx([, , , null, undefined]), '')
})

test('empty objects', () => {
  assert.strictEqual(cx({}), '')
  assert.strictEqual(cx([]), '')
  assert.strictEqual(cx([{}]), '')
  assert.strictEqual(cx([{}, {}, {}]), '')
})

test('booleans', () => {
  assert.strictEqual(cx(true), '')
  assert.strictEqual(cx(false), '')
  assert.strictEqual(cx([true, false]), '')
})

test('numbers', () => {
  assert.strictEqual(cx(0), '')
  assert.strictEqual(cx([0, 1]), '1')
  assert.strictEqual(cx({ 0: true, 1: true }), '0 1')
})

test('empty strings', () => {
  assert.strictEqual(cx(''), '')
  assert.strictEqual(
    cx({
      elf: '',
      orc: '',
      gnome: '',
    }),
    '',
  )
  assert.strictEqual(cx(['', '', '']), '')
})

test('arrays of strings', () => {
  assert.strictEqual(cx(['elf', 'orc', false, 'gnome']), 'elf orc gnome')
})

test('array of arrays', () => {
  assert.strictEqual(cx(['elf', ['orc', [false, 'gnome']]]), 'elf orc gnome')
})

test('object of key:string pairs', () => {
  assert.strictEqual(
    cx({
      elf: true,
      orc: true,
      dodo: false,
      gnome: true,
    }),
    'elf orc gnome',
  )
})

test('array of objects and arrays', () => {
  assert.strictEqual(
    cx([
      'elf',
      'half-orc',
      {
        'half-elf': true,
      },
      ['gnome', 'goblin', 'dwarf'],
    ]),
    'elf half-orc half-elf gnome goblin dwarf',
  )
})

test('cx', () => {
  assert.strictEqual(typeof cx, 'function', 'exports a function')
  assert.strictEqual(typeof cx(), 'string', '~> returns string output')
})

test('strings', () => {
  assert.strictEqual(cx(''), '')
  assert.strictEqual(cx('foo'), 'foo')
  assert.strictEqual(cx(true && 'foo'), 'foo')
  assert.strictEqual(cx(false && 'foo'), '')
})

test('strings (variadic)', () => {
  assert.strictEqual(cx(''), '')
  assert.strictEqual(cx('foo', 'bar'), 'foo bar')
  assert.strictEqual(cx(true && 'foo', false && 'bar', 'baz'), 'foo baz')
  assert.strictEqual(cx(false && 'foo', 'bar', 'baz', ''), 'bar baz')
})

test('objects', () => {
  assert.strictEqual(cx({}), '')
  assert.strictEqual(cx({ foo: true }), 'foo')
  assert.strictEqual(cx({ foo: true, bar: false }), 'foo')
  assert.strictEqual(cx({ foo: 'hiya', bar: 1 }), 'foo bar')
  assert.strictEqual(cx({ foo: 1, bar: 0, baz: 1 }), 'foo baz')
  assert.strictEqual(cx({ '-foo': 1, '--bar': 1 }), '-foo --bar')
})

test('objects (variadic)', () => {
  assert.strictEqual(cx({}, {}), '')
  assert.strictEqual(cx({ foo: 1 }, { bar: 2 }), 'foo bar')
  assert.strictEqual(cx({ foo: 1 }, null, { baz: 1, bat: 0 }), 'foo baz')
  assert.strictEqual(
    cx({ foo: 1 }, {}, {}, { bar: 'a' }, { baz: null, bat: Infinity }),
    'foo bar bat',
  )
})

test('arrays', () => {
  assert.strictEqual(cx([]), '')
  assert.strictEqual(cx(['foo']), 'foo')
  assert.strictEqual(cx(['foo', 'bar']), 'foo bar')
  assert.strictEqual(cx(['foo', 0 && 'bar', 1 && 'baz']), 'foo baz')
})

test('arrays (nested)', () => {
  assert.strictEqual(cx([[[]]]), '')
  assert.strictEqual(cx([[['foo']]]), 'foo')
  assert.strictEqual(cx([true, [['foo']]]), 'foo')
  assert.strictEqual(cx(['foo', ['bar', ['', [['baz']]]]]), 'foo bar baz')
})

test('arrays (variadic)', () => {
  assert.strictEqual(cx([], []), '')
  assert.strictEqual(cx(['foo'], ['bar']), 'foo bar')
  assert.strictEqual(cx(['foo'], null, ['baz', ''], true, '', []), 'foo baz')
})

test('arrays (no `push` escape)', () => {
  assert.strictEqual(cx({ push: 1 }), 'push')
  assert.strictEqual(cx({ pop: true }), 'pop')
  assert.strictEqual(cx({ push: true }), 'push')
  assert.strictEqual(cx('hello', { world: 1, push: true }), 'hello world push')
})

test('(compat) keeps object keys with truthy values', () => {
  const out = cx({ a: true, b: false, c: 0, d: null, e: undefined, f: 1 })
  assert.strictEqual(out, 'a f')
})

test('(compat) joins arrays of class names and ignore falsy values', () => {
  const out = cx('a', 0, null, undefined, true, 1, 'b')
  assert.strictEqual(out, 'a 1 b')
})

test('(compat) supports heterogenous arguments', () => {
  assert.strictEqual(cx({ a: true }, 'b', 0), 'a b')
  assert.strictEqual(cx(0, { a: true }, 'b'), 'a b')
})

test('(compat) should be trimmed', () => {
  assert.strictEqual(cx('', 'b', {}, ''), 'b')
})

test('(compat) returns an empty string for an empty configuration', () => {
  assert.strictEqual(cx({}), '')
})

test('(compat) supports an array of class names', () => {
  assert.strictEqual(cx(['a', 'b']), 'a b')
})

test('(compat) joins array arguments with string arguments', () => {
  assert.strictEqual(cx(['a', 'b'], 'c'), 'a b c')
  assert.strictEqual(cx('c', ['a', 'b']), 'c a b')
})

test('(compat) handles multiple array arguments', () => {
  assert.strictEqual(cx(['a', 'b'], ['c', 'd']), 'a b c d')
})

test('(compat) handles arrays that include falsy and true values', () => {
  assert.strictEqual(cx(['a', 0, null, undefined, false, true, 'b']), 'a b')
})

test('(compat) handles arrays that include arrays', () => {
  assert.strictEqual(cx(['a', ['b', 'c']]), 'a b c')
})

test('(compat) handles arrays that include objects', () => {
  assert.strictEqual(cx(['a', { b: true, c: false }]), 'a b')
})

test('(compat) handles deep array recursion', () => {
  assert.strictEqual(cx(['a', ['b', ['c', { d: true }]]]), 'a b c d')
})

test('(compat) handles arrays that are empty', () => {
  assert.strictEqual(cx('a', []), 'a')
})

test('(compat) handles nested arrays that have empty nested arrays', () => {
  assert.strictEqual(cx('a', [[]]), 'a')
})

test('(compat) handles all types of truthy and falsy property values as expected', () => {
  const out = cx({
    // falsy:
    null: null,
    emptyString: '',
    noNumber: NaN,
    zero: 0,
    negativeZero: -0,
    false: false,
    undefined: undefined,

    // truthy (literally anything else):
    nonEmptyString: 'foobar',
    whitespace: ' ',
    // eslint-disable-next-line @typescript-eslint/unbound-method
    function: Object.prototype.toString,
    emptyObject: {},
    nonEmptyObject: { a: 1, b: 2 },
    emptyList: [],
    nonEmptyList: [1, 2, 3],
    greaterZero: 1,
  })

  assert.strictEqual(
    out,
    'nonEmptyString whitespace function emptyObject nonEmptyObject emptyList nonEmptyList greaterZero',
  )
})
