import { Form, Modal, FormInstance, ColProps, ModalProps } from 'antd'
import { FormLayout } from 'antd/lib/form/Form'
import { FormLabelAlign, Store } from 'antd/lib/form/interface'
import React, { useState, useEffect, useRef } from 'react'

import { usePrevious, useDeepCompareEffect, isDeepEqual } from 'utils/hooks'

import MessageBar from '../MessageBar'

const defaultLayout = {
  labelCol: { span: 6 },
  wrapperCol: { span: 18 }
}

interface ModalFormProps<FormValues = object> extends ModalProps {
  title: string // modal title
  visible: boolean // visible of modal
  name: string // form name
  okText?: string
  cancelText?: string
  initValues?: FormValues
  getFields: (form?: FormInstance) => React.ReactNode
  onSubmit: (payload: FormValues, form: FormInstance) => Promise<void> | void
  onCancel: () => void
  onFieldsChange?: (changedFields: any[], allFields: any[]) => void
  layout?: {
    labelCol?: ColProps
    wrapperCol?: ColProps
  }
  labelAlign?: FormLabelAlign
  formLayout?: FormLayout
  width?: number
  destoryOnClose?: boolean
  maskClosable?: boolean
  form?: FormInstance<FormValues>
}

// reset form fields when modal is form, closed
function useResetFormOnCloseModal({
  reset,
  visible
}: {
  reset: () => void
  visible: boolean
}) {
  const prevVisibleRef = useRef<boolean>()

  useEffect(() => {
    prevVisibleRef.current = visible
  }, [visible])

  const prevVisible = prevVisibleRef.current

  useEffect(() => {
    if (!visible && prevVisible) {
      reset()
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [visible])
}

function ModalForm<FormValues = object>({
  title,
  visible,
  name,
  okText = 'Save',
  cancelText = 'Cancel',
  initValues,
  getFields,
  onSubmit,
  onCancel,
  layout = defaultLayout,
  labelAlign = 'right',
  formLayout,
  width,
  destoryOnClose,
  onFieldsChange,
  maskClosable,
  form: controlledForm,
  ...rest
}: ModalFormProps<FormValues>) {
  const [_form] = Form.useForm<FormValues>()

  const [saving, setSaving] = useState(false)
  const [errors, setErrors] = useState<string[] | null>(null)

  const preInitValues = usePrevious(initValues)

  const form = controlledForm || _form

  useDeepCompareEffect(() => {
    if (!preInitValues || isDeepEqual(preInitValues, initValues)) {
      return () => undefined
    }

    // update initValues
    if (initValues) {
      form.setFieldsValue(initValues)
    }
  }, [initValues])

  const reset = () => {
    form.resetFields()
    setErrors(null)
  }

  useResetFormOnCloseModal({ reset, visible })

  const handleOK = () => {
    form.submit()
  }

  const handleFinish = async () => {
    try {
      const values = await form.validateFields()
      setSaving(true)

      try {
        await onSubmit(values as FormValues, form)
      } catch (e) {
        console.error(e)
        setErrors([
          e?.response?.data?.message ||
            e?.response?.data?.errmsg ||
            e?.response?.data?.error ||
            e?.message ||
            JSON.stringify(e)
        ])
      }

      setSaving(false)
    } catch (err) {
      console.warn('Validate failed:', err)
    }
  }

  return (
    <Modal
      visible={visible}
      title={title}
      okText={okText}
      cancelText={cancelText}
      confirmLoading={saving}
      onCancel={onCancel}
      onOk={handleOK}
      centered
      width={width}
      destroyOnClose={destoryOnClose}
      maskClosable={maskClosable}
      {...rest}
    >
      <Form
        {...layout}
        layout={formLayout}
        form={form}
        name={`${name}_in_modal`}
        initialValues={initValues as Store}
        onFinish={handleFinish}
        labelAlign={labelAlign}
        onFieldsChange={onFieldsChange}
      >
        {getFields(form)}
      </Form>

      {errors && <MessageBar type="error" messages={errors} />}
    </Modal>
  )
}

// pure component
export default React.memo(ModalForm) as typeof ModalForm
