text: /* globals user, active_form_id, _user_form_config, fields, generate_id, kebabize */
  /* exported editor */
  
  let editor
  $('.field-actions').template({base: '.'})
  
  $(window).on('click', function(e) {
    if(!$(e.target).closest('.edit-properties').length && !$(e.target).closest('.user-form').length) {
      $('.edit-properties').empty()
      $('.user-form > *').removeClass('highlight')
      $('.actions').addClass('d-none')
    }
  })
  
  function render_form_from_json(_json) {
    _.each(_json, function(opts) {
      let dir = opts.component
      // _user_form_config only retains attributes from fields.js, `id` isn't captured
      $(`<${dir} id="${generate_id()}"></${dir}>`)
        .appendTo('.user-form')
        .attr(opts)
    })
  }
  
  $(function() {
    // add fields to the modal which can be viewed on + click in user form on the left
    for(let field in fields) {
      $(`<${field}></${field}>`).appendTo('.form-fields')
      $('<div class="divider"></div>').appendTo('.form-fields')
    }
  
    // render existing form using JSON
    if(active_form_id) {
      $('.edit-properties-title').removeClass('d-none')
      render_form_from_json(_user_form_config)
    }
    $('body').search()
  })
  
  /**
    * updates configuration for an existing form
    * @param {Object} form_details
  */
  function update_existing_form(form_details, $icon) {
    $.ajax('publish', {
      method: form_details.method,
      data: form_details.data,
      success: function () {
        $('.post-publish').removeClass('d-none')
      },
      error: function () {
        $('.toast-body').html('Unable to update the form. Please try again later.')
        $('.toast').toast('show')
      },
      complete: function() { $icon.fadeOut() }
    })
  }
  
  /**
    * creates configuration for a new form
    * @param {Object} form_details
  */
  function create_new_form(form_details, $icon) {
    $.ajax('publish', {
      method: form_details.method,
      data: form_details.data,
      success: function (response) {
        form_details.id = response.data.inserted[0].id
        $('.post-publish').removeClass('d-none')
        $('.form-preview-link').html(`<a class="btn btn-info" href="form/${form_details.id}" target="_blank">Preview</a>`)
        $('.form-view-link').html(`<a class="btn btn-success" href="view/${form_details.id}" target="_blank">View</a>`)
        window.location.href = `create?id=${form_details.id}`
      },
      error: function () {
        $('.toast-body').html('Unable to publish the form. Please try again later.')
        $('.toast').toast('show')
      },
      complete: function() { $icon.fadeOut() }
    })
  }
  
  function prepare_form_values(_values) {
    let form_values = []
    let _html = ''
    $('.user-form > :not(.actions)').each(function(ind, item) {
      _html += item.outerHTML
      _values = item.__model
      _values['component'] = item.tagName.toLowerCase()
      if(_values.component === 'bs4-html')
      _values.value = _values.value.replace(/\n/g, "\\n")
      if(typeof item !== undefined) {
        delete _values.$target
        form_values.push(_values)
      }
    })
    return {_form: form_values, _html: _html}
  }
  
  $('body').on('click', '#publish-form', function() {
    let _values = {}
    let $icon = $('<i class="fa fa-spinner fa-2x fa-fw align-middle"></i>').appendTo(this)
    let _md = {
      name: $('#form-name').val() || 'Untitled',
      categories: [],
      description: $('#form-description').val().trim() || 'Form description'
    }
  
    $('.edit-properties > input, .edit-properties > input').each(function(ind, item) { _values[item.id] = item.value })
    $('.user-form > *').removeClass('highlight')
    $('.edit-properties').empty()
  
    let { _form, _html } = prepare_form_values(_values)
    let form_details = {
      data: {
        config: JSON.stringify(_form),
        html: _html,
        metadata: JSON.stringify(_md),
        user: user
      }
    }
  
    if(active_form_id.length > 0) {
      form_details.data.id = active_form_id
      form_details.method = 'PUT'
      update_existing_form(form_details, $icon)
    } else {
      // POST creates a new identifier
      delete form_details.data.id
      form_details.method = 'POST'
      create_new_form(form_details, $icon)
    }
  }).on('click', '.form-fields > *:not(.divider)', function() {
    // every field added to .user-form will have a new identifier
    this.id = generate_id()
    var _type = this.tagName.toLowerCase()
    let _local_values = JSON.parse(JSON.stringify(fields[_type]))
    let vals = _.mapValues(_.keyBy(_local_values, 'name'), 'value')
    $(`.form-fields > ${_type}`)
      .data('type', _type)
      .data('vals', vals)
      .clone()
      .appendTo('.user-form')
    $('#publish-form').removeClass('d-none')
    $('.btn-link').removeClass('d-none')
    $('#addFieldModal').modal('hide')
  }).on('click', '[data-action]', function() {
    const form_el = $(this).parent().parent().next()
    if($(this).data('action') === 'duplicate') {
      form_el.removeClass('highlight')
      form_el.clone().attr('id', generate_id()).insertAfter(form_el)
    } else if($(this).data('action') === 'delete') {
      form_el.remove()
    }
    $('.edit-properties').empty()
    $('.user-form > *').removeClass('highlight')
    $('.actions').addClass('d-none')
  })
  
  $('body').on('click', '.user-form > :not(.actions)', function () {
    let this_el = $(this)
    let this_field = $(this).get(0).tagName.toLowerCase()
    $('.edit-properties').empty()
      .data('editing-element', $(this))
    $('.user-form > *').removeClass('highlight')
    $('.edit-properties-title').removeClass('d-none')
    $(this).addClass('highlight')
    $('.actions').insertBefore(this)
    $('.actions').removeClass('d-none')
  
    // Need access to field's (ex: bs4-button) JSON config to render the attributes on the right side.
    let values = JSON.parse(JSON.stringify(fields[this_field]))
    let names = _.map(values, function(item) { return item.name })
    // __model will have attributes in camelCase (ex: actionsBox for `.selectpicker`)
    let field_properties = this_el.get(0).__model
    for(let key in field_properties) {
      if(names.indexOf(kebabize(key)) !== -1) {
        _.each(values, function(item) {
          if(item.name === kebabize(key)) {
            item.value = encodeURI(field_properties[key])
          }
        })
      }
    }
    _.each(values, function(item) {
      let _el = document.createElement(item.field)
      item.id = generate_id()
      item.origin = this_el.get(0).id
      document.querySelector('.edit-properties').appendChild(_el)
      $(_el).attr(item)
    })
    // retain values on accidental page refresh
    /*eslint-disable no-unused-vars*/
    let { _form, _html } = prepare_form_values({})
    /*eslint-enable no-unused-vars*/
    localStorage.setItem('form', JSON.stringify(_form))
  })
  
  // use element.matches instead of tagName.toLowerCase()
  // each bs4-* element on the attributes form on the right side will have an origin attribute
  // its value is the id of the element that's currently edited
  $(document).on('change', '.edit-properties > [origin]', function () {
    let vals = {}
    var $el = $(this).closest('[origin]').get(0)
    var $current_attr = $(this)
    var edited_field = $(`#${$el.getAttribute('origin')}`)
  
    if($current_attr.find('select').length > 0) {
      // we have found a select element
      vals[$($el).attr('name')] = $current_attr.find('select').val()
    } else if($current_attr.find('.selectpicker').length > 0) {
      // we have found a select element
      vals[$($el).attr('name')] = $current_attr.find('.selectpicker').val()
    } else if(
        $current_attr.attr('field') == 'bs4-text' ||
        $current_attr.attr('field') == 'bs4-email' ||
        $current_attr.attr('field') == 'bs4-number' ||
        $current_attr.attr('field') == 'bs4-range' ||
        $current_attr.attr('field') == 'bs4-textarea' ||
        $current_attr.attr('field') == 'bs4-select') {
        if($current_attr.attr('field') == 'bs4-textarea') {
          // textarea
          vals[$($el).attr('name')] = $current_attr.find('textarea').val()
        } else if ($current_attr.attr('field') == 'bs4-text') {
          vals[$($el).attr('name')] = $current_attr.find('input').val()
        } else {
          // email, number, range, text
          vals[$($el).attr('name')] = $current_attr.find('input').val()
        }
    }
    else if(
        $current_attr.attr('field') === 'bs4-radio' ||
        $current_attr.attr('field') === 'bs4-checkbox') {
      // TODO - not implemented yet
      // since radio and checkbox fields each support multiple options
    } else if($current_attr.attr('field') === 'bs4-html') {
      vals[$el.name] = $current_attr.find('textarea').val()
    } else {
      // handle other attributes
    }
    delete vals[""]
    let _local_el = document.getElementById($el.getAttribute('origin'))
    for(let key in vals) {
      _local_el.setAttribute(key, vals[key])
    }
    // $($(edited_field).get(0)).attr(vals)
    $('.field-actions').template({base: '.'})
    $('.actions').removeClass('d-none')
    $('.actions').insertBefore(edited_field)
  })
  
  $('.user-form').on('submit', function(e) {
    e.preventDefault()
  })
  