/* 

https://leetcode.com/problems/implement-trie-prefix-tree/solution/
Visualize a trie as a 26 by n matrix with one way relation between the letters in different levels. n is the length of the longest word in the trie.

words — deed, nom, noon

a  b  c  d  e  f  g  h  i  j  k  l  m  n  o ...
           ↘                            ↘ 
a  b  c  d  e  f  g  h  i  j  k  l  m  n  o ...
            ↓                           ↙ ↓
            ↓                         ↙   ↓
a  b  c  d  e  f  g  h  i  j  k  l  m  n  o ...
          ↙                             ↙
a  b  c  d  e  f  g  h  i  j  k  l  m  n  o ...

The root would look like:

const trie =
{
  d: { e: { e: { d: { isWord: true } } } },
  n: { o: { o: { n: { isWord: true } },
       m: { isWord: true } }
     }
}

*/

// We'll assume that the words provided will consist of letters only and are always lowercase

const Trie = class Trie {
	#root
	constructor() {
		this.#root = {}
	}

	get root() {
		return this.#root
	}
	// helper. Change to private when private instance methods are supported
	_traverse(word) {
		let curr = this.#root
		for (const ch of word) {
			if (!curr) return null
			curr = curr[ch]
		}
		return curr
	}

	insert(word) {
		let curr = this.#root
		for (const ch of word) {
			curr[ch] = curr[ch] ? curr[ch] : {}
			curr = curr[ch]
		}

		curr.isWord = true
	}

	search(word) {
		let node = this._traverse(word)
		return node !== null && node.isWord === true
	}

	startsWith(word) {
		return this._traverse(word) !== null
	}
}

module.exports = Trie

Tests (jest):

const { Trie } = require('../src')

test('creates a new instance of Trie', () => {
	const trie = new Trie()
	expect(trie instanceof Trie).toBe(true)
})

test('gets root of trie', () => {
	const trie = new Trie(),
		root = {
			s: {
				o: { m: { e: { t: { h: { i: { n: { g: { isWord: true } } } } } } } },
			},
		}
	trie.insert('something')
	expect(trie.root).toEqual(root)
})

describe('insert', () => {
	test('inserts specified word characters in correct order in trie', () => {
		const trie = new Trie(),
			root = {
				s: {
					o: { m: { e: { t: { h: { i: { n: { g: { isWord: true } } } } } } } },
					p: { y: { isWord: true } },
				},
			}
		trie.insert('something')
		trie.insert('spy')
		expect(trie.root).toEqual(root)
	})
})

describe('search', () => {
	test('searches trie and returns true if the word is found', () => {
		const trie = new Trie(),
			wordToSearch = 'something'
		trie.insert('something')

		expect(trie.search(wordToSearch)).toEqual(true)
	})

	test('searches trie and returns false if the word is not found', () => {
		const trie = new Trie(),
			wordToSearch = 'something'
		trie.insert('some')

		expect(trie.search(wordToSearch)).toEqual(false)
	})
})