import assert from "assert"
import { TrieSearch } from "./trie-search"

describe("TrieSearch", function () {
  describe("new TrieSearch(keyFields) should work", function () {
    it("should set keyfields (1)", function () {
      var ts = new TrieSearch(["key"])
      assert.equal(ts.keyFields.length, 1)
    })

    it("should set keyfields (2)", function () {
      var ts = new TrieSearch("key")
      assert.equal(ts.keyFields.length, 1)
    })
  })

  describe("new TrieSearch(keyFields).keyToArr should work", function () {
    it("'key' -> ['k', 'e', 'y']", function () {
      var ts = new TrieSearch()
      assert.equal(ts.keyToArr("key")[0], "k")
      assert.equal(ts.keyToArr("key")[1], "e")
      assert.equal(ts.keyToArr("key")[2], "y")
    })

    it("for options.min == 2, 'key' -> ['ke', 'y']", function () {
      var ts = new TrieSearch("blah", { min: 2 })
      assert.equal(ts.keyToArr("key")[0], "ke")
      assert.equal(ts.keyToArr("key")[1], "y")
    })

    it("for options.min == 2, 'keyset' -> ['ke', 'y', 's', 'e', 't']", function () {
      var ts = new TrieSearch("blah", { min: 2 })
      assert.equal(ts.keyToArr("keyset")[0], "ke")
      assert.equal(ts.keyToArr("keyset")[1], "y")
      assert.equal(ts.keyToArr("keyset")[2], "s")
      assert.equal(ts.keyToArr("keyset")[3], "e")
      assert.equal(ts.keyToArr("keyset")[4], "t")
    })

    it("for options.min == 3, 'key' -> ['key']", function () {
      var ts = new TrieSearch("blah", { min: 3 })
      assert.equal(ts.keyToArr("key")[0], "key")
    })

    it("for options.min == 4, 'key' -> []", function () {
      var ts = new TrieSearch("blah", { min: 4 })
      assert.equal(ts.keyToArr("key").length, 0)
    })
  })

  describe("TrieSearch::add(...) and TrieSearch::get(...) should work for a single item", function () {
    const ts = new TrieSearch("key"),
      item = { key: "blah" }
    ts.add(item)

    it("add('blah') should build map of nodes", function () {
      assert(ts.root["b"] !== undefined, "b does not exist")
      // @ts-ignore
      assert(ts.root["b"]["l"] !== undefined, "bl does not exist")
      // @ts-ignore
      assert(ts.root["b"]["l"]["a"] !== undefined, "bla does not exist")
      // @ts-ignore
      assert(ts.root["b"]["l"]["a"]["h"] !== undefined, "blah does not exist")
    })

    it("get('blah') for each subkey should work", function () {
      assert.equal(ts.get("b")[0], item)
      assert.equal(ts.get("bl")[0], item)
      assert.equal(ts.get("bla")[0], item)
      assert.equal(ts.get("blah")[0], item)
      assert.equal(ts.get("blab").length, 0)
      assert.equal(ts.get("nope").length, 0)
    })
  })

  describe("TrieSearch::addAll(...)should work for an array", function () {
    var ts = new TrieSearch("key"),
      items = [{ key: "addendum" }, { key: "banana" }, { key: "cat" }]

    ts.addAll(items)

    it("get('blah') for each subkey should work", function () {
      assert.equal(ts.get("b")[0], items[1])
      assert.equal(ts.get("ba")[0], items[1])
      assert.equal(ts.get("ban")[0], items[1])
      assert.equal(ts.get("bana")[0], items[1])
      assert.equal(ts.get("banana")[0], items[1])

      assert.equal(ts.get("a")[0], items[0])
      assert.equal(ts.get("ad")[0], items[0])
      assert.equal(ts.get("add")[0], items[0])
      assert.equal(ts.get("adde")[0], items[0])
      assert.equal(ts.get("addendum")[0], items[0])

      assert.equal(ts.get("c")[0], items[2])
      assert.equal(ts.get("ca")[0], items[2])
      assert.equal(ts.get("cat")[0], items[2])
    })
  })

  describe("TrieSearch::add(...) and TrieSearch::get(...) should work for a single item with a numeric key", function () {
    var ts = new TrieSearch("key"),
      item = { key: 1234567890 }

    ts.add(item)

    it("add('1234567890') should build map of nodes", function () {
      assert(ts.root["1"] !== undefined, "1 does not exist")
      // @ts-ignore
      assert(ts.root["1"]["2"] !== undefined, "12 does not exist")
      // @ts-ignore
      assert(ts.root["1"]["2"]["3"] !== undefined, "123 does not exist")
      // @ts-ignore
      assert(ts.root["1"]["2"]["3"]["4"] !== undefined, "1234 does not exist")
    })

    it("get('1234567890') for each subkey should work", function () {
      assert.equal(ts.get("1")[0], item)
      assert.equal(ts.get("12")[0], item)
      assert.equal(ts.get("123")[0], item)
      assert.equal(ts.get("1234")[0], item)
      assert.equal(ts.get("1234")[0], item)
      assert.equal(ts.get("12345")[0], item)
      assert.equal(ts.get("123456")[0], item)
      assert.equal(ts.get("1234567")[0], item)
      assert.equal(ts.get("nope").length, 0)
    })
  })

  describe("TrieSearch::add(...) and TrieSearch::get(...) should work for a single item with no split and whitespace", function () {
    var ts = new TrieSearch("key", {
        splitOnRegEx: undefined,
      }),
      item = { key: "hello world" }

    ts.add(item)

    it("add('hello world') should build map of nodes", function () {
      // @ts-ignore
      assert(ts.root.h.e.l.l.o !== undefined, "'hello' does not exist")
      // @ts-ignore
      assert(ts.root.h.e.l.l.o[" "] !== undefined, "'hello ' does not exist")
      assert(
        // @ts-ignore
        ts.root.h.e.l.l.o[" "].w.o.r.l.d !== undefined,
        "'hello world' does not exist"
      )
    })

    it("get('hello world') for each subkey should work", function () {
      assert.equal(ts.get("h")[0], item)
      assert.equal(ts.get("he")[0], item)
      assert.equal(ts.get("hel")[0], item)
      assert.equal(ts.get("hell")[0], item)
      assert.equal(ts.get("hello")[0], item)
      assert.equal(ts.get("hello ")[0], item)
      assert.equal(ts.get("hello w")[0], item)
      assert.equal(ts.get("hello wo")[0], item)
      assert.equal(ts.get("hello wor")[0], item)
      assert.equal(ts.get("hello worl")[0], item)
      assert.equal(ts.get("hello world")[0], item)
      assert.equal(ts.get("nope").length, 0)
    })
  })

  describe("TrieSearch::get(...) should work for multiple keys and union the result", function () {
    var ts = new TrieSearch("key", { min: 2 }),
      item1 = { key: "the quick brown fox" },
      item2 = { key: "the quick brown" },
      item3 = { key: "the quick fox" },
      item4 = { key: "the fox" }

    ts.add(item1)
    ts.add(item2)
    ts.add(item3)
    ts.add(item4)

    it("get('the quick') should return all entries", function () {
      expect(ts.get("th").length).toBe(4)
      expect(ts.get("the").length).toBe(4)
      expect(ts.get("the quick").length).toBe(3)
    })

    it("get('the brown') should return 2 entries", function () {
      expect(ts.get("the brown").length).toBe(2)
    })

    it("get('the fox') should return 3 entries", function () {
      expect(ts.get("the fox").length).toBe(3)
    })

    it("get('fox brown') should return 1 entry", function () {
      expect(ts.get("fox brown").length).toBe(1)
    })

    it("get('brown fox') should return 1 entry", function () {
      expect(ts.get("brown fox").length).toBe(1)
    })

    it("get('brown f') should return 2 entry, ignoring the shortness of the second word", function () {
      expect(ts.get("brown f").length).toBe(2)
    })

    it("get('br f') should return 1 entry, ignoring the shortness of the second word", function () {
      expect(ts.get("br f").length).toBe(2)
    })

    it("get('qui b c d e f g h') should return 3 entries, ignoring the shortness of all subsequent words", function () {
      expect(ts.get("qui b c d e f g h").length).toBe(3)
    })
  })

  describe("TrieSearch::get(...) should work for array of phrases", function () {
    var ts = new TrieSearch("key", { min: 2 }),
      item1 = { key: "the quick brown fox" },
      item2 = { key: "the quick brown" },
      item3 = { key: "the quick fox" },
      item4 = { key: "the fox" }

    ts.add(item1)
    ts.add(item2)
    ts.add(item3)
    ts.add(item4)

    it("get(['the brown', 'quick']) should return 3 entries", function () {
      expect(ts.get(["the brown", "quick"]).length).toBe(3)
    })
  })

  describe("TrieSearch::get(...) should work with cache==true", function () {
    var ts = new TrieSearch("key", { min: 2 }),
      item1 = { key: "the quick brown fox" },
      item2 = { key: "the quick brown" },
      item3 = { key: "the quick fox" },
      item4 = { key: "the fox" }

    ts.add(item1)
    ts.add(item2)
    ts.add(item3)
    ts.add(item4)

    it("_get() should return identical array for the same request", function () {
      var f1 = ts._get("the brown"),
        f2 = ts._get("the brown")

      assert(f1 === f2, "was not the same array.")
    })

    it("_get() should clear cache when clearCache() is called", function () {
      var f1 = ts._get("the brown")
      ts.clearCache()
      var f2 = ts._get("the brown")

      assert(f1 !== f2, "cache was not cleared.")
    })
  })

  describe("TrieSearch::get(...) should work with cache==true and maxCacheSize == X", function () {
    var ts = new TrieSearch("key", {
        min: 2,
        maxCacheSize: 2,
      }),
      item1 = { key: "the quick brown fox" },
      item2 = { key: "the quick brown" },
      item3 = { key: "the quick fox" },
      item4 = { key: "the fox" }

    ts.add(item1)
    ts.add(item2)
    ts.add(item3)
    ts.add(item4)

    it("cache size should increment appropriately and cap at maxCacheSize.", function () {
      var f1 = ts._get("the brown")
      assert(ts.getCache.all.length == 1, "Cache size was not size 1.")
      ts._get("the quick")
      assert(
        ts.getCache.all.length == 2,
        "Cache size was not size 2 (this quick)."
      ) // Should cap at 2.
      ts._get("the fox")
      assert(
        ts.getCache.all.length == 2,
        "Cache size was not size 2 (the fox)."
      ) // Should remain at 2.
      var f2 = ts._get("the brown") // This should return different array.

      assert(f1 !== f2, "cache did not clear out old value!")
    })
  })

  describe("TrieSearch::get(...) should work with cache==false", function () {
    var ts = new TrieSearch("key", { min: 2, cache: false }),
      item1 = { key: "the quick brown fox" },
      item2 = { key: "the quick brown" },
      item3 = { key: "the quick fox" },
      item4 = { key: "the fox" }

    ts.add(item1)
    ts.add(item2)
    ts.add(item3)
    ts.add(item4)

    it("_get() should return DIFFERENT array for the same request", function () {
      var f1 = ts._get("the brown"),
        f2 = ts._get("the brown")

      assert(f1 !== f2, "was not the same array.")
    })
  })

  describe("TrieSearch::get(...) should work for multiple keys and union the result with an indexField", function () {
    var ts = new TrieSearch(["key", "key2"], { min: 2, indexField: "ix" }),
      item1 = { key: "the quick brown fox", key2: "jumped", ix: 1 },
      item2 = { key: "the quick brown", key2: "jumped", ix: 2 },
      item3 = { key: "the quick fox", key2: "brown", ix: 3 },
      item4 = { key: "the fox", key2: "quick brown", ix: 4 }

    ts.add(item1)
    ts.add(item2)
    ts.add(item3)
    ts.add(item4)

    it("get('the quick') should return all 4 entries", function () {
      expect(ts.get("the quick").length).toBe(4)
    })

    it("get('the brown') should return all 4 entries", function () {
      expect(ts.get("the brown").length).toBe(4)
    })

    it("get('the fox') should return 3 entries", function () {
      expect(ts.get("the fox").length).toBe(3)
    })

    it("get('fox brown') should return 3 entries", function () {
      expect(ts.get("fox brown").length).toBe(3)
    })

    it("get('brown fox') should return 3 entries", function () {
      expect(ts.get("brown fox").length).toBe(3)
    })

    it("get('brown z') should return 4 entries", function () {
      expect(ts.get("brown z").length).toBe(4)
    })

    it("get('br f') should return all entries", function () {
      expect(ts.get("br f").length).toBe(4)
    })

    it("get('jum b c d e f g h') should return 2 entries, ignoring the shortness of all subsequent words", function () {
      expect(ts.get("jum b c d e f g h").length).toBe(2)
    })
  })

  describe("TrieSearch::get(...) should work for a deep key combined with a non-deep key", function () {
    var ts = new TrieSearch(["key", ["key2", "key3"]], {
        min: 2,
        indexField: "ix",
      }),
      item1 = { key: "the quick brown fox", key2: { key3: "jumped" }, ix: 1 },
      item2 = { key: "the quick brown", key2: { key3: "jumped" }, ix: 2 },
      item3 = { key: "the quick fox", key2: { key3: "brown" }, ix: 3 },
      item4 = { key: "the fox", key2: { key3: "quick brown" }, ix: 4 }

    ts.add(item1)
    ts.add(item2)
    ts.add(item3)
    ts.add(item4)

    it("get('the quick') should return all 4 entries", function () {
      expect(ts.get("the quick").length).toBe(4)
    })

    it("get('the brown') should return all 4 entries", function () {
      expect(ts.get("the brown").length).toBe(4)
    })

    it("get('the fox') should return 3 entries", function () {
      expect(ts.get("the fox").length).toBe(3)
    })

    it("get('fox brown') should return 3 entries", function () {
      expect(ts.get("fox brown").length).toBe(3)
    })

    it("get('brown fox') should return 3 entries", function () {
      expect(ts.get("brown fox").length).toBe(3)
    })

    it("get('brown z') should return 4 entries", function () {
      expect(ts.get("brown z").length).toBe(4)
    })

    it("get('br f') should return all entries", function () {
      expect(ts.get("br f").length).toBe(4)
    })

    it("get('jum b c d e f g h') should return 2 entries, ignoring the shortness of all subsequent words", function () {
      expect(ts.get("jum b c d e f g h").length).toBe(2)
    })
  })

  describe("TrieSearch::add(...) and TrieSearch::get(...) should work for a single item with multiple subphrases", function () {
    var ts = new TrieSearch("key"),
      item = { key: "blah whatever yeah man" }

    ts.add(item)

    it("add('blah') should build map of nodes", function () {
      assert(ts.root["b"] !== undefined, "b does not exist")
      // @ts-ignore
      assert(ts.root["b"]["l"] !== undefined, "bl does not exist")
      // @ts-ignore
      assert(ts.root["b"]["l"]["a"] !== undefined, "bla does not exist")
      // @ts-ignore
      assert(ts.root["b"]["l"]["a"]["h"] !== undefined, "blah does not exist")
    })

    it("get('blah') and get('whatever') for each subkey should work", function () {
      assert.equal(ts.get("b")[0], item)
      assert.equal(ts.get("bl")[0], item)
      assert.equal(ts.get("bla")[0], item)
      assert.equal(ts.get("blah")[0], item)

      assert.equal(ts.get("w")[0], item)
      assert.equal(ts.get("wh")[0], item)
      assert.equal(ts.get("whatever")[0], item)
    })

    it("get('whatever') for each subkey should work", function () {
      assert.equal(ts.get("w")[0], item)
      assert.equal(ts.get("what")[0], item)
      assert.equal(ts.get("whatever")[0], item)
    })

    it("get('yeah') for each subkey should work", function () {
      assert.equal(ts.get("y")[0], item)
      assert.equal(ts.get("ye")[0], item)
      assert.equal(ts.get("yea")[0], item)
      assert.equal(ts.get("yeah")[0], item)
    })

    it("get('man') for each subkey should work", function () {
      assert.equal(ts.get("m")[0], item)
      assert.equal(ts.get("ma")[0], item)
      assert.equal(ts.get("man")[0], item)
    })
  })

  describe("TrieSearch::add(...) and TrieSearch::get(...) should work for multiple items", function () {
    var ts = new TrieSearch("key"),
      item1 = { key: "I am item1!" },
      item2 = { key: "I am item2!" }

    ts.add(item1)
    ts.add(item2)

    it("add(item1) and add(item2) should build map of nodes", function () {
      assert(ts.root["i"] !== undefined, "I does not exist")
      // @ts-ignore
      assert(ts.root["a"]["m"] !== undefined, "am does not exist")
      assert(
        // @ts-ignore
        ts.root["i"]["t"]["e"]["m"]["1"] !== undefined,
        "item1 does not exist"
      )
      assert(
        // @ts-ignore
        ts.root["i"]["t"]["e"]["m"]["2"] !== undefined,
        "item2 does not exist"
      )
    })

    it("get('i') should return 2 items", function () {
      assert(ts.get("i").length == 2, "did not return 2 items!")
      assert(ts.get("item").length == 2, "did not return 2 items!")
    })

    it("get('item1') and get('item2') should return 1 item", function () {
      assert(ts.get("item1").length == 1, "did not return 1 item!")
      assert(ts.get("item2").length == 1, "did not return 1 item!")
    })
  })

  describe("TrieSearch::add(...) and TrieSearch::get(...) should work with options.min", function () {
    var ts = new TrieSearch("key", { min: 2 }),
      item1 = { key: "I am item1!" },
      item2 = { key: "I am item2!" }

    ts.add(item1)
    ts.add(item2)

    it("add(item1) and add(item2) should build map of nodes", function () {
      assert(ts.root["i"] === undefined, "I should not exist!")
      assert(ts.root["am"] !== undefined, "am does not exist")
      assert(
        // @ts-ignore
        ts.root["it"]["e"]["m"]["1"] !== undefined,
        "item1 does not exist"
      )
      assert(
        // @ts-ignore
        ts.root["it"]["e"]["m"]["2"] !== undefined,
        "item2 does not exist"
      )
    })

    it("get('i') should return 0 items", function () {
      assert(ts.get("i").length == 0, "did not return 0 items!")
      assert(ts.get("item").length == 2, "did not return 2 items!")
    })

    it("get('item') should return 2 items", function () {
      assert(ts.get("item").length == 2, "did not return 2 items!")
    })

    it("get('item1') and get('item2') should return 1 item", function () {
      assert(ts.get("item1").length == 1, "did not return 1 item!")
      assert(ts.get("item2").length == 1, "did not return 1 item!")
    })
  })

  describe("TrieSearch::add(...) and TrieSearch::get(...) should work with customKeys", function () {
    var ts = new TrieSearch("key", { min: 2 }),
      item1 = { customKey1: "I am item1!", customKey2: "123" },
      item2 = { customKey1: "I am item2!", customKey2: "456" }

    ts.add(item1, ["customKey1"])
    ts.add(item2, ["customKey1", "customKey2"])

    it("add(item1) and add(item2) should build map of nodes", function () {
      assert(ts.root["i"] === undefined, "I should not exist!")
      assert(ts.root["am"] !== undefined, "am does not exist")
      assert(
        // @ts-ignore
        ts.root["it"]["e"]["m"]["1"] !== undefined,
        "item1 does not exist"
      )
      assert(
        // @ts-ignore
        ts.root["it"]["e"]["m"]["2"] !== undefined,
        "item2 does not exist"
      )
      assert(
        ts.root["12"] === undefined,
        "item1 should not exist on search for 123"
      )
      assert(
        // @ts-ignore
        ts.root["45"]["6"] !== undefined,
        "item2 does not exist on search for 456"
      )
    })

    it("get('i') should return 0 items", function () {
      assert(ts.get("i").length == 0, "did not return 0 items!")
      assert(ts.get("item").length == 2, "did not return 2 items!")
    })

    it("get('123') should return 0 items", function () {
      assert(ts.get("123").length == 0, "did not return 0 items!")
    })

    it("get('45') should return 1 items", function () {
      assert(ts.get("456").length == 1, "did not return 0 items!")
    })
  })

  describe("TrieSearch::get(...) should work with a custom reducer and accumulator", function () {
    var ts = new TrieSearch("key", {
        min: 2,
        idFieldOrFunction: "key",
      }),
      item1 = { key: "I am red robin!" },
      item2 = { key: "I am red cockatiel!" },
      item3 = { key: "I am green cardinal!" },
      item4 = { key: "I am green owl!" },
      item5 = { key: "robin cockatiel cardinal owl!" }

    ts.add(item1)
    ts.add(item2)
    ts.add(item3)
    ts.add(item4)
    ts.add(item5)

    it("get('robin', [reducer])", function () {
      let result = ts.get(
        "robin",
        function (_accumulator, phrase, phraseMatches, trie) {
          assert(
            _accumulator === undefined,
            "accumulator should be undefined on first pass for first phrase"
          )
          assert(phrase === "robin", "phrase was incorrect")
          assert(
            phraseMatches.length === 2,
            "phraseMatches length is incorrect"
          )
          assert(phraseMatches[0] === item5, "wrong phraseMatches returned")
          assert(phraseMatches[1] === item1, "wrong phraseMatches returned")
          assert(trie === ts, "Trie did not equal ts")

          _accumulator = _accumulator || []
          _accumulator.push(phraseMatches[1])
          _accumulator.push(phraseMatches[0])

          return _accumulator
        }
      )

      assert(
        result.length === 2,
        "result has incorrect length: " + result.length
      )
      assert(result[0] === item1, "result has incorrect items")
      assert(result[1] === item5, "result has incorrect items")
    })

    it("get(['red', 'robin'], TrieSearch.UNION_REDUCER)", function () {
      let result = ts.get(["red", "robin"], TrieSearch.UNION_REDUCER)

      assert(result.length, "result has incorrect length: " + result.length)
      assert(result[0] === item1, "result has incorrect items")
    })

    it("get(['green'], TrieSearch.UNION_REDUCER)", function () {
      let result = ts.get(["green"], TrieSearch.UNION_REDUCER)

      assert(result.length === 2, "result has incorrect length")
      assert(result[0] === item3, "result has incorrect items")
      assert(result[1] === item4, "result has incorrect items")
    })

    it("get('green', TrieSearch.UNION_REDUCER)", function () {
      let result = ts.get("green", TrieSearch.UNION_REDUCER)

      assert(result.length === 2, "result has incorrect length")
      assert(result[0] === item3, "result has incorrect items")
      assert(result[1] === item4, "result has incorrect items")
    })

    it("get('blue', TrieSearch.UNION_REDUCER)", function () {
      let result = ts.get("blue", TrieSearch.UNION_REDUCER)

      assert(result.length === 0, "result has incorrect length")
    })

    it("get('am', TrieSearch.UNION_REDUCER)", function () {
      let result = ts.get("am", TrieSearch.UNION_REDUCER)

      assert(result.length === 4, "result has incorrect length")
    })

    it("get(['owl', 'card', 'cock', 'rob'], TrieSearch.UNION_REDUCER)", function () {
      let result = ts.get(
        ["owl", "card", "cock", "rob"],
        TrieSearch.UNION_REDUCER
      )

      assert(
        result.length === 1,
        "result has incorrect length: " + result.length
      )
    })

    it("get(['owl', 'card', 'cock', 'rob', 'fubar'], TrieSearch.UNION_REDUCER)", function () {
      let result = ts.get(
        ["owl", "card", "cock", "rob", "fubar"],
        TrieSearch.UNION_REDUCER
      )

      assert(
        result.length === 0,
        "result has incorrect length: " + result.length
      )
    })
  })

  describe("TrieSearch::get(...) with internationalization turned on (default) should work", function () {
    let as = "åäàáâã".split("")
    let es = "èéêë".split("")
    let is = "ìíîï".split("")
    let os = "òóôõö".split("")
    let us = "ùúûü".split("")
    let aes = "æ".split("")

    let ts = new TrieSearch<{ key: string; arr: string[] }>("key"),
      As_items = as.map((letter) => ({ key: letter, arr: as })),
      Es_items = es.map((letter) => ({ key: letter, arr: es })),
      Is_items = is.map((letter) => ({ key: letter, arr: is })),
      Os_items = os.map((letter) => ({ key: letter, arr: os })),
      Us_items = us.map((letter) => ({ key: letter, arr: us })),
      AEs_items = aes.map((letter) => ({ key: letter, arr: aes }))

    ts.addAll(As_items)
    ts.addAll(Es_items)
    ts.addAll(Is_items)
    ts.addAll(Os_items)
    ts.addAll(Us_items)
    ts.addAll(AEs_items)

    it(`Should return international items for "a" -> any of "${as}"`, function () {
      let items = ts.get("a")

      // Note this will include overlap with the ae!
      expect(items.length).toBe(7)

      items.forEach((i) => {
        assert(i.arr === as || i.arr === aes)
      })
    })

    it(`Should return international items for "e" -> any of "${es}"`, function () {
      let items = ts.get("e")
      expect(items.length).toBe(4)

      items.forEach((i) => {
        assert(i.arr === es)
      })
    })

    it(`Should return international items for "i" -> any of "${is}"`, function () {
      let items = ts.get("i")
      expect(items.length).toBe(4)

      items.forEach((i) => {
        assert(i.arr === is)
      })
    })

    it(`Should return international items for "o" -> any of "${os}"`, function () {
      let items = ts.get("o")
      expect(items.length).toBe(5)

      items.forEach((i) => {
        assert(i.arr === os)
      })
    })

    it(`Should return international items for "u" -> any of "${us}"`, function () {
      let items = ts.get("u")
      expect(items.length).toBe(4)

      items.forEach((i) => {
        assert(i.arr === us)
      })
    })

    it(`Should return international items for Swedish as an example with ''godis på sötdag är bra''`, function () {
      let swedishSentence = { key: "godis på sötdag är bra" }

      const tss = new TrieSearch<{ key: string }>("key")
      tss.add(swedishSentence)

      expect(tss.get("pa").length).toBe(1)
      expect(tss.get("sot").length).toBe(1)
      expect(tss.get("ar").length).toBe(1)
    })
  })

  describe("TrieSearch::map(...) works with RegEx with positive lookahead (e.g. split on capital letters)", function () {
    it("should not error", function () {
      try {
        var ts = new TrieSearch("key", {
            splitOnRegEx: /([.\-\s']|(?=[A-Z]))/,
            splitOnGetRegEx: false,
          }),
          item = { someValue: 12345 }

        ts.map("This IsSome.Phrase-Whatever", item)
      } catch (error) {
        assert(false, error ? error.toString() : "")
      }
    })

    it("should match capital letter breaks", function () {
      var ts = new TrieSearch("key", {
          splitOnRegEx: /([.\-\s'_]|(?=[A-Z]))/,
          splitOnGetRegEx: false,
          insertFullUnsplitKey: true,
        }),
        item = { someValue: 12345 },
        item2 = { someValue: 67890 }

      ts.map("It'sOnlyA_Flesh Wound", item)
      ts.map("WhatIsYourFavoriteColor", item2)

      assert(ts.get("It")[0] === item, "Did not properly match It")
      assert(ts.get("s")[0] === item, "Did not properly match s")
      assert(ts.get("Only")[0] === item, "Did not properly match Only")
      assert(ts.get("A")[0] === item, "Did not properly match A")
      assert(ts.get("Flesh")[0] === item, "Did not properly match Flesh")
      assert(ts.get("Wound")[0] === item, "Did not properly match Wound")
      assert(
        ts.get("It'sOnlyA_Flesh Wound")[0] === item,
        "Did not properly match full phrase It'sOnlyAFlesh Wound"
      )

      assert(ts.get("What")[0] === item2, "Did not properly match What")
      assert(ts.get("Is")[0] === item2, "Did not properly match Is")
      assert(ts.get("Your")[0] === item2, "Did not properly match Your")
      assert(ts.get("Fav")[0] === item2, "Did not properly match Fav")
      assert(ts.get("Favorite")[0] === item2, "Did not properly match Favorite")
      assert(ts.get("Color")[0] === item2, "Did not properly match Color")
      assert(
        ts.get("WhatIsYourFavoriteColor")[0] === item2,
        "Did not properly match full phrase WhatIsYourFavoriteColor"
      )
    })

    it("should match capital letter breaks", function () {
      var ts = new TrieSearch("someValue", {
          splitOnRegEx: /([.\-\s'_]|(?=[A-Z]))/,
          splitOnGetRegEx: /[\s]/,
        }),
        item2 = { someValue: 67890 }

      ts.map("WhatIsYourFavoriteColor", item2)

      assert(ts.get("What Is")[0] === item2, "Did not properly match What")
      assert(ts.get("Color Favorite")[0] === item2, "Did not properly match Is")
    })
  })

  describe("TrieSearch:TrieSearch::get(...) should work with limits", function () {
    // NOTE: Cache is set to true since caching also needs to be tested
    var ts = new TrieSearch<{ _key_: string; value: string }>(["_key_"], {
      cache: true,
    })
    ts.addAll([
      {
        _key_: "a",
        value: "data",
      },
      {
        _key_: "ab",
        value: "data",
      },
      {
        _key_: "abc",
        value: "data",
      },
      {
        _key_: "abcd",
        value: "data",
      },
      {
        _key_: "abcde",
        value: "data",
      },
      {
        _key_: "abcdef",
        value: "data",
      },
    ])

    it("Get with limits and get without limits should work properly", function () {
      var getWithoutLimit = ts.get("a")
      assert(getWithoutLimit.length === 6, "Expected 6 in without-limit get")

      var getWithLimitResp = ts.get("a", undefined, 4)
      assert(getWithLimitResp.length === 4, "Expected 4 in with-limit get")
    })

    it("Failure case with limits should work properly", function () {
      var getWithLimit = ts.get("b", undefined, 4)
      assert(getWithLimit.length === 0, "Expected 0 in with-limit get")
    })

    it("A bigger limit value than the actual amount of data must work properly", function () {
      var getWithLimit = ts.get("a", undefined, 100)
      assert(getWithLimit.length === 6, "Expected 6 in with-limit get")
    })
  })

  describe("usage test", () => {
    const ts = new TrieSearch<{ class: string; label: string }>("label", {
      min: 2,
    })

    ts.add({
      class: "http://dbpedia.org/ontology/AcademicConference",
      label: "Academic Conference",
    })
    ts.add({
      class: "http://dbpedia.org/ontology/AcademicJournal",
      label: "Academic Journal",
    })
    ts.add({
      class: "http://dbpedia.org/ontology/AcademicSubject",
      label: "Academic Subject",
    })
    ts.add({
      class: "http://dbpedia.org/ontology/Activity",
      label: "Activity",
    })
    ts.add({ class: "http://dbpedia.org/ontology/Actor", label: "Actor" })
    ts.add({
      class: "http://dbpedia.org/ontology/AdministrativeRegion",
      label: "Administrative Region",
    })
    ts.add({
      class: "http://dbpedia.org/ontology/AdultActor",
      label: "Adult Actor",
    })
    ts.add({ class: "http://dbpedia.org/ontology/Agent", label: "Agent" })
    ts.add({
      class: "http://dbpedia.org/ontology/Agglomeration",
      label: "Agglomeration",
    })
    ts.add({
      class: "http://dbpedia.org/ontology/Aircraft",
      label: "Aircraft",
    })
    ts.add({
      class: "http://dbpedia.org/ontology/Airline",
      label: "Airline",
    })
    ts.add({
      class: "http://dbpedia.org/ontology/Airport",
      label: "Airport",
    })
    ts.add({ class: "http://dbpedia.org/ontology/Album", label: "Album" })
    ts.add({
      class: "http://dbpedia.org/ontology/Algorithm",
      label: "Algorithm",
    })
    ts.add({
      class: "http://dbpedia.org/ontology/Altitude",
      label: "Altitude",
    })
    ts.add({
      class: "http://dbpedia.org/ontology/AmateurBoxer",
      label: "Amateur Boxer",
    })
    ts.add({
      class: "http://dbpedia.org/ontology/Ambassador",
      label: "Ambassador",
    })
    ts.add({
      class: "http://dbpedia.org/ontology/AmericanFootballCoach",
      label: "American Football Coach",
    })
    ts.add({
      class: "http://dbpedia.org/ontology/AmericanFootballLeague",
      label: "American Football League",
    })
    ts.add({
      class: "http://dbpedia.org/ontology/AmericanFootballPlayer",
      label: "American Football Player",
    })

    it("does not match below min char length", () => {
      expect(ts.get("a").length).toBe(0)
    })

    it("prefix matches", () => {
      expect(ts.get("aca").length).toBe(3)
      expect(ts.get("amer").length).toBe(3)
    })
    // matches on word
    it("matches on word", () => {
      expect(ts.get("football").length).toBe(3)
    })
  })
  const randomChar = () => Math.floor(Math.random() * 26) + 97
  const randomWord = (length: number) => {
    const c: number[] = []
    for (let i = 0; i < length; i++) c.push(randomChar())
    return String.fromCharCode(...c)
  }
  describe("stress test", () => {
    const ts = new TrieSearch<{ key: string }>("key")
    const arr = new Array(1000000)
      .fill(null)
      .map((_) => ({ key: randomWord(3) }))

    ts.addAll(arr)
    it("adds 1 million entries", () => {
      const tss = new TrieSearch<{ key: string }>("key", { min: 3 })
      const arr = new Array(10000)
        .fill(null)
        .map((_) => ({ key: randomWord(10) }))
      tss.addAll(arr)
      console.log(
        `${
          (JSON.stringify(ts.root).length * 2) / 1024 / 1024
        } MB (assuming no unicode escapes)`
      )
      expect(Object.keys(ts.root).length > 0).toBeTruthy()
    })
    it("gets matches on 1 char with 1 million entries", () => {
      expect(ts.get("a").length > 0).toBeTruthy()
    })
    it("gets matches on 2 chars with 1 million entries", () => {
      expect(ts.get("aa").length > 0).toBeTruthy()
    })
  })
  describe("serialise json", () => {
    var tsOriginal = new TrieSearch(["key", "key2"], {
        min: 2,
        indexField: "ix",
      }),
      item1 = { key: "the quick brown fox", key2: "jumped", ix: 1 },
      item2 = { key: "the quick brown", key2: "jumped", ix: 2 },
      item3 = { key: "the quick fox", key2: "brown", ix: 3 },
      item4 = { key: "the fox", key2: "quick brown", ix: 4 }

    tsOriginal.add(item1)
    tsOriginal.add(item2)
    tsOriginal.add(item3)
    tsOriginal.add(item4)

    const ts = TrieSearch.fromJson(tsOriginal.toJson())

    it("get('the quick') should return all 4 entries", function () {
      expect(ts.get("the quick").length).toBe(4)
    })

    it("get('the brown') should return all 4 entries", function () {
      expect(ts.get("the brown").length).toBe(4)
    })

    it("get('the fox') should return 3 entries", function () {
      expect(ts.get("the fox").length).toBe(3)
    })

    it("get('fox brown') should return 3 entries", function () {
      expect(ts.get("fox brown").length).toBe(3)
    })

    it("get('brown fox') should return 3 entries", function () {
      expect(ts.get("brown fox").length).toBe(3)
    })

    it("get('brown z') should return 4 entries", function () {
      expect(ts.get("brown z").length).toBe(4)
    })

    it("get('br f') should return all entries", function () {
      expect(ts.get("br f").length).toBe(4)
    })

    it("get('jum b c d e f g h') should return 2 entries, ignoring the shortness of all subsequent words", function () {
      expect(ts.get("jum b c d e f g h").length).toBe(2)
    })
  })
})
