<template>
  <div class="form-grid" :class="[`form-grid__${prop}`]">
    <el-row v-if="isInit" v-bind="gridOption.params">
      <template v-for="(item, key) in gridOption.column" :key="key">
        <el-col
          :span="item.span"
          v-if="item.display"
          :class="[`control-${item.prop}`, 'control-col']"
        >
          <avue-form
            v-if="Object.keys(item.column).length || item.group?.length"
            :ref="(el) => (avueFormRef[key] = el)"
            v-model="gridData"
            :option="item"
            :upload-before="uploadBefore"
            :upload-exceed="uploadExceed"
            :upload-sized="uploadSized"
            :upload-preview="uploadPreview"
            class="[&_>_.el-form_>_.el-row]:ml-0! [&_>_.el-form_>_.el-row]:mr-0!"
          >
            <!-- 自定义表单 -->
            <template v-for="c in slotData[key].form" :key="c.prop" #[c.prop]="scope">
              <AvueSlot
                :ref="(el) => (formSlotRef[`${key}_${c.prop}`] = el)"
                slotType="form"
                :scope="scope"
                :control="c"
                :formType="formType"
                v-model="gridData[c.prop]"
                @set-form-data="(prop, val) => (gridData[prop] = val)"
                @change="() => avueFormRef[key].validateField(c.prop)"
              ></AvueSlot>
            </template>
            <!-- 表格布局 -->
            <template
              v-for="c in controlObj['layoutGrid_' + props.prop + '_' + key]?.layoutTable"
              :key="c.prop"
              #[c.prop]="scope"
            >
              <FormTable
                :ref="(el) => (formTableRef[c.prop] = el)"
                v-model="gridData[c.prop]"
                v-bind="{
                  column: scope.column,
                  disabled: scope.disabled,
                  size: scope.size,
                  prop: c.prop,
                  formType
                }"
              ></FormTable>
            </template>
          </avue-form>
        </el-col>
      </template>
    </el-row>
    <DesignPopup
      v-if="isMonacoEditor"
      v-model="MEDialog.value"
      v-bind="MEDialog.params"
      :isBodyFull="true"
    >
      <template #default>
        <MonacoEditor v-model="MEData.value" v-bind="MEData.params"></MonacoEditor>
      </template>
    </DesignPopup>
  </div>
</template>

<script setup lang="ts">
import useMEDialog from '@/hooks/design/useMEDialog'
import useAvueUpload from '@/hooks/design/useAvueUpload'
import { lowFormKey, LowFormType } from '@/utils/symbols'
import { cloneDeep } from 'lodash-es'

defineOptions({ name: 'FormGrid' })

interface Props {
  prop: string
  disabled: boolean
  size: string
  formType: string
  modelValue: object
  column: any
}
const props = withDefaults(defineProps<Props>(), {
  modelValue: () => {
    return {}
  }
})
const {
  controlObj,
  rulesObj,
  useFun,
  useImport,
  enhanceData,
  message,
  formData,
  viewLoading,
  rendControlData,
  unifyFormOption,
  Vue,
  route,
  routes,
  getComponent,
  enhanceErrorTip,
  handleMonacoEditorClick
} = inject<LowFormType>(lowFormKey) as LowFormType

const avueFormRef = ref<any>({})
const formSlotRef = ref({})
const formTableRef = ref({})
const gridOption = ref({}) as any
const gridData = ref(props.modelValue || {})
const isInit = ref(false)
const componentObj = ref({})

const { MEDialog, MEData, openMEDialog } = useMEDialog()
const { uploadBefore, uploadExceed, uploadSized, uploadPreview } = useAvueUpload()

const emit = defineEmits(['update:modelValue'])

const parentKey = computed(() => {
  return 'layoutGrid_' + props.prop
})

const isMonacoEditor = computed(() => {
  let bool = false
  for (let key in controlObj.value) {
    if (bool) break
    if (key.indexOf(`layoutGrid_${props.prop}_`) === 0) {
      if (controlObj.value[key].monacoEditor) bool = true
    }
  }
  return bool
})

const slotData = computed(() => {
  const slotObj = {}
  for (let colKey in gridOption.value.column) {
    slotObj[colKey] = { list: [] as any[], form: [] as any[] }
    const controlData = controlObj.value[`${parentKey.value}_${colKey}`]
    for (let key in controlData) {
      controlData[key].forEach((item) => {
        const { slotList } = item
        if (!slotList.length) return
        const obj: any = { controlType: key }
        if (key == 'monacoEditor') {
          obj.click = (scope) => {
            let params = scope.column.params
            params = handleMonacoEditorClick(params, executeStrFunction)
            openMEDialog(
              Object.assign(scope.column, { disabled: scope.disabled }),
              gridData.value[colKey]
            )
          }
        }
        if (key == 'customControl') obj.getComponent = (key) => componentObj.value[key]
        else if (['buttonList', 'comboBox'].includes(key)) {
          obj.click = (clickStr, obj, errorTip) => {
            executeStrFunction(clickStr, [obj], errorTip)
          }
        }
        slotList.forEach((type) => {
          if (slotObj[colKey][type]) slotObj[colKey][type].push({ ...item, ...obj })
        })
      })
    }
  }

  return slotObj
})

watch(
  () => props.modelValue,
  (val) => {
    gridData.value = val
  }
)
watch(
  () => gridData.value,
  (val) => {
    emit('update:modelValue', val)
  }
)

const initFun = () => {
  componentObj.value = getComponent().value
  gridOption.value = { ...props.column }
  for (let key in gridOption.value.column) {
    gridOption.value.column[key] = {
      ...gridOption.value.column[key],
      ...unifyFormOption.value,
      menuBtn: false,
      size: props.size
    }
    if (!gridData.value[key]) gridData.value[key] = {}
  }
  initRule()
  isInit.value = true
}

const initRule = () => {
  const setRule = (column, ruleKey, colKey) => {
    const colRuleObj = rulesObj.value[ruleKey]
    for (const prop in column) {
      if (column[prop].type == 'comboBox') {
        column[prop].rules = [
          {
            validator: (rule, value, callback) => {
              formSlotRef.value[`${colKey}_${prop}`]?.slotRef
                ?.validate()
                .then((bool) => (bool ? callback() : callback(new Error(''))))
            },
            trigger: 'change'
          }
        ]
        setRule(column[prop].column, `${column[prop].type}_${prop}`, colKey)
      }
    }
    for (const prop in colRuleObj) {
      colRuleObj[prop].forEach(({ index, rule, ruleStr, type }) => {
        if (column[prop] && type == 'customRule') {
          const ruleData = cloneDeep(rule)
          const obj = executeStrFunction(`()=>{${ruleStr}}`)
          if (obj instanceof Object) for (const k in obj) ruleData[k] = obj[k]
          column[prop].rules.splice(index, 0, ruleData)
        }
      })
    }
  }
  for (let colKey in gridOption.value.column) {
    const colData = gridOption.value.column[colKey]
    if (Object.keys(colData.column || {}).length)
      setRule(colData.column, `${parentKey.value}_${colKey}`, colKey)
    if (colData.group?.length) {
      colData.group = colData.group.map((item) => {
        if (Object.keys(item.column || {}).length)
          setRule(item.column, `${parentKey.value}_${colKey}`, colKey)
        return item
      })
    }
  }
}

const verifyForm = () => {
  let errorObj = {
    dom: null as Element | null,
    valid: true,
    colKey: '',
    prop: '',
    errorMsg: ''
  }
  const setError = (obj) => {
    if (errorObj.valid) errorObj = { ...errorObj, ...obj }
  }
  return new Promise(async (resolve) => {
    let promiseArr = [] as any
    for (let key in avueFormRef.value) {
      promiseArr.push(
        new Promise((subResolve) => {
          avueFormRef.value[key]?.validate(async (valid, hide, msg) => {
            hide()
            if (!valid && errorObj.valid) {
              const oneKey = Object.keys(msg)[0]
              const gridDom = document.querySelector(`.control-${props.prop}`)
              const colDom = gridDom ? gridDom.querySelector(`.form-grid  .control-${key}`) : null
              if (colDom) errorObj.dom = colDom.querySelector(`.control-${oneKey}`)
              setError({ valid: false, colKey: key, prop: oneKey, errorMsg: msg[oneKey].message })
            }
            await handleLayoutVerify(key, setError)
            subResolve(true)
          })
        })
      )
    }
    await Promise.all(promiseArr)
    resolve(errorObj)
  })
}

const handleLayoutVerify = (tabProp, setError) => {
  let errorObj = {} as any
  const layoutKeys = ['layoutTable']
  const refObj = { layoutTable: formTableRef.value }
  return new Promise(async (resolve) => {
    let controlData = controlObj.value[`${props.column.type}_${props.prop}_${tabProp}`]
    let promiseArr = [] as any
    layoutKeys.forEach((layoutKey) => {
      if (controlData && controlData[layoutKey]) {
        controlData[layoutKey]?.forEach(({ prop }) => {
          if (refObj[layoutKey][prop]) {
            promiseArr.push(
              new Promise((subResolve) => {
                refObj[layoutKey][prop].verifyForm().then((res) => {
                  if (!res.valid && errorObj.valid !== false) {
                    errorObj = {
                      valid: false,
                      dom: res.dom,
                      tabKey: tabProp,
                      prop: res.prop,
                      errorMsg: res.errorMsg
                    }
                  }
                  subResolve(true)
                })
              })
            )
          }
        })
      }
    })
    if (promiseArr.length) await Promise.all(promiseArr)
    setError(errorObj)
    resolve(errorObj)
  })
}

const clearValue = () => {
  for (let key in formTableRef.value) {
    formTableRef.value[key].clearValue()
  }
  for (let key in avueFormRef.value) {
    avueFormRef.value[key].resetForm()
  }
}

const executeStrFunction = (str: string, params?: Array<any>, errTip?: string) => {
  try {
    const fun = eval(`(function getFun(){return ${str}})()`)
    if (params) return fun.apply(null, params)
    else return fun()
  } catch (error) {
    enhanceErrorTip(errTip || '', error)
  }
}

onMounted(() => {
  initFun()
})

defineExpose({
  prop: props.prop,
  type: props.column.type,
  gridOption,
  gridData,
  avueFormRef,
  formTableRef,
  formSlotRef,
  verifyForm,
  clearValue
})
</script>

<style lang="scss" scoped></style>
