<script lang="jsx">
import { renderEditor } from '../../dg-editor'
import { hasOwn } from '../../utils'

export default {
  name: 'DgEditForm',

  props: {
    form: {
      type: Object,
      default: () => ({}),
    },
    items: {
      type: Array,
      default: () => [],
    },
    apiCode: {
      type: String,
      default: '',
    },
    drawer: {
      type: [Boolean, Object],
      default: true,
    },
    beforeSubmit: {
      type: Function,
      default: null,
    },
    onSuccess: {
      type: Function,
      default: null,
    },
    onError: {
      type: Function,
      default: null,
    },
    successText: {
      type: [String, Function, Boolean],
      default: () => (data, type) => `${type === 'insert' ? '新增' : '更新'}成功`,
    },
    type: {
      type: String,
      default: 'update',
    },
  },

  data() {
    return {
      visible: false,
      innerType: 'update',
      loading: false,
    }
  },
  computed: {
    normalizedItems() {
      const items = this.items.slice()

      return items.map((item) => {
        const { prop, models = { value: prop }, editorProps = {}, ...rest } = item
        if (!hasOwn(this.form, prop)) {
          this.$set(this.form, prop, null)
        }

        editorProps.on = editorProps.on || {}
        Object.entries(models).forEach(([attr, key]) => {
          editorProps[attr] = this.form[key]
          editorProps.on[attr === 'value' ? 'input' : `update:${attr}`] = (val) => {
            // eslint-disable-next-line vue/no-mutating-props
            this.form[key] = val
          }
        })
        return {
          ...rest,
          prop,
          editorProps,
        }
      })
    },
  },
  watch: {
    type: {
      immediate: true,
      handler(val) {
        this.innerType = val
      },
    },
  },
  beforeCreate() {
    ;['validate', 'validateField', 'resetFields', 'clearValidate'].reduce((vm, method) => {
      vm[method] = (...args) => vm.$refs['form'][method](...args)
      return vm
    }, this)
  },
  methods: {
    /**
     *
     * @param {object} form
     * @param {'update'|'insert'|'readonly'} type
     */
    show(type = 'update', form) {
      this.loading = false
      if (!this.drawer) return
      this.visible = true
      this.innerType = type

      if (form && typeof form === 'object') {
        for (let key in form) {
          if (hasOwn(form, key)) {
            // eslint-disable-next-line vue/no-mutating-props
            hasOwn(this.form, key) ? (this.form[key] = form[key]) : this.$set(this.form, key, form[key])
          }
        }
      }
    },
    handleDrawerClose() {
      this.visible = false
      this.$emit('close')
    },
    async handleSubmit(event) {
      try {
        event && event.preventDefault()

        if (this.innerType === 'readonly') return

        const valid = await this.validateAsync()
        if (!valid) return

        this.loading = true
        let rejected = false
        const done = (close = true) => {
          rejected = true
          this.visible = !close
          this.loading = false
        }
        const beforeSubmit = this.beforeSubmit || ((f) => f)
        const data = await beforeSubmit(this.form, this.innerType, done, () => (rejected = true))

        if (rejected) return

        this.$emit('submit', data, done)
        this.updateForm(data)
      } finally {
        // 可通过 beforeSubmit reject 进行打断提交
        this.loading = false
      }
    },
    async updateForm(data) {
      const { apiCode, innerType, onError } = this
      if (!apiCode) return

      try {
        const { success, msg } = await this.$api.DgJsonStore({
          apiCode,
          params: {
            command: innerType,
            records: JSON.stringify([data]),
          },
        })

        if (!success) {
          throw new Error(msg)
        }

        this.visible = false
        this._onSuccess(data)
      } catch (e) {
        this.$emit('error', e.message, data, innerType, this)
        onError && onError(e.message, data, innerType, this)
      } finally {
        this.loading = false
      }
    },
    _onSuccess(data) {
      const { onSuccess, innerType, successText } = this
      this.$emit('success', data, innerType, this)
      onSuccess && onSuccess(data, innerType, this)
      if (successText !== false) {
        let text = typeof successText === 'function' ? successText(data, innerType) : successText

        this.$message.success(text)
      }
    },
    validateAsync() {
      return new Promise((resolve) => this.validate((valid) => resolve(valid)))
    },
  },

  render(h) {
    const {
      $attrs,
      form,
      drawer,
      innerType,
      loading,
      visible,
      normalizedItems,
      formProps = {}, //  函数调用时替代 $attrs
    } = this

    const disabled = innerType === 'readonly'

    const Form = (
      <el-form
        ref="form"
        {...{
          class: 'dg-edit-form',
          props: { model: form, size: 'small', disabled, labelWidth: 'auto', ...$attrs, ...formProps },
          nativeOn: { submit: this.handleSubmit },
        }}
      >
        {normalizedItems.map((option) => {
          return (
            <el-form-item key={option.prop} label={option.label} prop={option.prop}>
              {option.slot ? this.$slots[option.slot] : renderEditor(h, { ...option }, form)}
            </el-form-item>
          )
        })}

        {!drawer && (
          <el-form-item>
            <el-button size="small" type="primary" loading={loading} native-type="submit">
              确 定
            </el-button>
          </el-form-item>
        )}
      </el-form>
    )

    if (drawer) {
      const TYPE_TITLE = {
        update: '编辑',
        insert: '新增',
        readonly: '查看',
      }
      const drawerProps = {
        title: TYPE_TITLE[innerType],
        appendToBody: true,
        ...(typeof drawer === 'object' ? drawer : {}),
      }

      const Drawer = (
        <el-drawer
          {...{ class: 'dg-edit-form__drawer', props: drawerProps, on: { 'update:visible': this.handleDrawerClose } }}
          visible={visible}
        >
          {this.$scopedSlots.title && h({ render: this.$scopedSlots.title }, { slot: 'title' })}
          <div class="dg-edit-form__drawer-body">
            {Form}
            <div class="dg-edit-form__drawer-footer">
              <el-button size="small" type="secondary" onClick={this.handleDrawerClose}>
                取 消
              </el-button>
              {innerType !== 'readonly' && (
                <el-button size="small" type="primary" loading={loading} onClick={this.handleSubmit}>
                  确 定
                </el-button>
              )}
            </div>
          </div>
        </el-drawer>
      )

      return Drawer
    }

    return Form
  },
}
</script>
