import warning from '../utils/warning'
import { default as defaultSchema } from '../plugins/schema'

/**
 * Refresh a reference to a node that have been modified in a transform.
 * @param  {Transform} transform
 * @param  {Node} node
 * @return {Node} newNode
 */

function _refreshNode(transform, node) {
  const { state } = transform
  const { document } = state

  if (node.kind == 'document') {
    return document
  }

  return document.getDescendant(node.key)
}

/**
 * Normalize all children of a node
 * @param  {Transform} transform
 * @param  {Schema} schema
 * @param  {Node} node
 * @return {Transform} transform
 */

function _normalizeChildrenWith(transform, schema, node) {
  if (!node.nodes) {
    return transform
  }

  return node.nodes.reduce(
    (t, child) => {
      return t.normalizeNodeWith(schema, child)
    },
    transform
  )
}

/**
 * Normalize a node without its children
 * @param  {Transform} transform
 * @param  {Schema} schema
 * @param  {Node} node
 * @return {Transform} transform
 */

function _normalizeNodeWith(transform, schema, node) {
  const failure = schema.__validate(node)

  // Node is valid?
  if (!failure) {
    return transform
  }

  const { value, rule } = failure

  // Normalize and get the new state
  transform = rule.normalize(transform, node, value)

  // Search for the updated node in the new state
  const newNode = _refreshNode(transform, node)

  // Node no longer exist, go back to normalize parents
  if (!newNode) {
    return transform
  }

  return _normalizeNodeWith(transform, schema, newNode)
}

/**
 * Normalize a node (itself and its children) using a schema.
 *
 * @param  {Transform} transform
 * @param  {Schema} schema
 * @param  {Node} node
 * @return {Transform}
 */

export function normalizeNodeWith(transform, schema, node) {
  // console.log(`normalize node key=${node.key}`)
  // Iterate over its children
  transform = _normalizeChildrenWith(transform, schema, node)

  // Refresh the node reference, and normalize it
  node = _refreshNode(transform, node)
  if (node) {
    transform = _normalizeNodeWith(transform, schema, node)
  }

  return transform
}

/**
 * Normalize a node its parents using a schema.
 *
 * @param  {Transform} transform
 * @param  {Schema} schema
 * @param  {Node} node
 * @return {Transform}
 */

export function normalizeParentsWith(transform, schema, node) {
  transform = _normalizeNodeWith(transform, schema, node)

  // Normalize went back up to the document
  if (node.kind == 'document') {
    return transform
  }

  // We search for the new parent
  node = _refreshNode(transform, node)
  if (!node) {
    return transform
  }

  const { state } = transform
  const { document } = state
  const parent = document.getParent(node.key)

  return normalizeParentsWith(transform, schema, parent)
}

/**
 * Normalize state using a schema.
 *
 * @param  {Transform} transform
 * @param  {Schema} schema
 * @return {Transform} transform
 */

export function normalizeWith(transform, schema) {
  const { state } = transform
  const { document } = state

  // Schema was not rule to edit the document
  if (!schema.isNormalization) {
    return transform
  }

  return transform.normalizeNodeWith(schema, document)
}

/**
 * Normalize the state using the core schema.
 *
 * @param  {Transform} transform
 * @return {Transform} transform
 */

export function normalize(transform) {
    return transform
        .normalizeDocument()
        .normalizeSelection()
}

/**
 * Normalize only the document
 *
 * @param  {Transform} transform
 * @return {Transform} transform
 */

export function normalizeDocument(transform) {
  return transform.normalizeWith(defaultSchema)
}

/**
 * Normalize a node and its children using core schema
 *
 * @param  {Transform} transform
 * @param  {Node or String} key
 * @return {Transform} transform
 */

export function normalizeNodeByKey(transform, key) {
  const { state } = transform
  const { document } = state
  const node = document.key == key ? document : document.assertDescendant(key)

  return transform.normalizeNodeWith(defaultSchema, node)
}

/**
 * Normalize a node and its parent using core schema
 *
 * @param  {Transform} transform
 * @param  {Node or String} key
 * @return {Transform} transform
 */

export function normalizeParentsByKey(transform, key) {
  const { state } = transform
  const { document } = state
  const node = document.key == key ? document : document.assertDescendant(key)

  return transform.normalizeParentsWith(defaultSchema, node)
}

/**
 * Normalize only the selection.
 *
 * @param  {Transform} transform
 * @return {Transform} transform
 */

export function normalizeSelection(transform) {
  let { state } = transform
  let { document, selection } = state
  selection = selection.normalize(document)

  // If the selection is nulled (not normal)
  if (
    selection.isUnset ||
    !document.hasDescendant(selection.anchorKey) ||
    !document.hasDescendant(selection.focusKey)
  ) {
      warning('Selection was invalid and reset to start of the document')
      const firstText = document.getTexts().first()
      selection = selection.merge({
        anchorKey: firstText.key,
        anchorOffset: 0,
        focusKey: firstText.key,
        focusOffset: 0,
        isBackward: false
      })
  }

  state = state.merge({ selection })
  transform.state = state
  return transform
}

@@ -94,16 +94,83 @@
 chema'%0A%0A
+// Maximum recursive calls for normalization%0Aconst MAX_CALLS = 50%0A%0A
 /**%0A * R
@@ -1879,57 +1879,297 @@
 %7B%0A  
-// console.log(%60normalize node key=$%7Bnode.key%7D%60)%0A
+let recursiveCount = 0%0A%0A  // Auxiliary function, called recursively, with a maximum calls safety net.%0A  function _recur() %7B%0A    recursiveCount++%0A    if (recursiveCount %3E MAX_CALLS) %7B%0A      warning('Unexpected number of successive normalizations. Aborting.')%0A      return transform%0A    %7D%0A  
   //
@@ -2195,16 +2195,18 @@
 hildren%0A
+  
   transf
@@ -2262,16 +2262,18 @@
 ode)%0A%0A  
+ 
+ 
 // Refre
@@ -2312,16 +2312,18 @@
 lize it%0A
+  
   node =
@@ -2349,24 +2349,26 @@
 form, node)%0A
+  
   if (node) 
@@ -2369,24 +2369,26 @@
 node) %7B%0A    
+  
 transform = 
@@ -2431,21 +2431,25 @@
 , node)%0A
+  
   %7D%0A%0A
+  
   return
@@ -2459,16 +2459,62 @@
 ansform%0A
+  %7D%0A%0A  return _recur(transform, schema, node)%0A
 %7D%0A%0A/**%0A 

Add maximum number of recursivity for normalization