/*******************************************************************************
 * @copyright 2021 IDEALIGN Stanisław Gregor
 ******************************************************************************/

import Vue, { VNode } from 'vue'

/**
 * Extracts the contents from the passed-in `<slot>`.
 *
 * @param slot - The `<slot>` which contents we wont to resolve.
 *
 * @author Stanisław Gregor, Vitim.us
 *
 * @see https://stackoverflow.com/a/52749314/11869579
 */
const getSlotContents = (slot: VNode[]): string => {
  if (typeof slot === 'undefined' || !Array.isArray(slot) || slot.length === 0) {
    return ''
  }

  /**
   * The contents from all the VNodes in the `<slot>`.
   */
  const textContents: string[] = slot.map(vnode => {
    if (typeof vnode.text === 'string') {
      return vnode.text
    }

    if (typeof vnode.elm !== 'undefined' && typeof vnode.elm.textContent === 'string') {
      return vnode.elm.textContent
    }

    return ''
  })

  return textContents.join('')
}

/**
 * Simple functional component that accepts HTML string and renders it as-is.
 * Why? Because `v-html` directive is not always an option 😉
 *
 * @author Stanisław Gregor, Thorsten Lünborg
 *
 * @see https://forum.vuejs.org/t/raw-html-without-a-parent-element-via-v-html/87160
 * @see https://jsfiddle.net/Linusborg/mfqjk5hm/
 */
export const VHTML = Vue.extend({
  functional: true,
  name: 'VHTML',

  props: {
    /**
     * The HTML string to render.
     */
    html: { type: String, required: false }
  },

  render (h, ctx) {
    /**
     * Determines whether the `[html]` prop has been specified.
     */
    const hasHtmlProp: boolean = typeof ctx.props.html === 'string'

    /**
     * Determines whether the default `<slot>` has been populated.
     */
    const hasDefaultSlot: boolean =
      typeof ctx.slots().default !== 'undefined' &&
      Array.isArray(ctx.slots().default) &&
      ctx.slots().default.length > 0

    /**
     * The HTML that is to be rendered.
     */
    let content: string

    if (hasHtmlProp && hasDefaultSlot) {
      console.warn(
        'VHTML: Received both [html] prop AND default <slot> - returning only the HTML from the <slot>.'
      )
      content = ''
    } else if (hasHtmlProp) {
      content = ctx.props.html
    } else if (hasDefaultSlot) {
      content = getSlotContents(ctx.slots().default)
    } else {
      console.warn(
        'VHTML: Expected to find the HTML string in [html] prop OR inside the default <slot>, but none was present!'
      )
      content = ''
    }

    return new Vue({
      template: `<div>${content}</div>`
    }).$mount()._vnode.children as VNode[]
  }
})
