import { prepareRenderComponent } from "./prepareRenderComponent"
import { isScreenSlot } from "./builtinComponents"
import deepEqual from "deep-equal"
import appStore from "../state/store"

export const attachChildren = initialiseOpts => (htmlElement, options) => {
  const {
    componentLibraries,
    treeNode,
    onScreenSlotRendered,
    setupState,
  } = initialiseOpts

  const anchor = options && options.anchor ? options.anchor : null
  const force = options ? options.force : false
  const hydrate = options ? options.hydrate : true
  const context = options && options.context

  if (!force && treeNode.children.length > 0) return treeNode.children

  for (let childNode of treeNode.children) {
    childNode.destroy()
  }

  if (!htmlElement) return

  if (hydrate) {
    while (htmlElement.firstChild) {
      htmlElement.removeChild(htmlElement.firstChild)
    }
  }

  const contextStoreKeys = []

  // create new context if supplied
  if (context) {
    let childIndex = 0
    // if context is an array,  map to new structure
    const contextArray = Array.isArray(context) ? context : [context]
    for (let ctx of contextArray) {
      const key = appStore.create(
        ctx,
        treeNode.props._id,
        childIndex,
        treeNode.contextStoreKey
      )
      contextStoreKeys.push(key)
      childIndex++
    }
  }

  const childNodes = []

  const createChildNodes = contextStoreKey => {
    for (let childProps of treeNode.props._children) {
      const { componentName, libName } = splitName(childProps._component)

      if (!componentName || !libName) return

      const ComponentConstructor = componentLibraries[libName][componentName]

      const childNode = prepareRenderComponent({
        props: childProps,
        parentNode: treeNode,
        ComponentConstructor,
        htmlElement,
        anchor,
        // in same context as parent, unless a new one was supplied
        contextStoreKey,
      })

      childNodes.push(childNode)
    }
  }

  if (context) {
    // if new context(s) is supplied, then create nodes
    // with keys to new context stores
    for (let contextStoreKey of contextStoreKeys) {
      createChildNodes(contextStoreKey)
    }
  } else {
    // otherwise, use same context store as parent
    // which maybe undefined (therfor using the root state)
    createChildNodes(treeNode.contextStoreKey)
  }

  // if everything is equal, then don't re-render
  if (areTreeNodesEqual(treeNode.children, childNodes)) return treeNode.children

  for (let node of childNodes) {
    const initialProps = setupState(node)
    node.render(initialProps)
  }

  const screenSlot = childNodes.find(n => isScreenSlot(n.props._component))

  if (onScreenSlotRendered && screenSlot) {
    // assuming there is only ever one screen slot
    onScreenSlotRendered(screenSlot)
  }

  treeNode.children = childNodes

  return childNodes
}

const splitName = fullname => {
  const nameParts = fullname.split("/")

  const componentName = nameParts[nameParts.length - 1]

  const libName = fullname.substring(
    0,
    fullname.length - componentName.length - 1
  )

  return { libName, componentName }
}

const areTreeNodesEqual = (children1, children2) => {
  if (children1.length !== children2.length) return false
  if (children1 === children2) return true

  let isEqual = false
  for (let i = 0; i < children1.length; i++) {
    // same context and same children, then nothing has changed
    isEqual =
      deepEqual(children1[i].context, children2[i].context) &&
      areTreeNodesEqual(children1[i].children, children2[i].children)
    if (!isEqual) return false
    if (isScreenSlot(children1[i].parentNode.props._component)) {
      isEqual = deepEqual(children1[i].props, children2[i].props)
    }
    if (!isEqual) return false
  }
  return true
}
