<template>
  <div class="edit-table">
    <EasyForm
      v-if="showSearchForm"
      ref="SearchEasyForm"
      v-model="searchForm"
      class="edit-table-search"
      :meta="{isSearchForm: true, ..._meta}"
      :fields="fields"
      v-bind="searchFormProp"
    >
      <template v-for="(item) in allSlotName" v-slot:[item]="scope">
        <slot
          :name="item"
          v-bind="scope"
        />
      </template>
    </EasyForm>
    <slot name="table-top" :instance="that" />
    <EasyTable
      ref="EasyTable"
      v-model="value"
      :slot-mode="inlineMode"
      :on-load="tableProp.onLoad || (searchLoader ? onTableLoad : undefined)"
      v-bind="tableProp"
      :meta="{isTable: true, ..._meta}"
      :fields="tableFields"
      @update:selected="(value)=>{
        _selected = value
      }"
    >
      <template v-slot:virtualForm="scope">
        <EasyForm
          :key="scope.row[rowKey]"
          :value="scope.row._editRowClone || scope.row"
          class="virtual-form"
          :custom-mode="true"
          :fields="fields"
          @created="(instance)=>{
            if(!scope.row._virtualForm || !virtualFormMap[scope.row[rowKey] || scope.row._virtualForm]){
              const id = scope.row[rowKey] || Math.random().toString()
              $set(scope.row, '_virtualForm', id)
              $set(virtualFormMap, id, instance)
            }
          }"
          @beforeDestroy="(instance) => {
            delete virtualFormMap[scope.row[rowKey] || scope.row._virtualForm]
          }"
        >
          <template>
            <el-form-item
              v-for="field in fields"
              v-show="false"
              :key="field.prop"
              :show-message="false"
              :prop="field.prop"
            />
          </template>
        </EasyForm>
      </template>
      <template v-for="(item) in allSlotName" v-slot:[item]="tableScope">
        <slot
          :isEdit="tableScope.row._isEditing || alwaysEdit"
          :editFormData="tableScope.row._editRowClone || tableScope.row"
          v-bind="tableScope"
          :name="item"
        />
      </template>
      <template
        v-for="field in fields"
        :slot="field.prop"
        slot-scope="tableScope"
      >
        <EasyFormItem
          v-if="virtualFormMap[tableScope.row[rowKey] || tableScope.row._virtualForm]"
          :key="field.prop"
          :field="tableScope.field"
          :config-is-view="!(tableScope.row._isEditing || alwaysEdit)"
          v-bind="{
            ...virtualFormMap[tableScope.row[rowKey] || tableScope.row._virtualForm].easyFormItemBindData,
            formData: tableScope.row._editRowClone || tableScope.row,
            componentScope: {
              ...virtualFormMap[tableScope.row[rowKey] || tableScope.row._virtualForm].easyFormItemBindData.componentScope,
              ...tableScope.componentScope,
            }
          }"
          @update:rules="(value)=>{ virtualFormMap[tableScope.row[rowKey] || tableScope.row._virtualForm].updateRules(tableScope.field.prop, value) }"
          @created="(instance) => { virtualFormMap[tableScope.row[rowKey] || tableScope.row._virtualForm].registerFormItem(tableScope.field.prop, instance) }"
          @beforeDestroy="(instance) => {
            const virtualForm = virtualFormMap[tableScope.row[rowKey] || tableScope.row._virtualForm]
            if(virtualForm){
              virtualForm.unRegisterFormItem(tableScope.field.prop, instance)
            }
          }"
        >
          <template v-for="(item) in allSlotName" v-slot:[item]="formScope">
            <slot
              :isEdit="tableScope.row._isEditing || alwaysEdit"
              :editFormData="tableScope.row._editRowClone || tableScope.row"
              v-bind="{
                ...formScope,
                ...tableScope
              }"
              :name="item"
            />
          </template>
        </EasyFormItem>
      </template>
    </EasyTable>
    <el-dialog
      :visible.sync="dialogVisible"
      v-bind="{
        closeOnClickModal: false,
        appendToBody: true,
        ...dialogProp,
        title: getPropValue(dialogProp.title, dialogFormData, formProp),
      }"
    >
      <EasyForm
        ref="EasyForm"
        v-model="dialogFormData"
        v-bind="formProp"
        :meta="{isForm: true, ..._meta}"
        :fields="fields"
      >
        <template v-for="(item) in allSlotName" v-slot:[item]="scope">
          <slot
            :editFormData="dialogFormData"
            :isEdit="true"
            v-bind="scope"
            :name="item"
          />
        </template>
      </EasyForm>
    </el-dialog>
  </div>
</template>

<script>
import EasyTable from '../EasyTable'
import EasyForm from '../EasyForm'
import EasyFormItem from '../EasyFormItem'
import { createPathObject, deepClone } from '../utils'
export default {
  name: 'EditTable',
  version: '3',
  components: {
    EasyTable,
    EasyForm,
    EasyFormItem
  },
  props: {
    fields: {
      type: Array,
      default: () => {
        return []
      }
    },
    tableProp: {
      type: Object,
      default: () => {
        return {}
      }
    },
    formProp: {
      type: Object,
      default: () => {
        return {}
      }
    },
    searchFormProp: {
      type: Object,
      default: () => {
        return {}
      }
    },
    dialogProp: {
      type: Object,
      default: () => {
        return {}
      }
    },
    meta: {
      type: Object,
      default: () => {
        return {}
      }
    },
    value: {
      type: Array,
      default: () => {
        return []
      }
    },
    rowKey: {
      type: String,
      default: () => {
        return 'id'
      }
    },
    inlineMode: {
      type: Boolean,
      default: () => {
        return false
      }
    },
    showSearchForm: {
      type: Boolean,
      default: () => {
        return false
      }
    },
    searchLoader: {
      type: Function,
      default: undefined
    },
    alwaysEdit: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      // 仅弹窗编辑使用
      dialogFormData: {},
      dialogVisible: false,
      parseSearchFormData: {},
      searchForm: {},
      selected: [],
      virtualFormMap: {}
    }
  },
  computed: {
    tableFields() {
      return [
        ...this.fields,
        {
          type: 'slot',
          slotName: 'virtualForm',
          label: 'virtualForm',
          prop: 'virtualForm',
          tableProps: {
            width: '1px',
            minWidth: '1px'
          }
        }
      ]
    },
    componentScope() {
      return {
        searchForm: this.searchForm,
        selected: this.selected
      }
    },
    _selected: {
      set(value) {
        this.$emit('update:selected', value)
        this.selected = value
      },
      get() {
        return this.selected
      }
    },
    _meta() {
      return {
        _editTable: {
          submitForm: (row, options) => {
            const realRowPayload = this.findRow(row)
            if (realRowPayload) {
              return this.virtualFormMap[realRowPayload.row[this.rowKey] || realRowPayload.row._virtualForm].submitForm(options)
            } else {
              throw new Error('无法找到对应的表单实例')
            }
          },
          editTableInstance: this
        },
        ...this.meta
      }
    },
    allSlotName() {
      return Object.keys({
        ...this.$slots,
        ...this.$scopedSlots
      })
    },
    that() {
      return this
    }
  },
  methods: {
    createPathObject(...args) {
      return createPathObject(...args)
    },
    getPropValue(prop, ...args) {
      return typeof prop === 'function' ? prop(this.componentScope, ...args) : prop
    },
    onTableLoad(pageParam, instance) {
      return this.searchLoader(pageParam, this.parseSearchFormData, instance)
    },
    /**
     * public
     */
    editReady(row = {}) {
      if (!row[this.rowKey] && !row._tsid) {
        row._tsid = (new Date()).valueOf()
      }
      const isAdd = !this.value.some((item) => {
        if (row._tsid) {
          return row._tsid === item._tsid
        }
        if (row[this.rowKey]) {
          return row[this.rowKey] === item[this.rowKey]
        }
        return row === item
      })
      const editRowClone = {}
      for (const key in row) {
        if (!['_isEditing', '_editRowClone'].includes(key)) {
          editRowClone[key] = row[key]
        }
      }
      try {
        this.$set(row, '_editRowClone', createPathObject(deepClone(editRowClone)))
      } catch (e) {
        console.warn('深拷贝对象失败，将使用浅拷贝', e)
        this.$set(row, '_editRowClone', createPathObject({ ...editRowClone }))
      }
      row._editRowClone._isAdd = isAdd
      row._isEditing = true
      if (this.inlineMode) {
        if (isAdd) {
          row._isNoSave = true
          this.value.push(row)
          this.$emit('input', this.value)
        }
      } else {
        this.dialogVisible = true
        this.dialogFormData = row._editRowClone
      }
      this.$nextTick(() => {
        if (this.$refs.EasyForm) {
          this.$refs.EasyForm.clearValidate()
        }
      })
    },
    editConfirm(row) {
      if (!row[this.rowKey] && !row._tsid) {
        row._tsid = (new Date()).valueOf()
      }
      const index = this.value.findIndex((item) => {
        if (row._tsid) {
          return row._tsid === item._tsid
        }
        if (row[this.rowKey]) {
          return row[this.rowKey] === item[this.rowKey]
        }
        return row === item
      })
      const newRow = {
        ...row
      }
      for (const key in this.value[index]) {
        if (key.startsWith('_')) {
          newRow[key] = this.value[index][key]
        }
      }
      delete newRow._editRowClone
      delete newRow._isNoSave
      delete newRow._isEditing
      if (index > -1) {
        this.value.splice(index, 1, newRow)
      } else {
        if (!this.inlineMode) {
          this.value.push(newRow)
        }
      }
      this.$emit('input', this.value)
      this.dialogVisible = false
    },
    deleteRow(row) {
      const { index } = this.findRow(row)
      this.value.splice(index, 1)
      this.$emit('input', this.value)
    },
    cancelEdit(fakeRow) {
      this.dialogVisible = false
      const { row } = this.findRow(fakeRow)
      delete row._isEditing
      delete row._editRowClone
      if (row._isNoSave) {
        this.deleteRow(row)
      }
      delete row._isNoSave
    },
    findRow(row) {
      for (let i = 0; i < this.value.length; i++) {
        const item = this.value[i]
        if (row._tsid && row._tsid === item._tsid) {
          return {
            row,
            index: i
          }
        }
        if (row[this.rowKey] && row[this.rowKey] === item[this.rowKey]) {
          return {
            row,
            index: i
          }
        }
        if (row === item) {
          return {
            row,
            index: i
          }
        }
      }
    },
    setSelected(selected) {
      this.$refs.EasyTable.setSelected(selected)
    },
    search(options = { reset: false, resetData: {}}) {
      if (this.showSearchForm) {
        if (options.reset) {
          this.$refs.SearchEasyForm.resetFormData(options.resetData)
        }
        return this.$refs.SearchEasyForm.submitForm({
          showValidMessage: false
        }).then((formData) => {
          this.parseSearchFormData = formData
          return this.$refs.EasyTable.reset()
        })
      } else {
        this.parseSearchFormData = {}
        return this.$refs.EasyTable.reset()
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.edit-table-search{
  margin-bottom: 10px;
}
.edit-table{
  /deep/ .easy-table-header-cell-virtualForm{
    display: none;
  }
}
</style>
