import _ from 'lodash'
import Vue from 'vue'

export function getTags (vnode, key = '') {
  const children = vnode?.children
    || vnode.componentOptions?.children

  return (children?.map((el, i) => {
    let cKey = `children[${i}]`
    if (vnode.componentOptions?.children) {
      cKey = `componentOptions.children[${i}]`
    }
    const newKey = _.join([key, cKey].filter((str) => str), '.')
    const tags = getTags(el, newKey)
    const tag = el.componentOptions?.tag || el.tag
    const data = {
      tag,
      propsData: el.componentOptions?.propsData,
      children: tags,
      key: newKey
    }
    return data
  }) || [])
  .filter((el) => el.tag !== undefined)
}
export function getTagsDeep (tags) {
  return _.flattenDeep(tags.map((tag) => [tag, getTagsDeep(tag.children)]))
}
export function getComponents (tags, whitelist) {
  return getTagsDeep(tags)
    .filter((el) => whitelist.includes(el.tag))
}
export function getSections (tags, sectionWhitelist, componentWhitelist) {
  let sections = []
  tags.forEach((el) => {
    if (sectionWhitelist.includes(el.tag)) {
      el.children = getComponents(el.children, componentWhitelist)
      sections.push(el)
    } else {
      sections = getSections(el.children, sectionWhitelist, componentWhitelist)
    }
  })
  return sections
}

const sectionWhitelist = ['snt-section']
const componentWhitelist = [
  'snt-text',
  'snt-text-editor',
  'snt-navbar',
  'snt-product',
  'snt-products',
  'snt-all-products',
  'snt-product-detail',
  'snt-video',
  'snt-image',
  'snt-footer',
  'snt-feed',
  'snt-feeds',
  'snt-carousel',
  'snt-link',
  'snt-icon',
  'snt-cart',
  'snt-payment',
  'snt-order',
  'snt-orders',
  'snt-card',
  'snt-dialog'
]

function compile () {
  const xtemplate = this
  const compiledTemplate = Vue.compile(xtemplate.template)
  const vm = new Vue({
    components: xtemplate.components,
    directives: xtemplate.directives,
    data () {
      return xtemplate.data
    },
    render: compiledTemplate.render,
    staticRenderFns: compiledTemplate.staticRenderFns
  })
  const x = Vue.compile(xtemplate.template)
  return x.render.call(vm)
}

function compute () {
  const xtemplate = this
  const compiled = xtemplate.compile()
  const tags = getTags(compiled)
  const sections = getSections(tags, sectionWhitelist, componentWhitelist)
  const keys = []
  const values = []
  sections.forEach((section, i) => {
    keys.push({
      key: section.key,
      name: section.propsData.name,
      values: section.children.map((component) => ({
        key: component.key,
        component: component.tag
      }))
    })
    values.push({
      sequence: i,
      hidden: false,
      values: section.children.map((component) => ({
        component: component.tag,
        props: component.propsData
      }))
    })
  })
  return {
    vnode: compiled,
    keys,
    values
  }
}

function combine (keyValues) {
  const xtemplate = this
  let compiled = xtemplate.compile()
  keyValues.forEach((section, i) => {
    compiled = _.update(
      compiled,
      `${section.key}.data.attrs`,
      (props) => ({
        ...props,
        'data-index': i,
        'data-sequence': section.sequence,
        'hidden': section.hidden
      })
    )
    section.values.forEach((component, j) => {
      compiled = _.update(
        compiled,
        `${component.key}.data.attrs`,
        (props) => ({
          ...props,
          'data-index': j
        })
      )
      compiled = _.update(
        compiled,
        `${component.key}.componentOptions`,
        (props) => _.assignIn(props, {
          propsData: component.props
        })
      )
    })
  })
  return compiled
}

function build (template, values) {
  const wrapper = document.createElement('div')
  wrapper.innerHTML = template
  const sectionWhitelistStr = this.$_.join(sectionWhitelist, ',')
  const sections = wrapper.getElementsByTagName(sectionWhitelistStr)
  const componentWhitelistStr = this.$_.join(componentWhitelist, ',')
  values.forEach((section, i) => {
    const components = sections[i].querySelectorAll(componentWhitelistStr)
    section.values.forEach(({ props }, j) => {
      this.$_.forEach(props, (value, key) => {
        if (this.$_.isObject(value)) {
          components[j].setAttribute(`:${key}`, JSON.stringify(value))
        } else {
          components[j].setAttribute(key, value)
        }
      })
    })
  })
  return wrapper.innerHTML
}

export function VueXTemplate ({ template = '', components = {}, directives = {}, data = {} }) {
  if (!(this instanceof VueXTemplate)) {
    return new VueXTemplate({ template, components, data })
  }
  const xtemplate = this
  xtemplate.template = template
  xtemplate.directives = {
    ...directives
  }
  xtemplate.components = {
    ...components
  }
  xtemplate.data = {
    ...data
  }
}
VueXTemplate.prototype = { compile, compute, combine, build }
