import Fuse from '../dist/fuse.mjs'
import Books from './fixtures/books.json' assert { type: 'json' }

const idx = (result) => result.map((obj) => obj.refIndex)

describe('Logical parser', () => {
  const options = {
    useExtendedSearch: true,
    includeMatches: true,
    includeScore: true,
    keys: ['title', 'author.firstName', 'author.lastName']
  }

  test('Tree structure', () => {
    const query = {
      $and: [
        { title: 'old war' },
        {
          $or: [{ title: '!arts' }, { title: '^lock' }]
        }
      ]
    }
    let root = Fuse.parseQuery(query, options, { auto: false })

    expect(root).toMatchSnapshot()
  })

  test('Implicit operations', () => {
    const query = {
      $and: [
        { title: 'old war' },
        {
          $or: [{ title: '!arts', tags: 'kiro' }, { title: '^lock' }]
        }
      ]
    }
    let root = Fuse.parseQuery(query, options, { auto: false })

    expect(root).toMatchSnapshot()
  })
})

describe('Searching using logical search', () => {
  const options = {
    useExtendedSearch: true,
    includeMatches: true,
    includeScore: true,
    keys: ['title', 'author.firstName', 'author.lastName']
  }
  const fuse = new Fuse(Books, options)

  test('Search: implicit AND', () => {
    let result = fuse.search({ title: 'old man' })
    expect(result.length).toBe(1)
    expect(result[0].refIndex).toBe(0)
    expect(result[0].matches[0].indices).toMatchObject([
      [0, 2],
      [4, 6]
    ])
  })

  test('Search: AND with single item', () => {
    let result = fuse.search({ $and: [{ title: 'old man' }] })
    expect(result.length).toBe(1)
    expect(idx(result)).toMatchObject([0])
    expect(result[0].matches[0].indices).toMatchObject([
      [0, 2],
      [4, 6]
    ])
  })

  test('Search: AND with multiple entries', () => {
    let result = fuse.search({
      $and: [{ 'author.lastName': 'Woodhose' }, { title: 'the' }]
    })
    expect(result.length).toBe(2)
    expect(idx(result)).toMatchObject([4, 5])
  })

  test('Search: AND with multiple entries + exact match', () => {
    let result = fuse.search({
      $and: [{ 'author.lastName': 'Woodhose' }, { title: "'The" }]
    })
    expect(result.length).toBe(1)
    expect(idx(result)).toMatchObject([4])
  })

  test('Search: OR with multiple entries', () => {
    let result = fuse.search({
      $or: [{ title: 'angls' }, { title: 'incmpetnce' }]
    })
    expect(result.length).toBe(3)
    expect(idx(result)).toMatchObject([14, 7, 0])
  })

  test('Search: OR with multiple entries', () => {
    let result = fuse.search({
      $or: [
        { title: 'angls' },
        { $and: [{ title: '!dwarf' }, { title: 'bakwrds' }] }
      ]
    })
    expect(result.length).toBe(2)
    expect(idx(result)).toMatchObject([7, 0])
  })
})

describe('Multiple nested conditions', () => {
  const list1 = [
    {
      title: "Old Man's War",
      author: {
        firstName: 'John',
        lastName: 'Scalzi',
        age: '61'
      }
    }
  ]

  const list2 = [
    ...list1,
    {
      title: "Old Man's War",
      author: {
        firstName: 'John',
        lastName: 'Scalzi',
        age: '62'
      }
    }
  ]

  const options = {
    includeScore: true,
    useExtendedSearch: true,
    keys: ['title', 'author.firstName', 'author.lastName', 'author.age']
  }

  const fuse1 = new Fuse(list1, options)
  const fuse2 = new Fuse(list2, options)

  test('Search: nested AND + OR', () => {
    const result = fuse1.search({
      $and: [
        { title: 'old' },
        {
          $or: [{ 'author.firstName': 'j' }, { 'author.lastName': 'Sa' }]
        },
        {
          $or: [{ 'author.age': "'62" }]
        }
      ]
    })

    expect(result.length).toBe(0)
  })

  test('Search: deep nested AND + OR', () => {
    const result = fuse1.search({
      $and: [
        { title: 'old' },
        {
          $or: [{ 'author.firstName': 'jon' }, { 'author.lastName': 'Sazi' }]
        },
        {
          $or: [
            { 'author.age': "'62" },
            { $and: [{ title: 'old' }, { 'author.age': "'61" }] }
          ]
        }
      ]
    })

    expect(result.length).toBe(1)
    expect(result[0]).toHaveProperty('score')
    expect(result[0].score).toBeGreaterThan(0)
  })

  test('Search: deep nested AND + OR', () => {
    const result = fuse2.search({
      $and: [
        { title: 'old' },
        {
          $and: [{ 'author.firstName': 'jon' }, { 'author.lastName': 'Sazi' }]
        },
        {
          $or: [
            { 'author.age': "'62" },
            { $and: [{ title: 'old' }, { 'author.age': "'62" }] }
          ]
        }
      ]
    })

    expect(result.length).toBe(1)
    expect(result[0]).toHaveProperty('score')
    expect(result[0].score).toBeGreaterThan(0)
  })
})

describe('Logical search with dotted keys', () => {
  const list = [
    {
      title: "Old Man's War",
      author: {
        'first.name': 'John',
        'last.name': 'Scalzi',
        age: '61'
      }
    }
  ]

  const options = {
    useExtendedSearch: true,
    includeScore: true,
    keys: [
      'title',
      ['author', 'first.name'],
      ['author', 'last.name'],
      'author.age'
    ]
  }
  const fuse = new Fuse(list, options)

  test('Search: deep nested AND + OR', () => {
    const query = {
      $and: [
        {
          $path: ['author', 'first.name'],
          $val: 'jon'
        },
        {
          $path: ['author', 'last.name'],
          $val: 'scazi'
        }
      ]
    }

    const result = fuse.search(query)

    expect(result.length).toBe(1)
  })
})

describe('Keyless string entries in logical queries', () => {
  const list = [
    { title: "Old Man's War", author: 'Scalzi' },
    { title: 'The Lock Artist', author: 'Hamilton' },
    { title: 'Old Ways', author: 'Macfarlane' }
  ]

  const options = {
    useExtendedSearch: true,
    keys: ['title', 'author']
  }
  const fuse = new Fuse(list, options)

  test('Search: $and with key + keyless string', () => {
    const result = fuse.search({
      $and: [{ author: 'Scalzi' }, 'Old']
    })
    expect(result.length).toBe(1)
    expect(result[0].item.title).toBe("Old Man's War")
  })

  test('Search: $or with key + keyless string', () => {
    const result = fuse.search({
      $or: [{ title: "'Lock" }, 'Macfarlane']
    })
    expect(result.length).toBe(2)
  })

  test('Search: keyless string matches across all keys', () => {
    const result = fuse.search({
      $and: ['Hamilton']
    })
    expect(result.length).toBe(1)
    expect(result[0].item.author).toBe('Hamilton')
  })
})

describe('Searching using logical OR with same query across fields', () => {
  const options = { keys: ['title', 'author.lastName'] }
  const fuse = new Fuse(Books, options)
  let result
  beforeEach(() => {
    const query = {
      $or: [{ title: 'wood' }, { 'author.lastName': 'wood' }]
    }

    result = fuse.search(query)
  })

  describe('When searching for the term "wood"', () => {
    test('we get the top three results scored based matches from all their fields', () => {
      expect(idx(result.slice(0, 3))).toMatchObject([4, 3, 5])
    })
  })
})
