<template>
  <EcoDialog v-model="dialogFormVisible" draggable v-bind="dialogProps">
    <slot name="form">
      <EcoForm
        ref="ecoFormRef"
        v-bind="$attrs"
        v-model="model"
        :form-props="dialogFormProps?.formProps"
        :items-props="newItemsProps"
        :include-model-keys="includeModelKeys">
        <template #footer v-if="$slots.formFooter">
          <slot name="formFooter" v-bind="model"></slot>
        </template>

        <template #operation v-if="$slots.formOperation">
          <slot name="formOperation" v-bind="model"></slot>
        </template>
      </EcoForm>
    </slot>

    <template #header="scope" v-if="$slots.dialogHeader">
      <slot name="dialogHeader" v-bind="{ ...model, ...scope }"></slot>
    </template>

    <template #footer>
      <slot name="dialogFooter" v-bind="model">
        <el-button @click="dialogFormVisible = !dialogFormVisible">取消</el-button>
        <el-button type="primary" @click="handleFormConfirm(model, status)">保存</el-button>
      </slot>
    </template>
  </EcoDialog>
</template>
<script setup lang="ts" name="EcoDialogForm">
import { FormInstance, type DialogProps } from 'element-plus'
import EcoForm, { EcoFormProps } from '../EcoForm/index.vue'

export type EcoDialogStatus = '' | 'edit' | 'add' | 'read'
export interface DialogFormItemProps<T = any> extends EcoForm.Item<T> {
  destroyIn?: Array<'add' | 'edit'>
  hiddenIn?: Array<'add' | 'edit'>
  disabledIn?: Array<'add' | 'edit'>
}

export interface EcoDialogFormProps<T = any> {
  dialogFormProps?: EcoFormProps
  dialog?: Partial<
    Omit<DialogProps, 'modelValue' | 'title' | 'height'> & {
      title: string | ((model: any, status: EcoDialogStatus) => string)
      fullscreen: boolean
      height: string | number | ((model: any, status: EcoDialogStatus) => string | number)
    }
  >
  id?: string | string[] // 数据主键。编辑时必传，默认 id
  cache?: boolean // 是否缓存新增、编辑后遗留的数据
  addApi?: (params: any) => Promise<any> // 新增接口
  addCarryParams?: Record<string, any> // 新增时，额外添加的函数
  addFilterKeys?: string[] // 新增时，过滤的参数
  editApi?: (params: any) => Promise<any> // 编辑接口
  editCarryParams?: Record<string, any> // 编辑时，额外添加的参数
  editFilterKeys?: string[] // 编辑时，过滤的参数
  apiFilterKeys?: string[] // 新增、编辑、删除时，过滤的参数
  apiCarryParams?: Record<string, any> // 新增、编辑、删除时，额外添加的参数
  beforeAdd?: (model: Record<string, any>) => void | Promise<any> | any // 新增前回调
  afterAdd?: (model: Record<string, any>, result: any) => void // 新增后回调
  beforeEdit?: (model: Record<string, any>) => void | Promise<any> | any // 编辑前回调
  afterEdit?: (model: Record<string, any>, result: any) => void // 编辑后回调
  beforeConfirm?: (status: string) => void // 确定按钮触发前回调
  afterConfirm?: (status: string, result: any) => void // 确定按钮触发后回调
}
const props = withDefaults(defineProps<EcoDialogFormProps>(), {
  dialogFormProps: () => ({
    formProps: {
      hasFooter: false
    }
  }),
  dialog: () => ({
    closeOnClickModal: false
  })
})
const ecoFormRef = shallowRef<Omit<InstanceType<typeof EcoForm>, keyof ComponentPublicInstance | keyof EcoFormProps>>()
const dialogFormVisible = defineModel<boolean>({ required: true })
// const dialogFormVisible = ref

// 表单
const model = ref<Record<string, any>>({})
const status = ref<EcoDialogStatus>('')
const dialogProps = computed(() => {
  const { dialog } = props

  const title = typeof dialog?.title === 'function' ? dialog?.title(unref(model), unref(status)) : dialog?.title
  const height = typeof dialog?.height === 'function' ? dialog.height(unref(model), unref(status)) : dialog?.height
  return { ...dialog, title, height }
})

// 组装主键 id & ProForm 不过滤的 keys
const includeModelKeys = computed(() => {
  const ids = Array.isArray(props.id) ? props.id : [props.id || 'id']

  const { dialogFormProps } = props
  return dialogFormProps?.includeModelKeys?.length ? [...dialogFormProps.includeModelKeys, ...ids] : ids
})

/**
 * 表单配置项
 */
const newItemsProps = computed((): EcoForm.Item[] | undefined => {
  // 目前 status 一变化，都走一遍循环，优化：可以利用 Map 存储有 show 的 column（存下标），然后监听 status，当 status 变化，则通过下标获取 column，将 hidden 设置为 true
  props.dialogFormProps.itemsProps?.forEach(column => {
    if (!column) return
    const { destroyIn, hiddenIn, disabledIn } = column

    if (Array.isArray(hiddenIn)) {
      if (hiddenIn.includes('add')) (column.hidden as boolean) = status.value === 'add'
      else if (hiddenIn.includes('edit')) (column.hidden as boolean) = status.value === 'edit'
    }

    if (Array.isArray(disabledIn)) {
      if (disabledIn.includes('add')) (column.disabled as boolean) = status.value === 'add'
      else if (disabledIn.includes('edit')) (column.disabled as boolean) = status.value === 'edit'
    }
  })

  return props.dialogFormProps?.itemsProps as EcoForm.Item[]
})

/**
 * 点击保存事件
 */
const handleFormConfirm = (data: any, status: EcoDialogStatus) => {
  const { beforeConfirm } = props

  beforeConfirm && beforeConfirm(status)

  // _enum 是 ProTable 内置的属性，专门存储字典数据，不需要发送给后台
  delete data._enum

  if (status === 'add') handleDoAdd(data)
  else if (status === 'edit') handleDoEdit(data)
}

/**
 * 执行新增事件
 */
const handleDoAdd = (data: any) => {
  const formRef = unref(ecoFormRef)?.form as FormInstance

  formRef.validate(async valid => {
    if (valid) {
      const { beforeAdd, apiFilterKeys, addFilterKeys, addApi, apiCarryParams, addCarryParams, afterAdd, cache, afterConfirm } = props

      data = (beforeAdd && (await beforeAdd(data))) || data

      // 删除 Add 不允许传输的数据
      const filterParams = [...(apiFilterKeys || []), ...(addFilterKeys || [])]
      filterParams.forEach(item => delete data[item])

      // 执行新增接口
      executeApi(
        addApi,
        { ...apiCarryParams, ...addCarryParams, ...data },
        '添加成功！',
        '添加失败！',
        async res => {
          afterAdd && (await afterAdd({ ...addCarryParams, data }, res))
          if (!cache) model.value = {}
          dialogFormVisible.value = false
          afterConfirm && afterConfirm(unref(status), true)
        },
        () => afterConfirm && afterConfirm(unref(status), false)
      )
    }
  })
}

/**
 * 执行编辑事件
 */
const handleDoEdit = (data: any) => {
  const formRef = unref(ecoFormRef)?.form as FormInstance

  formRef.validate(async valid => {
    if (valid) {
      const { beforeEdit, apiFilterKeys, editFilterKeys, editApi, apiCarryParams, editCarryParams, afterEdit, cache, afterConfirm } = props

      data = (beforeEdit && (await beforeEdit(data))) || data

      // 删除 Update 不允许传输的数据
      const filterParams = [...(apiFilterKeys || []), ...(editFilterKeys || [])]
      filterParams.forEach(item => delete data[item])

      executeApi(
        editApi,
        { ...apiCarryParams, ...editCarryParams, ...data },
        '编辑成功！',
        '编辑失败！',
        async res => {
          afterEdit && (await afterEdit({ ...editCarryParams, data }, res))
          if (!cache) model.value = {}
          dialogFormVisible.value = false
          // 回调
          afterConfirm && afterConfirm(unref(status), res)
        },
        () => afterConfirm && afterConfirm(unref(status), false)
      )
    }
  })
}

const executeApi = (
  api: undefined | ((params: any) => Promise<any>),
  params: any,
  success: string,
  failure: string,
  successCallBack?: (res: any) => void,
  failureCallBack?: (res: any) => void
) => {
  if (!api) return ElMessage.warning({ message: `${failure}没有提供对应接口`, plain: true })
  api(params)
    .then(res => {
      ElMessage.success({ message: success, plain: true })
      return successCallBack && successCallBack(res)
    })
    .catch(err => {
      return failureCallBack && failureCallBack(err)
    })
}
</script>
