import m from 'mithril';
import Ajv from 'ajv';
import jsonSchemaDraft04 from 'ajv/lib/refs/json-schema-draft-04.json';
import { inputGroup, selectGroup } from './formFields';

export default class JSONSchemaForm {
  constructor() {
    this.data = {};
    this.errors = {};

    this.ajv = new Ajv({
      missingRefs: 'ignore',
      errorDataPath: 'property',
      allErrors: true,
    });
    this.ajv.addMetaSchema(jsonSchemaDraft04);
  }

  oninit(vnode) {
    this.schema = vnode.attrs.schema;
    if (this.schema === null || typeof this.schema !== 'object') {
      this.schema = undefined;
    } else {
      this.ajv.addSchema(this.schema, 'schema');
    }
  }

  // bind form-fields to the object data and validation
  bind(attrs) {
    // initialize error-list for every bound field
    if (!this.errors[attrs.name]) this.errors[attrs.name] = [];

    const boundFormelement = {
      oninput: (e) => {
        // bind changed data
        this.data[e.target.name] = e.target.value;

        // validate against schema
        const validate = this.ajv.getSchema('schema');
        this.valid = validate(this.data);

        if (this.valid) {
          Object.keys(this.errors).forEach((field) => {
            this.errors[field] = [];
          });
        } else {
          // get errors for respective fields
          Object.keys(this.errors).forEach((field) => {
            const errors = validate.errors.filter(error =>
              `.${field}` === error.dataPath);
            this.errors[field] = errors.map(error => error.message);
          });
        }
      },
      getErrors: () => this.errors[attrs.name],
      value: this.data[attrs.name],
    };
    // add the given attributes
    Object.keys(attrs).forEach((key) => { boundFormelement[key] = attrs[key]; });

    return boundFormelement;
  }

  view() {
    const elements = this.renderFormElements();
    return m('form', elements);
  }

  isValid() {
    return this.schema === undefined || this.valid;
  }

  validate() {
    // validate the new data against the schema
    const validate = this.ajv.getSchema('schema');
    this.valid = validate(this.data);
  }

  getValue() {
    return this.data;
  }

  // render all schema properties to an array of form-fields
  renderFormElements() {
    const elements = [];
    if (this.schema !== undefined) {
      Object.keys(this.schema.properties).forEach((key) => {
        elements.push(this._renderProperty(key, this.schema.properties[key]));
      });
    }
    return elements;
  }

  // render schema property to form-fields
  _renderProperty(key, item) {
    if ('enum' in item) {
      return m(selectGroup, this.bind({
        name: key,
        title: item.description,
        args: {
          options: item.enum,
          type: 'select',
          multipleSelect: false,
        },
      }));
    }
    switch (item.type) {
      case 'integer': {
        return m(inputGroup, this.bind({ name: key, title: item.description, args: { type: 'number', step: 1 } }));
      }
      case 'number': {
        return m(inputGroup, this.bind({ name: key, title: item.description, args: { type: 'number' } }));
      }
      case 'boolean': {
        return m(inputGroup, this.bind({ name: key, title: item.description, args: { type: 'checkbox' } }));
      }
      case 'array': {
        return m(selectGroup, this.bind({
          name: key,
          title: item.description,
          args: {
            options: item.items.enum,
            type: item.items.enum.length > 8 ? 'select' : 'buttons',
            multipleSelect: true,
          },
        }));
      }
      case 'string':
      default: {
        return m(inputGroup, this.bind({ name: key, title: item.description, args: { type: 'text' } }));
      }
    }
  }
}