<template>
	<view class="uv-form">
		<slot />
	</view>
</template>

<script>
	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
	import props from "./props.js";
	import Schema from "./valid.js";
	// 去除警告信息
	Schema.warning = function() {};
	/**
	 * Form 表單
	 * @description 此組件壹般用於表單場景，可以配置Input輸入框，Select彈出框，進行表單驗證等。
	 * @tutorial https://www.uvui.cn/components/form.html
	 * @property {Object}						model			當前form的需要驗證字段的集合
	 * @property {Object | Function | Array}	rules			驗證規則
	 * @property {String}						errorType		錯誤的提示方式，見上方說明 ( 默認 message )
	 * @property {Boolean}						borderBottom	是否顯示表單域的下劃線邊框   ( 默認 true ）
	 * @property {String}						labelPosition	表單域提示文字的位置，left-左側，top-上方 ( 默認 'left' ）
	 * @property {String | Number}				labelWidth		提示文字的寬度，單位px  ( 默認 45 ）
	 * @property {String}						labelAlign		lable字體的對齊方式   ( 默認 ‘left' ）
	 * @property {Object}						labelStyle		lable的樣式，對象形式
	 * @example <uv-form labelPosition="left" :model="model1" :rules="rules" ref="form1"></uv-form>
	 */
	export default {
		name: "uv-form",
		mixins: [mpMixin, mixin, props],
		provide() {
			return {
				uForm: this,
			};
		},
		data() {
			return {
				formRules: {},
				// 規則校驗器
				validator: {},
				// 原始的model快照，用於resetFields方法重置表單時使用
				originalModel: null,
			};
		},
		watch: {
			// 監聽規則的變化
			rules: {
				immediate: true,
				handler(n) {
					this.setRules(n);
				},
			},
			// 監聽屬性的變化，通知子組件uv-form-item重新獲取信息
			propsChange(n) {
				if (this.children?.length) {
					this.children.map((child) => {
						// 判斷子組件(uv-form-item)如果有updateParentData方法的話，就就執行(執行的結果是子組件重新從父組件拉取了最新的值)
						typeof child.updateParentData == "function" &&
							child.updateParentData();
					});
				}
			},
			// 監聽model的初始值作為重置表單的快照
			model: {
				immediate: true,
				handler(n) {
					if (!this.originalModel) {
						this.originalModel = this.$uv.deepClone(n);
					}
				},
			},
		},
		computed: {
			propsChange() {
				return [
					this.errorType,
					this.borderBottom,
					this.labelPosition,
					this.labelWidth,
					this.labelAlign,
					this.labelStyle,
				];
			},
		},
		created() {
			// 存儲當前form下的所有uv-form-item的實例
			// 不能定義在data中，否則微信小程序會造成循環引用而報錯
			this.children = [];
		},
		methods: {
			// 手動設置校驗的規則，如果規則中有函數的話，微信小程序中會過濾掉，所以只能手動調用設置規則
			setRules(rules) {
				// 判斷是否有規則
				if (Object.keys(rules).length === 0) return;
				if (process.env.NODE_ENV === 'development' && Object.keys(this.model).length === 0) {
					this.$uv.error('設置rules，model必須設置！如果已經設置，請刷新頁面。');
					return;
				};
				this.formRules = rules;
				// 重新將規則賦予Validator
				this.validator = new Schema(rules);
			},
			// 清空所有uv-form-item組件的內容，本質上是調用了uv-form-item組件中的resetField()方法
			resetFields() {
				this.resetModel();
			},
			// 重置model為初始值的快照
			resetModel(obj) {
				// 歷遍所有uv-form-item，根據其prop屬性，還原model的原始快照
				this.children.map((child) => {
					const prop = child?.prop;
					const value = this.$uv.getProperty(this.originalModel, prop);
					this.$uv.setProperty(this.model, prop, value);
				});
			},
			// 清空校驗結果
			clearValidate(props) {
				props = [].concat(props);
				this.children.map((child) => {
					// 如果uv-form-item的prop在props數組中，則清除對應的校驗結果信息
					if (props[0] === undefined || props.includes(child.prop)) {
						child.message = null;
					}
				});
			},
			// 對部分表單字段進行校驗
			async validateField(value, callback, event = null) {
				// $nextTick是必須的，否則model的變更，可能會延後於此方法的執行
				this.$nextTick(() => {
					// 校驗錯誤信息，返回給回調方法，用於存放所有form-item的錯誤信息
					const errorsRes = [];
					// 如果為字符串，轉為數組
					value = [].concat(value);
					// 歷遍children所有子form-item
					this.children.map((child) => {
						// 用於存放form-item的錯誤信息
						const childErrors = [];
						if (value.includes(child.prop)) {
							// 獲取對應的屬性，通過類似'a.b.c'的形式
							const propertyVal = this.$uv.getProperty(
								this.model,
								child.prop
							);
							// 屬性鏈數組
							const propertyChain = child.prop.split(".");
							const propertyName =
								propertyChain[propertyChain.length - 1];

							const rule = this.formRules[child.prop];
							// 如果不存在對應的規則，直接返回，否則校驗器會報錯
							if (!rule) return;
							// rule規則可為數組形式，也可為對象形式，此處拼接成為數組
							const rules = [].concat(rule);

							// 對rules數組進行校驗
							for (let i = 0; i < rules.length; i++) {
								const ruleItem = rules[i];
								// 將uv-form-item的觸發器轉為數組形式
								const trigger = [].concat(ruleItem?.trigger);
								// 如果是有傳入觸發事件，但是此form-item卻沒有配置此觸發器的話，不執行校驗操作
								if (event && !trigger.includes(event)) continue;
								// 實例化校驗對象，傳入構造規則
								const validator = new Schema({
									[propertyName]: ruleItem,
								});
								validator.validate({
										[propertyName]: propertyVal,
									},
									(errors, fields) => {
										if (this.$uv.test.array(errors)) {
											errorsRes.push(...errors);
											childErrors.push(...errors);
										}
										this.$nextTick(() => {
											child.message = childErrors[0]?.message ? childErrors[0]?.message : null;
										})
									}
								);
							}
						}
					});
					// 執行回調函數
					typeof callback === "function" && callback(errorsRes);
				});
			},
			// 校驗全部數據
			validate(callback) {
				return new Promise((resolve, reject) => {
					// $nextTick是必須的，否則model的變更，可能會延後於validate方法
					this.$nextTick(() => {
						// 獲取所有form-item的prop，交給validateField方法進行校驗
						const formItemProps = this.children.map(
							(item) => item.prop
						);
						this.validateField(formItemProps, (errors) => {
							if (errors.length) {
								// 如果錯誤提示方式為toast，則進行提示
								this.errorType === 'toast' && this.$uv.toast(errors[0].message)
								reject(errors)
							} else {
								resolve(true)
							}
						});
					});
				});
			},
		},
	};
</script>