// LAYOUT.JS
import utils from './utils'

/**
 * Base class for controlling the layout of each 'row' on the form
 * Can be extended & customised with the new object being passed to FormRender as the new layout object
 * Controls things like the label, help text, and how they fit together with the control itself
 */
export default class layout {
  /**
   * Prepare the templates for layout
   * @param {Object} templates object containing custom or overwrite templates
   * @param {Boolean} preview - are we rendering a preview for the formBuilder stage
   */
  constructor(templates, preview) {
    this.preview = preview

    // supported templates for outputting a field
    // preferred layout template can be indicated by specifying a 'layout' in the return object of control::build
    this.templates = {
      label: null, // can be overridden with a function(labelDOMElements, data) to generate the label element - returns a DOM element
      help: null, // can be overridden with a function(helpText, data) to generate the help element - returns a DOM element
      default: (field, label, help, data) => {
        // append help into the label
        if (help) {
          label.appendChild(help)
        }

        // wrap the output in a form-group div & return
        let className = data.id ? `fb-${data.type} form-group field-${data.id}` : ''
    
        // Lift any col- and row- type class to the form-group wrapper. The row- class denotes the row group it should go to
        if(data.className){
          const classes = data.className.split(' ').filter(className => (/^col-(xs|sm|md|lg)-([^\s]+)/.test(className) || className.startsWith('row-')));
          if(classes && classes.length > 0){
            className += ` ${classes.join(' ')}`;

            // Now that the col- types were lifted, remove from the actual input field
            for (let index = 0; index < classes.length; index++) {
              const element = classes[index];
              field.classList.remove(element)
            }
          }
        }

        return this.markup('div', [label, field], {
          className: className,
        })
      },
      noLabel: (field, label, help, data) => {
        // wrap the output in a form-group div & return without a label element
        const className = data.id ? `fb-${data.type} form-group field-${data.id}` : ''
        return this.markup('div', field, {
          className: className,
        })
      },
      hidden: (field, label, help, data) => {
        // no wrapper any any visible elements
        return field
      },
    }

    // merge in any custom templates
    if (templates) {
      this.templates = jQuery.extend(this.templates, templates)
    }
    this.configure()
  }

  /**
   * this method is called by the constructor and should be overwritten for custom layouts that need to
   * process the configuration arguments prior to rendering
   */
  configure() {}

  /**
   * Process the configuration from an element from the standard formData array
   * building the control, label and help text, and then putting them all together.
   * Should support the control object returning a DOM element, or an object containing
   * configuration properties:
   *   - field - the DOM element
   *   - noLabel - this control shouldn't have a label (nor a space for a label)
   *   - hidden - this control shouldn't render anything visible to the page
   * @param {Object} renderControl - the relevant control class
   * @param {Object} data - configuration data passed through formData for this control
   * @param {String} forceTemplate - programatically force the template with which this control to be rendered
   * @return {Object} element
   */
  build(renderControl, data, forceTemplate) {
    // prepare the data
    if (this.preview) {
      if (data.name) {
        data.name = data.name + '-preview'
      } else {
        data.name = utils.nameAttr(data) + '-preview'
      }
    }
    data.id = data.name
    this.data = jQuery.extend({}, data)

    // build the control
    const control = new renderControl(data, this.preview)
    let field = control.build()
    if (typeof field !== 'object' || !field.field) {
      field = { field: field }
    }

    // build the label & help text
    const label = this.label()
    const help = this.help()

    // process the relevant layout template
    let elementTemplate
    if (forceTemplate && this.isTemplate(forceTemplate)) {
      elementTemplate = forceTemplate
    } else {
      elementTemplate = this.isTemplate(field.layout) ? field.layout : 'default'
    }
    const element = this.processTemplate(elementTemplate, field.field, label, help)

    // execute prerender events
    control.on('prerender')(element)

    // bind control on render events
    element.addEventListener('fieldRendered', control.on('render'))
    return element
  }

  /**
   * Build a label element
   * @return {Object} dom element to render the label
   */
  label() {
    const label = this.data.label || ''
    const labelText = utils.parsedHtml(label)
    const labelContents = [labelText]
    if (this.data.required) {
      labelContents.push(this.markup('span', '*', { className: 'fb-required' }))
    }

    // support an override template for labels
    if (this.isTemplate('label')) {
      return this.processTemplate('label', labelContents)
    }

    // generate a label element
    return this.markup('label', labelContents, {
      for: this.data.id,
      className: `fb-${this.data.type}-label`,
    })
  }

  /**
   * Build a help element
   * @return {Object} dom element to render the help text
   */
  help() {
    if (!this.data.description) {
      return null
    }

    // support an override template for labels
    if (this.isTemplate('help')) {
      return this.processTemplate('help', this.data.description)
    }

    // generate the default help element
    return this.markup('span', '?', {
      className: 'tooltip-element',
      tooltip: this.data.description,
    })
  }

  /**
   * Determines if a template is defined for the specified key
   * @param {String} template string template key to check for
   * @return {Boolean}
   */
  isTemplate(template) {
    return typeof this.templates[template] === 'function'
  }

  /**
   * Process a template & prepare the results
   * @param {String} template - template key to execute
   * @param {Array} args - any number of args that should be passed to the template. this.data is sent as the last parameter to any template.
   * @return {DOMElement}
   */
  processTemplate(template, ...args) {

    let processed = this.templates[template](...args, this.data)

    if (processed.jquery) {
      processed = processed[0]
    }
    return processed
  }

  /**
   * link to the utils.markup method
   * ideally this would be inherited from a parent 'dom' type element supporting dom helper type methods
   * @param {String} tag
   * @param {Object|String|Array} content
   * @param {Object} attributes
   * @return {Object} DOM element
   */
  markup(tag, content = '', attributes = {}) {
    return utils.markup(tag, content, attributes)
  }
}
