import { computed, defineComponent, inject, onMounted, reactive, ref, watch } from "vue"
import { MP_UPLOADER, MP_FORM } from "../provider/MpProvider.jsx"
import { cloneDeep, every, isEqual, isFunction, isObject, isString, pick } from "lodash-es"
import { initItemDefaultValue } from "./utils"
import { useFetch, useFormFail, useFormFormat, useProcessStatusSuccess } from "../../hooks"
import createFormItem from "./FormItem.jsx"
import MpButton from "../button/MpButton.jsx"
import MpSkeleton from "../skeleton/MpSkeleton.jsx"
import Taro from "@tarojs/taro"

/**
 * MpForm 表单
 *
 * @version 1.0.0
 */
export default defineComponent({
	name: "MpForm",
	props: {
		/**
		 * 表单标题
		 */
		title: { type: String, default: "" },

		/**
		 * 表单项 label 宽度，默认单位为px
		 */
		labelWidth: { type: [String, Number], default: "6.2em" },

		/**
		 * 是否在 label 后面添加冒号
		 */
		colon: { type: Boolean, default: false },

		/**
		 * 表单数据，用于初始化表单，并会进行 Watch
		 */
		data: { type: [Object, String], default: "" },

		/**
		 * 是否自动加载
		 * true: 表示自动加载数据
		 * Array,String: 表示会对 `extraData` 数据中的相关字段进行非空验证，不为空再加载数据
		 */
		autoLoad: {
			//自动加载数据,在fetchData里找
			type: [Boolean, Array, String],
			default: true,
		},

		/**
		 * 获取表单数据的URL
		 */
		fetchUrl: { type: String, default: "" },

		/**
		 * 额外的数据，在提交时会合并到表单数据中并一起提交
		 */
		extraData: { type: Object, default: () => ({}) },

		/**
		 * 是否禁用表单
		 */
		disabled: { type: Boolean, default: false },

		/**
		 * 是否只读
		 */
		readonly: { type: Boolean, default: false },

		/**
		 * submit button 是否固定在底部
		 */
		fixed: { type: Boolean, default: false },

		/**
		 * 提交数据URL
		 */
		submitUrl: { type: String, default: "" },

		/**
		 * 提交按钮文字
		 */
		submitButtonText: { type: String, default: "保存" },

		/**
		 * 提交确认提示内容
		 */
		submitConfirmText: { type: String, default: "" },

		/**
		 * 是否禁用提交按钮
		 */
		submitDisabled: { type: Boolean, default: false },

		/**
		 * 当有分割线时，分割线的配置
		 */
		dividerProps: { type: Object, default: () => ({}) },

		/**
		 *
		 * 表单配置
		 *
		 * @typedef {Object} FormItemConfig
		 * @property {string} key 数据库关联名称
		 * @property {string} title 显示的名字
		 * @property {string} [type] 类型,默认是input
		 * @property {array|Function} [options] 组件选项
		 * @property {string} [placeholder] 组件里的提示
		 * @property {string|Function} [help] MpField 里的提示
		 * @property {array} [rules] 验证规则
		 * @property {boolean} [is-link] 是否展示右侧箭头并开启点击反馈
		 * @property {boolean} [readonly] 是否只读
		 * @property {boolean|Function} [required] 是否必填,默认是false
		 * @property {boolean|Function} [disabled] 组件不可编辑状态,默认是false
		 * @property {boolean|Function} [hidden] 组件是否隐藏
		 * @property {Function} [match] 支持根据条件返回不同的配置进行动态渲染
		 * @property {Function} [init] 初始化函数，用于初始化表单项的值
		 * @property {Function} [beforeSubmit] 在提交前修改表单项的值，该函数会在 MpForm 的 beforeSubmit 之前调用
		 * @property {boolean|string} [break] 新起一行，默认为false，如果为 String 则以 Divider 分割
		 * @property {Object} [fieldProps] MpField 的原生配置
		 * @property {Object} [defaultProps] 组件的配置
		 * @property {Object} [defaultSlots] 混合 Field 和 input slot 组件的 slots 组合
		 * @property {*} [defaultValue] 默认值，默认是空字符串
		 * @property {*} [_temp] 临时数据，内部使用
		 */

		/**
		 * 表单配置，[见下表](#form-表单配置)
		 */
		form: {
			type: Array,
			default() {
				return []
			},
		},

		/**
		 * fetch 返回数据处理函数
		 * @return {Object} 返回处理后的数据，将用于初始化表单
		 */
		afterFetched: { type: Function, default: null },

		/**
		 *
		 * @typedef {Object} MpposedFormData
		 * @property {Object} formatForm Format后的表单数据
		 * @property {Object} originalForm 原生的表单数据
		 *
		 *
		 * 提交数据处理函数
		 * @param {MpposedFormData} data
		 * @return {Boolean|Object} return false会阻止提交操作，return Object会替换提交的数据
		 *
		 */
		beforeSubmit: { type: Function, default: null },

		/**
		 * 提交成功后的回调
		 */
		afterSubmit: { type: Function, default: null },

		/**
		 * [原生配置](https://nutui.jd.com/taro/vue/4x/#/zh-CN/component/form)
		 */
		formProps: { type: Object, default: () => ({}) },
	},
	emits: ["success"],

	setup(props, { expose, emit, slots }) {
		const formRef = ref(null) //表单容器

		const state = reactive({
			temporary: {}, // 用于存放一些临时数据
			submitFetcher: {
				loading: false,
			},
			isInitializing: true, //是否正在初始化
			rules: {},
			submitForm: {}, //提交表单，初始化数据后会生成
			submitFormBackup: {}, //初始化后的表单数据备份，用于重置表单以及脏数据判断
		})

		const formItems = computed(() => props.form)

		const uploaderProvider = inject(MP_UPLOADER, () => ({}))
		const formProvider = inject(MP_FORM, () => ({}))

		watch(
			() => props.data,
			(newV) => {
				initFormData(newV || "")
			},
		)

		const init = () => {
			state.isInitializing = false
			initFormData(props.data || "")
		}

		onMounted(() => {
			init()
		})

		/**
		 * 初始化表单数据
		 *
		 * @param {Object} formData 表单数据
		 */
		const initFormData = (formData) => {
			// 如果有 FormData, 则从中提取 FormItem 中有定义的数据，并进行初始化后存放于 extractFormData
			// 无 FromData 则直接使用 FormItem 的 defaultValue
			let extractFormData = {}
			let existingData = formData ? cloneDeep(formData) : false
			formItems.value.forEach((item) => {
				extractFormData[item.key] = initItemDefaultValue(item, existingData, state.submitForm, { uploaderProvider })
			})

			if (existingData) {
				// 将初始化后的数据覆盖原有数据，并保留不在 FormItems 中的数据
				extractFormData = { ...existingData, ...extractFormData }
			}

			state.submitForm = extractFormData
			state.submitFormBackup = cloneDeep(extractFormData)
		}

		//远程拿数据模式
		const fetchItem = () => {
			if (props.fetchUrl) {
				state.isInitializing = true
				useFetch()
					.get(props.fetchUrl, { params: props.extraData })
					.then((res) => {
						state.isInitializing = false
						useProcessStatusSuccess(res, () => {
							if (props.afterFetched && isFunction(props.afterFetched)) {
								res = props.afterFetched(res)
							} else if (formProvider.afterFetched && isFunction(formProvider.afterFetched)) {
								res = formProvider.afterFetched(res)
							}
							initFormData(res)
						})
					})
					.finally(() => {
						state.isInitializing = false
					})
			}
		}

		if (props.autoLoad) {
			let auto = true
			if (props.autoLoad && isObject(props.autoLoad)) {
				auto = every(Object.values(pick(props.extraData, Object.keys(props.autoLoad))))
			} else if (props.autoLoad && isString(props.autoLoad)) {
				auto = !!props.extraData[props.autoLoad]
			}

			if (auto) {
				fetchItem()
			}
		}

		const showConfirmDialog = ({ content }) => {
			return new Promise((resolve, reject) => {
				Taro.showModal({
					content,
					success(res) {
						if (res.confirm) {
							resolve()
						} else {
							reject()
						}
					},
				})
			})
		}

		const onSubmit = async () => {
			await formRef.value.validate()

			if (props.submitConfirmText) {
				try {
					await showConfirmDialog({ content: props.submitConfirmText })
				} catch (e) {
					return
				}
			}

			// 为了在 item 中也能定制 beforeSubmit 这里和下面的 useFormFormat 会重复 copy 一次 submitForm
			let form = cloneDeep(state.submitForm)

			formItems.value
				.filter((item) => item.beforeSubmit && isFunction(item.beforeSubmit))
				.forEach((item) => {
					form[item.key] = item.beforeSubmit({
						value: form[item.key],
						submitForm: state.submitForm,
					})
				})

			form = useFormFormat(form, formProvider.format || {})

			if (props.beforeSubmit && isFunction(props.beforeSubmit)) {
				form = await props.beforeSubmit({ formatForm: form, originalForm: state.submitForm })
				if (form === false) {
					return
				}
			}

			try {
				let res = await useFetch(state.submitFetcher).post(props.submitUrl, form)

				//提交后再次备份表单数据，isDirty 检测即为 false
				state.submitFormBackup = cloneDeep(state.submitForm)

				if (props.afterSubmit) {
					props.afterSubmit(res)
				} else {
					useProcessStatusSuccess(res, () => {
						Taro.showToast({
							icon: "success",
							title: `${props.submitButtonText}成功`,
						})
						emit("success", res)
					})
				}
			} catch (e) {
				useFormFail(e)
			}
		}

		/********** exposes **********/

		/**
		 * 获取复制的表单数据
		 * @return {*}
		 */
		const getFormStandalone = () => cloneDeep(state.submitForm)

		/**
		 * 获取表单实时数据
		 * @return {*}
		 */
		const getForm = () => state.submitForm

		/**
		 *
		 * 设置表单数据
		 * @param {Object} fields
		 */
		const setForm = (fields) => {
			Object.keys(fields).forEach((key) => {
				state.submitForm[key] = fields[key]
			})
		}

		/**
		 * 判断表单是否被修改
		 * @return {boolean}
		 */
		const isDirty = () => {
			return !isEqual(state.submitForm, state.submitFormBackup)
		}

		expose({ getForm, getFormStandalone, setForm, isDirty })

		/********** render **********/

		const formItemElems = () =>
			formItems.value.map((formItem) =>
				createFormItem(formItem, state.submitForm, {
					props,
					slots,
				}),
			)

		const footerElem = () => {
			if (slots.footer) {
				return <div class={"mp-form__footer"}>{slots.footer()}</div>
			}
			return null
		}

		const submitBtnElem = () => {
			if (props.readonly) {
				return null
			}

			const submitBtn = (
				<MpButton disabled={props.submitDisabled} type={"primary"} fetcher={state.submitFetcher} buttonProps={{ nativeType: "submit" }}>
					{() => props.submitButtonText || "提交"}
				</MpButton>
			)

			if (props.fixed) {
				return <div class={"mp-form__submit-btn-fixed van-hairline--top"}>{submitBtn}</div>
			}

			return <div class={"mp-form__submit-btn"}>{submitBtn}</div>
		}

		return () => (
			<Form
				ref={formRef}
				colon={props.colon}
				class={`mp-form ${props.fixed ? "mp-form__fixed" : ""}`}
				disabled={props.disabled}
				readonly={props.readonly}
				onSubmit={onSubmit}
			>
				{() => [
					<nut-cell-group title={props.title}>{state.isInitializing ? <MpSkeleton /> : formItemElems()}</nut-cell-group>,
					footerElem(),
					submitBtnElem(),
				]}
			</Form>
		)
	},
})
