import Vue from 'vue'
import _ from 'lodash'
import helpers from '@/helpers'

let processProperties = function(item, properties) {
  if (typeof properties === 'function') {
    properties = properties(item)
  }
  return _.map(_.omit(properties, ['template']), (value, key) => {
    return key + '="' + value + '"'
  }).join(' ')
}

let generalMethods = {
  emitter(method, values) {
    this.$emit(method, values)
  },
}

let popProperty = function(property, key) {
  let value = property[key]
  delete property[key]
  return value
}

let defaultTypes = {
  input: 'b-form-input',
  select: 'b-form-select',
}

let temptableComponent = function(type, data, parts, component = {}, componentProps, properties) {
  let template = ''

  let props = _.defaultsDeep(componentProps, properties, {
    template: {
      default: `<div><slot name="label"></slot><slot></slot></div>`,
    },
  })

  let defaultTemplate = props.template['type'] || props.template['default']

  if (props.hideLabel === true) {
    delete parts['label']
  }

  // process the default attribute for the part
  Object.keys(parts).forEach(function(part) {
    let partAttributes = {}
    let errorElement = ''

    if (part === 'element') {
      partAttributes = {
        ':value': 'currentValue',
        ':readonly': 'computedReadOnly',
        ':is': type + 'Type',
        '@change': 'trigggerChange',
      }

      if (type === 'select') {
        partAttributes[':disabled'] = 'computedReadOnly'
      }

      if (data.options) {
        partAttributes[':options'] = 'currentOptions'
      }

      if (data.required) {
        partAttributes['v-validate'] = "'required'"
        partAttributes['name'] = data.label
        partAttributes[':class'] = "{'border-danger': errors.has('" + data.label + "')}"
        errorElement = `<span v-show="errors.has('${data.label}')" class="help text-danger">{{ errors.first('${data.label}') }}</span>`
      }
    } else if (part === 'label') {
      partAttributes = {}

      if (data.required) {
        partAttributes['class'] = 'required'
      }
    }

    parts[part] =
      parts[part].replace(
        /(<\w+)([\s|>])/,
        '$1 ' + processProperties(data, { ...componentProps, ...properties, ...partAttributes }) + ' $2'
      ) + errorElement
  })

  // Places the parts relative to the template provided. That is eg if the template has slot with name label, the part
  // label will be tagged to use that slot
  Object.keys(parts).forEach(function(part) {
    let myRegexp = /<slot.*?name="(.*?)"/g
    let match = myRegexp.exec(defaultTemplate)

    while (match != null) {
      if (match[1] === part) {
        template += `<template slot="${part}">${parts[part]}</template>`
        return
      }
      match = myRegexp.exec(defaultTemplate)
    }

    template += parts[part]
  })

  // Define the template component of the element ----------------------------------------------------------------------
  let loadElementTemplate = {
    'element-template': Vue.component('element-template', function(resolve, reject) {
      resolve({
        template: defaultTemplate,
      })
    }),
  }

  // wrap the template
  template = `<element-template :class="{'d-none': ${data.hidden === true}}">${template}</element-template>`

  // component.data = function () {
  //   return {
  //     modelValue: ''
  //   }
  // }

  component.components = component.components
    ? { ...component.components, ...loadElementTemplate }
    : loadElementTemplate

  // Define the computed values of the component -----------------------------------------------------------------------
  let computed = {
    currentValue() {
      // current value is the value provided in the props by v-model or autofill value or the value from Api
      return this.value
        ? (typeof this.value === 'boolean' && type === 'radio' && this.value) ||
            (typeof this.value !== 'boolean' && this.value) ||
            ''
        : this.modelValue
        ? this.modelValue
        : helpers.isEmpty(data.value) && !helpers.isEmpty(data.autofill)
        ? data.autofill
        : data.value
    },
    computedReadOnly() {
      return data.readonly === true || this.readonly
    },
  }

  if (data.options) {
    if (typeof data.options === 'function') {
      computed['currentOptions'] = data.options
    } else {
      computed['currentOptions'] = function() {
        return data.options
      }
    }
  }

  component.computed = component.computed ? { ...component.computed, ...computed } : computed

  // Define the props for the component --------------------------------------------------------------------------------
  let defaultProps = {
    value: {
      type: [Number, Boolean, String],
      default: null,
    },
    [type + 'Type']: {
      type: String,
      default: defaultTypes[type],
    },
    readonly: {
      type: Boolean,
      default: false,
    },
  }

  component.props = component.props ? { ...component.props, ...defaultProps } : defaultProps

  // Define the template of the component ------------------------------------------------------------------------------
  component.template = component.template ? component.template + template : template

  // Specify the mounted method of the component -----------------------------------------------------------------------
  if (component.mounted === undefined) {
    component.mounted = function() {
      if (this.value === null && this.currentValue !== this.value && !helpers.isEmpty(this.currentValue)) {
        this.$emit('input', this.currentValue)
      }
    }
  }

  // Define the methods of the component -------------------------------------------------------------------------------
  component.methods = component.methods ? { ...generalMethods, ...component.methods } : generalMethods

  // Define trigger method if the component needs it -------------------------------------------------------------------
  component.methods = {
    ...component.methods,
    trigggerChange: function(changeValue) {
      this.$emit('input', changeValue)
      if (data.triggerIndex) {
        this.$emit('triggerIndex', {
          index: data.triggerIndex,
          value:
            data.options && data.triggerColumn
              ? (value => (value ? value[data.triggerColumn] : changeValue))(
                  _.find(data.options, { value: changeValue })
                )
              : this.currentValue,
        })
      }
    },
  }

  component.inject = ['$validator']

  return component
}

let elementProcessors = {
  link: function(link, properties) {
    return componentProps => {
      return {
        template: `<b-btn
                    ${processProperties(link, { ...componentProps, ...properties })}
                    to="${link.url}"
                    v-on:click="emitter('click', $event)"
                 >
                    {{ $t('${link.text}') }}
                 </b-btn>`,
        methods: {
          ...generalMethods,
        },
      }
    }
  },
  select: function(input, properties) {
    return componentProps => {
      return temptableComponent(
        'select',
        input,
        {
          label: `<label> {{ $t('${input.label}') }} </label>`,
          element: `<component type="text" />`,
        },
        {},
        componentProps,
        properties
      )
    }
  },
  input: function(input, properties) {
    return componentProps => {
      return temptableComponent(
        'input',
        input,
        {
          label: `<label> {{ $t('${input.label}') }} </label>`,
          element: `<component type="text" />`,
        },
        {},
        componentProps,
        properties
      )
    }
  },
  radio: function(input, properties) {
    return componentProps => {
      return {
        props: {
          radioWrap: {
            type: String,
            default: properties.wrap || 'div',
          },
          radioType: {
            type: String,
            default: 'b-radio',
          },
          radioShowLabel: {
            type: Boolean,
            default: true,
          },
          value: {
            type: [Number, Boolean, String],
            default: false,
          },
          readonly: {
            type: Boolean,
            default: false,
          },
        },
        template: `<component :is="radioWrap">
                      <label v-if="radioShowLabel"> {{ $t('${input.title}') }} </label>
                      <component :is="radioType" :value="${input.value}" v-model="value" @change="changed" :checked="${input.selected}" :disabled="readonly">
                        <span :class="{'c-pointer':!readonly}">{{ $t('${input.title}') }}</span>
                      </component>
                   </component>`,
        mounted() {
          if (input.selected === true && this.computedValue === null) {
            this.$emit('input', input.value)
          }
        },
        methods: {
          ...generalMethods,
          changed(val) {
            this.$emit('input', val)
          },
        },
        computed: {
          computedValue() {
            return this.value !== null ? parseInt(this.value) : this.value
          },
        },
      }
    }
  },
}

let normalizeElement = function(element) {
  if (element.type && Object.keys(elementProcessors).includes(element.type)) {
    return {
      [element.type]: element,
    }
  } else {
    return element
  }
}

export default function(object, properties = {}) {
  object = normalizeElement(object)
  let processableElements = Object.keys(object).filter(k => k in elementProcessors)
  let elements = []
  for (let key in processableElements) {
    let elementType = processableElements[key]
    elements.push(elementProcessors[elementType](object[elementType], properties))
  }
  return elements
}
