import type { Arrayable } from 'element-plus/es/utils'
import {
  ElButton,
  ElForm,
  ElLoading,
  ElMessage,
  type FormInstance,
  type FormItemRule,
} from 'element-plus'
import {
  defineComponent,
  withDirectives,
} from 'vue'
import type { SetupContext } from '@vue/runtime-core'
import ADialog from '@/components/a-dialog/ADialog.vue'
import Utils from '@/common/utils'
import FormUtil from '@/common/utils/formUtil.ts'
import styles from '@/components/a-form-panel/a-form-panel.module.styl'

type RuleType<T extends object> = Partial<Record<keyof T | string, Arrayable<FormItemRule>>>

export interface FormPanelType<T extends object> {
  title: string
  defaultFormData: T
  detailsLoader: (id?: string) => Promise<T | undefined | null | void>
  doSubmit: (data: T) => Promise<boolean | undefined | null | void>
  rules: RuleType<T> | ((formData: T) => RuleType<T>)
  watchForm: {
    formItems: string[] | '_all_' | '_none_'
    callback?: (formData: T) => void
  }
  labelWidth: string
  width: string
  modal: boolean
  appendToBody: boolean
}

const component = defineComponent(
  <T extends object>(props: FormPanelType<T>, {slots, expose}: SetupContext) => {
    const formIns = useTemplateRef<FormInstance>('formRef')
    const showDialog = ref(false)
    const loading = ref(false)

    const formData = Utils.resetAble(reactive<T>(props.defaultFormData))

    function dialogCloseHandler() {
      formData.$reset()
    }

    const submiting = ref(false)

    function submitHandler() {
      submiting.value = true
      FormUtil.submit(formIns, () => props.doSubmit(formData.$clone() as T))
        .then(res => {
          ElMessage.success('提交成功')
          if ((res ?? true)) {
            showDialog.value = false
          }
        })
        .finally(() => {
          submiting.value = false
        })
    }

    const open = (id?: string) => {
      loading.value = true
      props.detailsLoader(id)
        .then(res => {
          formData.$reset((res ?? {}) as any)
        })
        .finally(() => {
          loading.value = false
        })
      showDialog.value = true
    }
    expose({open})
    const formRules = ref(typeof props.rules === 'function' ? props.rules(formData.$clone() as T) : props.rules)
    if (props.watchForm.formItems === '_all_') {
      watch(formData,
        () => {
          if (typeof props.rules === 'function') {
            formRules.value = props.rules(formData.$clone() as T)
          }
          props.watchForm.callback?.(formData.$clone() as T)
        },
        {deep: true})
    } else if (Array.isArray(props.watchForm.formItems)) {
      props.watchForm.formItems.forEach(val => {
        watch(
          () => val.split('.').reduce((current, key) => {
            // @ts-ignore
            return current?.[key]
          }, formData as T),
          () => {
            if (typeof props.rules === 'function') {
              formRules.value = props.rules(formData.$clone() as T)
            }
            props.watchForm.callback?.(formData.$clone() as T)
          },
          {deep: true},
        )
      })
    }
    return () => (<ADialog
      show={showDialog.value}
      onUpdate:show={val => showDialog.value = val}
      closed={dialogCloseHandler}
      submit-handler={submitHandler}
      title={props.title}
      width={props.width}
      append-to-body={props.appendToBody}
      modal={props.modal}
    >
      {{
        default: () => withDirectives(<ElForm
            ref="formRef"
            label-width={props.labelWidth}
            model={formData}
            rules={formRules.value}
            class={styles.formPanel}>
            {
              slots.default?.(formData as T)
            }
          </ElForm>,
          [ [ ElLoading.directive, loading.value ] ]),
        footer: () => (
          <>
            <ElButton onClick={() => showDialog.value = false}>关闭</ElButton>
            <ElButton loading={submiting.value} type="primary" onClick={submitHandler}>提交</ElButton>
          </>
        ),
      }}
    </ADialog>)
  },
  {
    name: 'AFormPanel',
    props: [ 'title', 'defaultFormData', 'detailsLoader', 'doSubmit', 'rules', 'watchForm', 'labelWidth', 'width', 'modal', 'appendToBody' ],
  })

export interface AFormPanelInstance extends InstanceType<typeof component> {
  open: (id?: string) => void
}


export function buildFormPanelProps<T extends object>(props: Partial<Exclude<FormPanelType<T>, 'title' | 'detailsLoader' | 'doSubmit' | 'watchForm'>> & Pick<FormPanelType<T>, 'detailsLoader' | 'doSubmit'> & Partial<Pick<FormPanelType<T>, 'watchForm'>>) {
  if (props.labelWidth == null) {
    props.labelWidth = '90px'
  }
  if (props.appendToBody == null) {
    props.appendToBody = false
  }
  if (props.width == null) {
    props.width = 'fit-content'
  }
  if (props.modal == null) {
    props.modal = false
  }
  if (props.rules == null) {
    props.rules = {} as RuleType<T>
  }
  if (props.watchForm == null) {
    props.watchForm = {
      formItems: '_none_',
    }
  }
  if (props.watchForm.formItems == null) {
    props.watchForm.formItems = '_none_'
  }

  if (props.defaultFormData == null) {
    props.defaultFormData = {} as T
  }

  return reactive({
    title: props.title!,
    detailsLoader: props.detailsLoader!,
    doSubmit: props.doSubmit!,
    rules: props.rules,
    watchForm: props.watchForm!,
    labelWidth: props.labelWidth!,
    width: props.width!,
    modal: props.modal!,
    defaultFormData: props.defaultFormData!,
    appendToBody: props.appendToBody!,
  } as FormPanelType<T>)
}

export default component
