const {MissingResult, ChildrenResult} = require('./result')

// Private: Non-leaf node in a {Tree} used by the {NativeWatcherRegistry} to cover the allocated {PathWatcher}
// instances with the most efficient set of {NativeWatcher} instances possible. Each {DirectoryNode} maps to a directory
// in the filesystem tree.
class DirectoryNode {
  // Private: Construct a new, empty node representing a node with no watchers.
  constructor (children) {
    this.children = children || {}
  }

  // Private: Recursively discover any existing watchers corresponding to a path.
  //
  // * `pathSegments` filesystem path of a new {PathWatcher} already split into an Array of directory names.
  //
  // Returns: A {ParentResult} if the exact requested directory or a parent directory is being watched, a
  //   {ChildrenResult} if one or more child paths are being watched, or a {MissingResult} if no relevant watchers
  //   exist.
  lookup (pathSegments) {
    if (pathSegments.length === 0) {
      return new ChildrenResult(this.childWatchers([]), this.children)
    }

    const child = this.children[pathSegments[0]]
    if (child === undefined) {
      return new MissingResult(this)
    }

    return child.lookup(pathSegments.slice(1))
  }

  // Private: Insert a new {RecursiveWatcherNode} into the tree, creating new intermediate {DirectoryNode} instances as
  // needed. Any existing children of the watched directory are removed.
  //
  // * `pathSegments` filesystem path of the new {PathWatcher}, already split into an Array of directory names.
  // * `leaf` initialized {RecursiveWatcherNode} to insert
  //
  // Returns: The root of a new tree with the {RecursiveWatcherNode} inserted at the correct location. Callers should
  // replace their node references with the returned value.
  insert (pathSegments, leaf) {
    if (pathSegments.length === 0) {
      return leaf
    }

    const pathKey = pathSegments[0]
    let child = this.children[pathKey]
    if (child === undefined) {
      child = new DirectoryNode()
    }
    this.children[pathKey] = child.insert(pathSegments.slice(1), leaf)
    return this
  }

  // Private: Remove a {RecursiveWatcherNode} by its exact watched directory.
  //
  // * `pathSegments` absolute pre-split filesystem path of the node to remove.
  // * `createSplitNative` callback to be invoked with each child path segment {Array} if the {RecursiveWatcherNode}
  //   is split into child watchers rather than removed outright. See {RecursiveWatcherNode.remove}. If `null`,
  //   no child node splitting will occur.
  //
  // Returns: The root of a new tree with the {RecursiveWatcherNode} removed. Callers should replace their node
  // references with the returned value.
  remove (pathSegments, createSplitNative) {
    if (pathSegments.length === 0) {
      // Attempt to remove a path with child watchers. Do nothing.
      return this
    }

    const pathKey = pathSegments[0]
    const child = this.children[pathKey]
    if (child === undefined) {
      // Attempt to remove a path that isn't watched. Do nothing.
      return this
    }

    // Recurse
    const newChild = child.remove(pathSegments.slice(1), createSplitNative)
    if (newChild === null) {
      delete this.children[pathKey]
    } else {
      this.children[pathKey] = newChild
    }

    // Remove this node if all of its children have been removed
    return Object.keys(this.children).length === 0 ? null : this
  }

  // Private: Discover all {RecursiveWatcherNode} instances beneath this tree node and the child paths
  //  that they are watching.
  //
  // * `prefix` {Array} of intermediate path segments to prepend to the resulting child paths.
  //
  // Returns: A possibly empty {Array} of `{node, path}` objects describing {RecursiveWatcherNode}
  //  instances beneath this node.
  leaves (prefix) {
    const results = []
    for (const p of Object.keys(this.children)) {
      results.push(...this.children[p].leaves(prefix.concat([p])))
    }
    return results
  }

  // Private: Return a {String} representation of this subtree for diagnostics and testing.
  print (indent = 0) {
    let spaces = ''
    for (let i = 0; i < indent; i++) {
      spaces += ' '
    }

    let result = ''
    for (const p of Object.keys(this.children)) {
      result += `${spaces}${p}\n${this.children[p].print(indent + 2)}`
    }
    return result
  }
}

module.exports = {DirectoryNode}

@@ -1262,38 +1262,28 @@
 t a new 
-%7BRecursiveW
+w
 atcher
-N
+ n
 ode
-%7D
  into th
@@ -1362,13 +1362,9 @@
 ed. 
-Any e
+E
 xist
@@ -1405,23 +1405,118 @@
 ory are 
-removed
+adopted as children by a new %7BNonrecursiveWatcherNode%7D%0A  // or removed by a new %7BRecursiveWatcherNode%7D
 .%0A  //%0A 
@@ -1634,20 +1634,20 @@
   // * %60
-leaf
+node
 %60 initia
@@ -1674,16 +1674,45 @@
 herNode%7D
+ or %7BNonrecursiveWatcherNode%7D
  to inse
@@ -1713,16 +1713,17 @@
 o insert
+.
 %0A  //%0A  
@@ -1766,38 +1766,24 @@
 ith the 
-%7BRecursiveWatcherN
+new n
 ode
-%7D
  inserte
@@ -1823,21 +1823,16 @@
 s should
-%0A  //
  replace
@@ -1833,24 +1833,29 @@
 eplace their
+%0A  //
  node refere
@@ -1908,20 +1908,20 @@
 gments, 
-leaf
+node
 ) %7B%0A    
@@ -1966,20 +1966,20 @@
  return 
-leaf
+node
 %0A    %7D%0A%0A
@@ -2189,20 +2189,20 @@
 ice(1), 
-leaf
+node
 )%0A    re

Insert recursive or nonrecursive nodes