<script>
import Vue from 'vue';
import mergeTag from './mf-tag.vue';
import * as _ from 'lodash';

const MergeTag = Vue.extend(mergeTag);

export default Vue.component('editable-area', {
  components: {
    MergeTag
  },

  props: {
    content: { required: true, type: Array, default: () => [] },
    tagList: { required: true, type: Object, default: () => {} }
  },

  data () {
    return {
      mergeTags: [],
      activeEl: null,
      activeElOffset: 0,
      observer: null
    };
  },

  created () {
    // console.log('CREATED editable-area')
  },

  mounted () {
    this.$nextTick(() => this.mountMergeTagComponents());

    // when a merge-field got deleted the style of the newly entered text will remain the same (blue or red color)
    // moving caret for 1 position back and forth helps to eliminate this bug
    this.observer = new MutationObserver((mutations) => {
      debugger;
      if (!mutations[0].removedNodes.length) return;

      const selection = window.getSelection();
      selection.modify('move', 'right', 'character');
      selection.modify("move", 'left', 'character');
    });
    this.observer.observe(this.$el, {childList: true, subtree: true, attributeFilter: ['data-type']});
  },

  beforeDestroy () {
    _.forEach(this.mergeTags, tag => tag.$destroy());
    this.observer.disconnect();
  },

  methods: {
    parseNode (arr, createElement) {
      if (!Array.isArray(arr) && arr === '') {
        throw new Error('editable-area parseNode: Content must be an Array', arr);
      }

      return arr.map((item, i) => {
        if (item.type === 'merge-field') {
          if (item.hasOwnProperty('replacer') && item.replacer !== '') {
            return this._v(item.replacer);
          }

          const classNames = ['text-wrapper', 'merge-field', 'text-wrapper--' + item.variable];
          const attrList = {
            'data-type': item.type,
            'data-variable': item.variable,
            'data-label': '{' + item.variable + '}'
          };

          if (item.isActive) attrList['data-active-tag'] = '';

          return createElement(
            'span',

            {
              attrs: {
                ...attrList
              },

              class: classNames.join(' ')
            },

            '{' + item.variable + '}'
            
            // ' ' // single space for selection
          );
        } else if (item.type === 'text') {
          return this._v(item.content);
        } else {
          const classNames = ['text-wrapper', 'text-wrapper--' + item.type];
          const attrList = this.elementAttrs(item);

          if (item.isActive) {
            attrList['data-active-tag'] = '';
          }

          if (this.tagList && !this.tagList.hasOwnProperty(item.type)) {
            classNames.push('text-wrapper--error');
          }

          return createElement(
            'span',

            {
              attrs: {
                ...attrList
              },

              class: classNames.join(' ')
            },

            Array.isArray(item.content) ? this.parseNode(item.content, createElement) : item.content
          );
        }
      });
    },

    isMFTagDomNode (node) {
      if (node) {
        return node.nodeType === Node.ELEMENT_NODE && node.dataset && _.isString(node.dataset.variable);
      }
      return false;
    },

    isTagDomNode (node) {
      if (node) {
        return node.nodeType === Node.ELEMENT_NODE && node.dataset && !_.isString(node.dataset.variable);
      }
      return false;
    },

    addMFTagToDomNode (node, variableName) {
      const tag = new MergeTag({
        el: node,
        propsData: {
          dataVariable: variableName
        }
      });
			
      this.mergeTags.push(tag);
    },

    mountMergeTagComponents () {
      // skip creating of editable merge-tags for preview
      if (this.$el.contentEditable === 'false') return;
			
      // destroy all merge-tag components from the previous convertion
      _.forEach(this.mergeTags, tag => {
        tag.$destroy();
      });
      // clean up the list of merge-tag components
      this.mergeTags = [];

      _.forEach(this.$el.childNodes, node => {
        if (this.isMFTagDomNode(node)) {
          this.addMFTagToDomNode(node, node.dataset.variable);
        } else if (this.isTagDomNode(node)) {
          const child = node.childNodes[0];
					
          if (this.isMFTagDomNode(child)) {
            this.addMFTagToDomNode(child, child.dataset.variable);
          }
        }
      });
    },

    setFocus () {
      this.$el.focus();
      const sel = document.getSelection();

      const lastNode = _.last(this.$el.childNodes);
      const lastNodeTextLength = _.get(lastNode, 'textContent.length');

      const [activeEl, activeOffset] = _.get(sel, 'focusNode.nodeType') === 3
        ? [sel.focusNode, sel.focusOffset]
        : [lastNode, lastNodeTextLength];

      this.activeEl = activeEl;
      this.activeElOffset = activeOffset;
    },

    addMFTagDomNode (variableName = '') {
      this.$el.focus();
      const sel = document.getSelection();
      sel.collapseToStart();

      if (this.activeEl) {
        sel.setBaseAndExtent(
          this.activeEl,
          this.activeElOffset,
          this.activeEl,
          this.activeElOffset
        );
      }

      const mergeTagNode = document.createElement('span');
      mergeTagNode.setAttribute('class', `text-wrapper merge-field text-wrapper--${variableName}`);
      mergeTagNode.setAttribute('contenteditable', 'false');
      mergeTagNode.setAttribute('data-active-tag', '');
      mergeTagNode.setAttribute('data-type', 'merge-field');
      mergeTagNode.setAttribute('data-variable', variableName);
      mergeTagNode.innerHTML = `{${variableName}}`;
			
      if (sel.rangeCount) {
        const range = sel.getRangeAt(0).cloneRange();
        range.insertNode(mergeTagNode);
        sel.removeAllRanges();
        sel.addRange(range);
        range.collapse();

        this.mountMergeTagComponents();
      }
    },

    lineBreakHandler (e) {
      if (e.key === 'Enter') {
        e.preventDefault();
      }

      if (e.shiftKey) {
        
      }
    },

    selectionHandler (e) {
      if (e.shiftKey) {
        return;
      }

      this.$el.querySelectorAll('[data-active-tag]').forEach(item => {
        item.removeAttribute('data-active-tag');
      });

      if (e.target.classList.contains('text-wrapper--error') === true) { return; }

      if (this.$el !== e.target) {
        e.target.setAttribute('data-active-tag', '');
      }

      // detects if all content was selected and deleted with backspace. 
      // Fix for issue with extra <br> and <break> tag
      if (this.$el.textContent.length === 0 && e.keyCode === 8) {
        this.$el.innerHTML = '';
      }

      this.$emit('editable:modified', e);
    },

    blurHandler (e) {
      this.$emit('editable:blurred', e);
    },

    inputHandler (e) {
      e.preventDefault();
      this.$emit('editable:input', e);
    },

    pasteHandler (e) {
      const paste = (e.clipboardData || window.clipboardData).getData('text');

      const selection = window.getSelection();
      if (!selection.rangeCount) return false;
      selection.deleteFromDocument();
      selection.getRangeAt(0).insertNode(document.createTextNode(paste));

      e.target.normalize();
      e.preventDefault();

      this.$emit('editable:paste', e);
    },

    elementAttrs (item) {
      const output = {};

      output['data-type'] = item.type;
      output['data-label'] = item.type;

      if (item.hasOwnProperty('attrs')) {
        Object.keys(item.attrs).forEach(key => {
          const val = item.attrs[key];
          const attrDetails = this.tagList[item.type] ? this.tagList[item.type].attr[key] : {};
          const showIf = attrDetails.hasOwnProperty('showif') ? attrDetails.showif : false;

          if (showIf && showIf.name) {
            const showIfTarget = item.attrs[showIf.name];
            const showThisAttribute = showIfTarget === showIf.value;

            if (showThisAttribute) {
              output['data-label'] = val ? item.type + ': ' + val : item.type;
            }
          } else {
            output['data-label'] = val ? item.type + ': ' + val : item.type;
          }

          output['data-attr-' + key] = val;
        });
      }

      return output;
    }
  },
  render: function (createElement) {
    return createElement(
      'div',

      {
        attrs: {
          class: 'editable',
          id: 'editable',
          contenteditable: true
        },

        on: {
          keydown: this.lineBreakHandler,
          mouseup: this.selectionHandler,
          keyup: this.selectionHandler,
          blur: this.blurHandler,
          input: this.inputHandler,
          paste: this.pasteHandler
        }
      },

      this.parseNode(this.content, createElement)
    );
  }
});
</script>
