interface SerializedTrie<T> {
  // terminal node?
  t: 1 | 0;
  // value of the node
  v: T;
  // optional children
  c?: { [s: string]: SerializedTrie<T> };
}

export class TrieNode<T> {
  public terminal: boolean;
  public value: T;
  public children: Map<string, TrieNode<T>>;
  public serialized: SerializedTrie<T>;

  constructor(ser: SerializedTrie<T>) {
    this.terminal = ser.t == 1;
    this.value = ser.v;
    this.children = new Map();
    this.serialized = ser;

    if (ser.c != null) {
      for (const [key, value] of Object.entries(ser.c)) {
        this.children.set(key, new TrieNode(value));
      }
    }
  }
}

export default class Trie<T> {
  public root: TrieNode<T>;
  constructor(ser: SerializedTrie<T>) {
    this.root = new TrieNode(ser);
  }

  public nodeForPrefix(key: string): {
    chain: string[];
    node: TrieNode<T> | null;
  } {
    let chain = [];
    let node = this.root;
    let remaining = key;
    while (node && remaining.length > 0) {
      let exactChild = null;
      console.log('remaining: ', remaining);

      for (const [childKey, child] of node.children.entries()) {
        if (remaining.startsWith(childKey)) {
          console.log('exact match for: ', childKey);
          exactChild = child;
          chain.push(childKey);
          remaining = remaining.slice(childKey.length);
          break;
        }
      }

      // if an exact match was found, continue iterating
      if (exactChild) {
        node = exactChild;
        continue;
      }

      console.log('looking for partial match for ', remaining);
      for (const [childKey, child] of node.children.entries()) {
        const startsWith = childKey.startsWith(remaining);
        console.log(
          'test ',
          childKey,
          ' against ',
          remaining,
          ': ',
          startsWith,
          ' ',
          child.serialized,
        );
        if (startsWith) {
          console.log('partial match for: ', remaining, ': ', child.serialized);
          chain.push(childKey);
          return { chain, node: child };
        }
      }

      console.log('did not find partial, bailing!');
      return { chain, node: null };
    }

    // // return remaining.length === 0 && node && node.terminal ? node : null;
    console.log('returning child ', node, ' for remaining ', remaining);
    return { chain, node };
  }
}
