<template>
  <z-form ref="form" class="z-schema-form" v-model="model" v-bind="schema.props" v-on="schema.on">
    <template v-for="(item, index) in schema.items">
      <template v-if="item.is">
        <z-form-item v-if="bindParam(item, 'if')" v-show="bindParam(item, 'show')" v-bind="bindItemProps(item)" :key="index">
          <slot v-if="$scopedSlots[item.prop]" :name="item.prop" :value="get(model, item.prop)" :onInput="value => onComponentInput({ value, item })" v-bind="slotProps"> </slot>
          <item-render v-else :item="item" :value="get(model, item.prop)" :onInput="value => onComponentInput({ value, item })"></item-render>
          <slot :name="`label-${item.prop}`" slot="label" v-bind="slotProps"></slot>
          <slot :name="`error-${item.prop}`" slot="error" v-bind="slotProps"></slot>
        </z-form-item>
      </template>
      <template v-else>
        <z-form-item v-if="bindParam(item, 'if')" v-show="bindParam(item, 'show')" v-bind="bindItemProps(item)" :key="index" :value="get(model, item.prop)">
          <slot :name="item.prop" :value="get(model, item.prop)" :onInput="value => onComponentInput({ value, item })" v-bind="slotProps"></slot>
        </z-form-item>
      </template>
    </template>
    <slot name="footer" v-bind="slotProps"></slot>
  </z-form>
</template>

<script>
import MIX_FORM from '../mixins/form';
import { cloneDeep, get, set } from '../utils';

export default {
  name: 'SchemaForm',
  mixins: [MIX_FORM],
  components: {
    ItemRender: {
      functional: true,
      render(h, context) {
        const props = context.props;
        const item = props.item || {};
        let content = [];
        if (item.render && typeof item.render === 'function') {
          content = context.props.render;
        }
        if (item.children) {
          if (Array.isArray(item.children)) {
            if (item.children.length > 0) {
              content = item.children.map(i => h('item-render', { props: { item: i } }));
            }
          } else {
            content = [item.children];
          }
        }
        let _props = item.props || {};
        if (props.value) {
          _props = { ..._props, value: props.value };
        }
        let _on = item.on || {};
        if (props.onInput) {
          _on = { ..._on, input: props.onInput };
        }
        return h(item.is, { attrs: item.attrs, props: _props, on: _on }, content);
      },
    },
  },
  props: {
    value: {
      type: Object,
      default() {
        return {};
      },
    },
    schema: {
      type: Object,
      required: true,
    },
  },
  data() {
    return {
      model: this.value,
      originData: {},
    };
  },
  computed: {
    schemaProps() {
      return this.schema.props;
    },
    slotProps() {
      return {
        submit: this.onSubmit,
        cancel: this.onCancel,
        reset: this.onReset,
      };
    },
  },
  created() {
    const { originData, ...other } = this._data;
    this.originData = cloneDeep(other);
  },
  watch: {
    value(val = {}) {
      this.model = val;
    },
    model: {
      handler(val) {
        this.$emit('input', val);
      },
      deep: true,
    },
  },
  methods: {
    get,
    bindParam(item, key) {
      if (typeof item[key] === 'function') {
        return item[key]({ model: this.model });
      }
      if (['if', 'show'].includes(key)) {
        if (['', null, undefined].includes(item[key])) {
          return true;
        }
      }
      return item[key];
    },
    bindItemProps(item) {
      const { children, is, props, on, ...other } = item || {};
      return Object.keys(other).reduce((result, current) => {
        result = { ...result, [current]: this.bindParam(item, current, item[current]) };
        return result;
      }, {});
    },
    onComponentInput({ value, item }) {
      set(this.model, item.prop, value);
    },
    onSubmit() {
      this.$refs.form.validate(valid => {
        if (valid) {
          this.$emit('submit', this.model);
        }
      });
    },
    onCancel() {
      this.$emit('cancel');
    },
    onReset() {
      this.model = cloneDeep(this.originData).model;
      this.$refs.form.resetFields();
      this.$emit('reset');
    },
  },
};
</script>
