import * as url from 'url'

import generate from '@babel/generator'
import t from '@babel/types'
import visit from 'unist-util-visit'
import remove from 'unist-util-remove'

// TODO: create types

// match component name by regexp
const componentName = (value: any) => {
  const match = value.match(/^\<\\?(\w+)/)
  return match && match[1]
}

// iterate in a reverse way to merge values then delete the unused node
const valuesFromNodes = (tree: any) => (first: number, last: number) => {
  const values = []

  if (first !== last) {
    for (let i = last; i >= first; i--) {
      const found = tree.children[i]

      if (found.children && found.children.length > 0) {
        values.push(...found.children.map((child: any) => child.value))
      }

      if (found.value && found.value.length > 0) {
        values.push(found.value)
      }

      if (i !== first) remove(tree, found)
    }
  }

  return values
}

const mergeNodeWithoutCloseTag = (tree: any, node: any, idx: any) => {
  if (!node.value || typeof node.value !== 'string') return

  // parse component name and create two regexp to check open and close tag
  const component = componentName(node.value)
  const tagOpen = new RegExp(`^\\<${component}`)
  const tagClose = new RegExp(`\\<\\/${component}\\>$`)

  const hasOpenTag = (val: any) => tagOpen.test(val)
  const hasCloseTag = (val: any) => tagClose.test(val)
  const hasJustCloseTag = (val: any) =>
    val && !hasOpenTag(val) && hasCloseTag(val)

  // return default value is has open and close tag
  if (!component || (hasOpenTag(node.value) && hasCloseTag(node.value))) {
    return
  }

  // when some node has just the open tag
  // find node index with equivalent close tag
  const tagCloseIdx = tree.children.findIndex(({ value, children }: any) => {
    if (children) return children.some((c: any) => hasJustCloseTag(c.value))
    return hasJustCloseTag(value)
  })

  if (tagCloseIdx > -1 && tagCloseIdx !== idx) {
    // merge all values from node open tag until node with the close tag
    const mergeUntilCloseTag = valuesFromNodes(tree)
    const values = mergeUntilCloseTag(idx, tagCloseIdx)
    node.value = values.reverse().join('\n')
  }
}

const createImgSrc = (src: string) => {
  const parsed = url.parse(src)

  if (parsed.protocol) {
    return t.stringLiteral(src)
  }

  let { pathname } = parsed as { pathname: string }
  if (!/^(?:\.[./]+|@)/.test(pathname)) {
    pathname = `./${pathname}`
  }
  return t.jsxExpressionContainer(
    t.callExpression(t.identifier('require'), [t.stringLiteral(pathname)])
  )
}

const imageToJsx = (node: any): string =>
  generate(
    t.jsxOpeningElement(
      t.jsxIdentifier('img'),
      [
        t.jsxAttribute(t.jsxIdentifier('alt'), t.stringLiteral(node.alt)),
        t.jsxAttribute(t.jsxIdentifier('src'), createImgSrc(node.url)),
      ],
      true
    )
  ).code

// turns `html` nodes into `jsx` nodes
export default () => (tree: any) => {
  visit(tree, 'image', (node: any): void => {
    // check if a node has just open tag
    node.type = 'jsx'
    node.value = imageToJsx(node)
  })
  visit(tree, 'jsx', (node: any, idx: any): void => {
    // check if a node has just open tag
    mergeNodeWithoutCloseTag(tree, node, idx)
  })
}
