//@flow
'use strict';

/* these functions are used by component that are built from model
properties, settings, etc. */

var _ = require('lodash');

module.exports = (app) => {
  return {
    // makes column from model for datatable Component
    makeColumns: function(scContent) {
      if (_.isEmpty(scContent['data'])) {
        return;
      }
      let columns = [];
      if (scContent['columns']) {
        // already have columns
        columns = scContent['columns'];
      }
      else {
        // make columns headers from data
        let model = this._modelInfo(scContent['forModel']);
        _.forEach(_.head(scContent['data']), function(value, key) {
          if (!model.prop[key] || model.prop[key]['id']) {
            // no need to display Id column OR column not from model (programatic)
            return;
          }
          columns.push({ title: key.replace(model.ns, ''), data: key });
        });
      }
      return columns;
    },

    // load data from model for datatable Component
    loadData: function(modelData, scContent) {
      if (scContent['treeData']) {
        // this data must be represented as hierarchical data (tree)
        let parentColumn = scContent.treeData.parentColumn;
        let rootParent = (scContent.treeData['root'])?scContent.treeData['root']:0;
        let childColumn = scContent.idColumn;
        // keep track of deepest level
        scContent.treeData['deepestLevel'] = 0;
        let orderStart = 1;

        function sortData(all, top, data, sort, level) {
          if (level > scContent.treeData['deepestLevel']) {
            scContent.treeData['deepestLevel'] = level;
          }
          _.forEach(_.filter(all, [parentColumn, top]), function(val) {
            val = val.toJSON();
            val['treeDisplayOrder'] = sort;
            val['treeLevel'] = level;
            data.push(val);
            // check if have children
            if (_.find(all, [parentColumn, val[childColumn]])) {
              data = sortData(all, val[childColumn], data, orderStart, (level + 1));
            }
            sort++;
          });
          return data;
        }
        return sortData(modelData, rootParent, [], orderStart, 0);
      }
      else {
        var data = [];
        _.forEach(modelData, function(val) {
          data.push(val.toJSON());
        });
        return data;
      }
    },

    // make fields list for Form Component from model
    makeFormFields: function(res, scContent, embedIn) {
      this.app = app;
      this.res = res;
      const self = this;
      let fields = [];
      if (scContent['fields']) {
        // already have fields
        fields = scContent['fields'];
      }
      else {
        // make fields from data
        let model = this._modelInfo(scContent['forModel']);
        _.forEach(model.prop, function(value, key) {
          // skip fields with a force value since they cannot modify
          if (scContent.forceVals && scContent.forceVals[key]) return;
          // skip exclude fields
          if (scContent.exclude && -1 !== scContent.exclude.indexOf(key)) return;
          // only include fields that we're told to
          if (scContent.include && -1 == scContent.include.indexOf(key)) return;
          // skip notForForm fields
          if (value.notForForm) return;
          // defaults
          let field = {
            label: (value.label)?value.label:key.replace(model.ns, ''),
            placeholder: value.placeholder,
            name: key,
            control: 'input',
            value: (value['default'])?value['default']:'',
            help: value['description'],
            format: value['format'],
            addons: value['addons'],
            embedIn: embedIn,
            validations: { },
            columns: (value.columns)?value.columns:null
          };
          // label should always propercase
          field.label = field.label.charAt(0).toUpperCase() + field.label.slice(1);
          // default is to use label as placeholder
          if (!field.placeholder) field.placeholder = field.label;
          // make Id column hidden
          if (value['id']){
            field.hidden = true;
            field.isId = true;
          }
          // other hidden fields
          if (scContent.hiddenFields && -1 !== scContent.hiddenFields.indexOf(key)){
            field.hidden = true;
          }
          if (value['enum']) {
            // controls for list items
            field.options = value['enum'];
            if (4 > value['enum'].length) field.control = 'radio';
            else field.control = 'select';
          }
          if (value['required']) {
            // required fields cannot be empty
            field.validations.allowEmpty = false;
          }
          if (value.format) {
            // format validation
            field.validations.format = value.format;
          }
          if (value.validation) {
            // add validations
            _.forEach(value.validation, function(val, k) {
              if ('type' == k && 'boolean' == val.value) {
                // for boolean, we show a single checkbox
                field.label = '';
                field.options = [value.label];
                field.control = 'checkbox';
                field.columns = 12;
                // prevents from assigning values as obj array
                field.boolean = true;
                // no need to assign as client-side validator
                return;
              }
              if (!field.validations.messages) field.validations.messages = {};
              field.validations[k] = val.value;
              if (val.message) field.validations['messages'][k] = val.message;
              // determin input size
              if ('input' == field.control && 'maxLength' == k) {
                if (val.value > 140) field.control = 'textarea';
                else if (val.value > 60) field.columns = 12;
              }
            });
          }
          // get embeded model props
          let embed = _.find(model.settings.relations, { type: 'embedsOne', property: key });
          if (embed) {
            let embedField = self.makeFormFields(res, {forModel: embed.model}, key);
            fields = fields.concat(embedField);
            return;
          }
          if (value.embedGetter) {
            // embed additional fields from theme/apps
            return _embedGetter(self, fields, value.embedGetter, key);
          }
          if (value.optionGetter) {
            // embed additional options from theme/apps
            _optionGetter(self, value.optionGetter, field.options, field);
          }
          // check for mixin
          if (scContent.fieldMixins && scContent.fieldMixins[key]) {
            let mixin = scContent.fieldMixins[key];
            if (mixin.eval) {
              // when mixin has script
              eval(mixin.eval);
            }
          }
          fields.push(field);
        });
      }
      return fields;
    },

    // save posted data for Form Component from model
    saveForm: function(res, scContent, post, cb) {
      let model = this._modelInfo(scContent['forModel']);
      // get list of expected model props
      let fields = this.makeFormFields(res, scContent);
      let data = {};
      let where = null;
      _.forEach(fields, function(val) {
        if (val.isId) {
          if (post[val.name]) where = {[val.name]: post[val.name]}
        }
        else if (val.name in post) {
          if (val.embedIn) {
            data[val.embedIn] = Object.assign({}, data[val.embedIn],
              {[val.name]: post[val.name]});
          }
          else data[val.name] = post[val.name];
        }
      });
      // add forceVals
      if (scContent.forceVals) {
        _.forEach(scContent.forceVals, function(val, key) {
          data[key] = val;
        });
      }
      // save data
      if (where) {
        // update existing
        model.model.upsertWithWhere(where, data, (err, inst) => {
          cb(err, inst);
        });
      }
      else {
        model.model.create(data, (err, inst) => {
          cb(err, inst);
        });
      }
    },

    // get infomation about a model
    _modelInfo: function(forModel) {
      let model = {
        model: app.models[forModel],
        prop: app.models[forModel].definition.properties,
        settings: app.models[forModel].settings,
        meta: app.models[forModel].settings.zlMeta,
        ns: ''
      };
      if (model.meta && model.meta['Namespace']) {
        model.ns = model.meta['Namespace'];
      }
      return model;
    },

    // eval and other code call this to run a model method then cb
    _doMethThenCall: function (modMeth, filter, cb = (err, val, cb) => cb()) {
      this.promises.push( new Promise((resolve, reject) => {
        modMeth(...filter, (err, val) => {
          cb(err, val, resolve);
        });
      }));
    }
  }
}

// get theme options to embed fields
function _embedGetter(self, fields, embedGetter, key) {
  let allOpts = [];
  self.res.locals.emiter.emit('get-options', embedGetter, allOpts);
  if (allOpts) {
    allOpts.forEach(function(obj) { obj.embedIn = key; });
    fields.push(...allOpts);
  }
}

function _optionGetter(self, optionGetter, allOpts = [], field) {
  allOpts = _.cloneDeep(allOpts);
  self.res.locals.emiter.emit('get-options', optionGetter, allOpts);
  if (allOpts) {
    field.options = allOpts;
    if (4 > allOpts.length) field.control = 'radio';
    else field.control = 'select';
  }
}
