<template>
	<ElContainer class="_fc-designer" :style="'height:' + dragHeight">
		<ElMain>
			<ElContainer style="height: 100%">
				<el-aside class="_fc-l" width="266px">
					<template v-for="(item, index) in menuList" :key="index">
						<div class="_fc-l-group">
							<h4 class="_fc-l-title">{{ item.title }}</h4>
							<draggable :group="{ name: 'default', pull: 'clone', put: false }" :sort="false" item-key="name" :list="item.list">
								<template #item="{ element }">
									<div class="_fc-l-item">
										<div class="_fc-l-icon">
											<i class="fc-icon" :class="element.icon || 'icon-input'"></i>
										</div>
										<span class="_fc-l-name">{{ t('components.' + element.name + '.name') || element.label }}</span>
									</div>
								</template>
							</draggable>
						</div>
					</template>
				</el-aside>
				<ElContainer class="_fc-m">
					<el-header class="_fc-m-tools" height="45">
						<slot name="handle"></slot>
						<el-button size="small" @click="setJson">导入JSON</el-button>
						<el-button v-if="false" size="small" type="primary" @click="showJson">生成JSON</el-button>
						<el-button size="small" type="primary" @click="saveAsForm">暂存表单</el-button>
						<el-button type="primary" plain round size="small" @click="previewFc"
							><i class="fc-icon icon-preview"></i> {{ t('designer.preview') }}
						</el-button>
						<el-popconfirm
							:title="t('designer.clearConfirmTitle')"
							width="200px"
							:confirm-button-text="t('designer.clearConfirm')"
							:cancel-button-text="t('designer.clearCancel')"
							@confirm="clearDragRule"
						>
							<template #reference>
								<el-button type="danger" plain round size="small"><i class="fc-icon icon-delete"></i>{{ t('designer.clear') }} </el-button>
							</template>
						</el-popconfirm>
					</el-header>
					<ElMain style="background: #f5f5f5; padding: 20px">
						<div class="_fc-m-drag">
							<DragForm v-model:api="dragForm.api" :rule="dragForm.rule" :option="form.value"></DragForm>
						</div>
					</ElMain>
				</ElContainer>
				<ElAside v-if="!config || config.showConfig !== false" class="_fc-r" width="320px">
					<ElContainer style="height: 100%">
						<el-header height="40px" class="_fc-r-tabs">
							<div
								v-if="!!activeRule || (config && config.showFormConfig === false)"
								class="_fc-r-tab"
								:class="{ active: activeTab === 'props' }"
								@click="activeTab = 'props'"
							>
								{{ t('designer.config.component') }}
							</div>
							<div
								v-if="!config || config.showFormConfig !== false"
								class="_fc-r-tab"
								:class="{ active: activeTab === 'form' && !!activeRule }"
								@click="activeTab = 'form'"
							>
								{{ t('designer.config.form') }}
							</div>
						</el-header>
						<ElMain v-show="activeTab === 'form'" v-if="!config || config.showFormConfig !== false">
							<DragForm v-model="form.value.form" v-model:api="form.api" :rule="form.rule" :option="form.option"></DragForm>
						</ElMain>
						<ElMain v-show="activeTab === 'props'" :key="activeRule ? activeRule._id : ''" style="padding: 0 20px">
							<div>
								<ElDivider v-if="showBaseRule">{{ t('designer.config.rule') }}</ElDivider>
								<DragForm
									v-show="showBaseRule"
									v-model:api="baseForm.api"
									:rule="baseForm.rule"
									:option="baseForm.options"
									:model-value="baseForm.value"
									@change="baseChange"
								></DragForm>
								<ElDivider>{{ t('designer.config.props') }}</ElDivider>
								<DragForm
									v-model:api="propsForm.api"
									:rule="propsForm.rule"
									:option="propsForm.options"
									:model-value="propsForm.value"
									@change="propChange"
									@removeField="propRemoveField"
								></DragForm>
								<ElDivider v-if="showBaseRule">{{ t('designer.config.validate') }}</ElDivider>
								<DragForm
									v-show="showBaseRule"
									v-model:api="validateForm.api"
									:rule="validateForm.rule"
									:option="validateForm.options"
									:model-value="validateForm.value"
									@update:modelValue="validateChange"
								></DragForm>
							</div>
						</ElMain>
					</ElContainer>
				</ElAside>
				<!-- 预览表单 -->
				<ElDialog v-model="preview.state" width="800px" append-to-body>
					<ViewForm v-if="preview.state" :rule="preview.rule" :option="preview.option"></ViewForm>
				</ElDialog>
				<!-- 生成JSON / 导入JSON -->
				<el-dialog v-model="state" :title="title[type]" class="_fc-t-dialog">
					<div v-if="state" id="editor" ref="editorRef"></div>
					<span v-if="err" style="color: red">输入内容格式有误!</span>
					<template v-if="type > 2" #footer>
						<span slot="footer" class="dialog-footer">
							<el-button size="small" @click="state = false">取 消</el-button>
							<el-button type="primary" size="small" @click="onOk">确 定</el-button>
						</span>
					</template>
				</el-dialog>
			</ElContainer>
		</ElMain>
	</ElContainer>
</template>

<script>
// 代码样式
import 'codemirror/lib/codemirror.css'
import 'codemirror/addon/lint/lint.css'
import CodeMirror from 'codemirror/lib/codemirror'
import 'codemirror/addon/lint/lint'
import 'codemirror/addon/lint/json-lint'
import 'codemirror/mode/javascript/javascript'
import 'codemirror/mode/vue/vue'
import 'codemirror/mode/xml/xml'
import 'codemirror/mode/css/css'
import 'codemirror/addon/mode/overlay'
import 'codemirror/addon/mode/simple'
import 'codemirror/addon/selection/selection-pointer'
import 'codemirror/mode/handlebars/handlebars'
import 'codemirror/mode/htmlmixed/htmlmixed'
import 'codemirror/mode/pug/pug'
// 代码样式
import form from '../../config/base/form'
import field from '../../config/base/field'
import validate from '../../config/base/validate'
import { deepCopy } from '@form-create/utils/lib/deepextend'
import is, { hasProperty } from '@form-create/utils/lib/type'
import { lower } from '@form-create/utils/lib/tocase'
import ruleList from '../../config/rule'
import draggable from 'vuedraggable/src/vuedraggable'
import createMenu from '../../config/menu'
import { upper, useLocale } from '../../utils/formCreateIndex'
import { designerForm } from '../../utils/form'
import viewForm from '../../utils/form'
import { t as globalT } from '../../utils/locale'
import { computed, reactive, toRefs, toRef, ref, getCurrentInstance, provide, nextTick, watch, defineComponent, markRaw, defineExpose } from 'vue'

export default defineComponent({
	name: 'FcDesigner',
	components: {
		draggable,
		DragForm: designerForm.$form(),
		ViewForm: viewForm.$form()
	},
	props: ['menu', 'height', 'config', 'mask', 'locale'],
	setup(props) {
		const { menu, height, mask, locale } = toRefs(props)
		const vm = getCurrentInstance()
		provide('fcx', ref({ active: null }))
		provide('designer', vm)

		const config = toRef(props, 'config', {})
		const baseRule = toRef(config.value, 'baseRule', null)
		const componentRule = toRef(config.value, 'componentRule', {})
		const validateRule = toRef(config.value, 'validateRule', null)
		const formRule = toRef(config.value, 'formRule', null)
		const dragHeight = computed(() => {
			const h = height.value
			if (!h) return '100%'
			return is.Number(h) ? `${h}px` : h
		})
		let _t = globalT
		if (locale.value) {
			_t = useLocale(locale).t
		}
		const t = (...args) => _t(...args)

		const tidyRuleConfig = (orgRule, configRule, ...args) => {
			if (configRule) {
				if (is.Function(configRule)) {
					return configRule(...args)
				}
				if (configRule.rule) {
					let rule = configRule.rule(...args)
					if (configRule.append) {
						rule = [...rule, ...orgRule(...args)]
					}
					return rule
				}
			}
			return orgRule(...args)
		}
		const editorRef = ref()
		const data = reactive({
			cacheProps: {},
			moveRule: null,
			addRule: null,
			added: null,
			activeTab: 'form',
			activeRule: null,
			children: ref([]),
			menuList: menu.value || createMenu({ t }),
			showBaseRule: false,
			visible: {
				preview: false
			},
			t,
			preview: {
				state: false,
				rule: [],
				option: {}
			},
			dragForm: ref({
				rule: [],
				api: {}
			}),
			form: {
				rule: tidyRuleConfig(form, formRule.value, { t }),
				api: {},
				option: {
					form: {
						labelPosition: 'top',
						size: 'small'
					},
					submitBtn: false
				},
				value: {
					form: {
						inline: false,
						hideRequiredAsterisk: false,
						labelPosition: 'right',
						size: 'small',
						labelWidth: '125px',
						formCreateSubmitBtn: true,
						formCreateResetBtn: false
					},
					submitBtn: false
				}
			},
			baseForm: {
				rule: tidyRuleConfig(field, baseRule.value, { t }),
				api: {},
				value: {},
				options: {
					form: {
						labelPosition: 'top',
						size: 'small'
					},
					submitBtn: false,
					mounted: fapi => {
						fapi.activeRule = data.activeRule
						fapi.setValue(fapi.options.formData || {})
					}
				}
			},
			validateForm: {
				rule: tidyRuleConfig(validate, validateRule.value, { t }),
				api: {},
				value: [],
				options: {
					form: {
						labelPosition: 'top',
						size: 'small'
					},
					submitBtn: false,
					mounted: fapi => {
						fapi.activeRule = data.activeRule
						fapi.setValue(fapi.options.formData || {})
					}
				}
			},
			propsForm: {
				rule: [],
				api: {},
				value: {},
				options: {
					form: {
						labelPosition: 'top',
						size: 'small'
					},
					submitBtn: false,
					mounted: fapi => {
						fapi.activeRule = data.activeRule
						fapi.setValue(fapi.options.formData || {})
					}
				}
			},
			// 生成JSON / 导入JSON
			title: ['生成规则', '表单规则', '生成组件', '设置生成规则', '设置表单规则'],
			state: false,
			value: null,
			err: false,
			type: -1,
			editor: null
		})

		watch(
			() => data.preview.state,
			function (n) {
				if (!n) {
					nextTick(() => {
						data.preview.rule = data.preview.option = null
					})
				}
			}
		)

		watch(
			() => data.state,
			n => {
				if (!n) {
					data.value = null
					data.err = false
				}
			}
		)

		watch(
			() => data.value,
			n => {
				methods.load()
			}
		)

		let unWatchActiveRule = null

		watch(
			() => locale.value,
			n => {
				_t = n ? useLocale(locale).t : globalT
				const formVal = data.form.api.formData && data.form.api.formData()
				const baseFormVal = data.baseForm.api.formData && data.baseForm.api.formData()
				const validateFormVal = data.validateForm.api.formData && data.validateForm.api.formData()
				data.validateForm.rule = tidyRuleConfig(validate, validateRule.value, { t })
				data.baseForm.rule = tidyRuleConfig(field, baseRule.value, { t })
				data.form.rule = tidyRuleConfig(form, formRule.value, { t })
				data.cacheProps = {}
				const rule = data.activeRule
				let propsVal = null
				if (rule) {
					propsVal = data.propsForm.api.formData && data.propsForm.api.formData()
					data.propsForm.rule = data.cacheProps[rule._id] = tidyRuleConfig(
						rule.config.config.props,
						componentRule.value && componentRule.value[rule.config.config.name],
						rule,
						{ t, api: data.dragForm.api }
					)
				}
				nextTick(() => {
					formVal && data.form.api.setValue(formVal)
					baseFormVal && data.baseForm.api.setValue(baseFormVal)
					validateFormVal && data.validateForm.api.setValue(validateFormVal)
					propsVal && data.propsForm.api.setValue(propsVal)
				})
			}
		)

		const methods = {
			unWatchActiveRule() {
				unWatchActiveRule && unWatchActiveRule()
				unWatchActiveRule = null
			},
			watchActiveRule() {
				methods.unWatchActiveRule()
				unWatchActiveRule = watch(
					() => data.activeRule,
					function (n) {
						n && methods.updateRuleFormData()
					},
					{ deep: true, flush: 'post' }
				)
			},
			makeChildren(children) {
				return reactive({ children }).children
			},
			addMenu(config) {
				if (!config.name || !config.list) return
				let flag = true
				data.menuList.forEach((v, i) => {
					if (v.name === config.name) {
						data.menuList[i] = config
						flag = false
					}
				})
				if (flag) {
					data.menuList.push(config)
				}
			},
			removeMenu(name) {
				[...data.menuList].forEach((v, i) => {
					if (v.name === name) {
						data.menuList.splice(i, 1)
					}
				})
			},
			setMenuItem(name, list) {
				data.menuList.forEach(v => {
					if (v.name === name) {
						v.list = list
					}
				})
			},
			appendMenuItem(name, item) {
				data.menuList.forEach(v => {
					if (v.name === name) {
						v.list.push(...(Array.isArray(item) ? item : [item]))
					}
				})
			},
			removeMenuItem(item) {
				data.menuList.forEach(v => {
					let idx
					if (is.String(item)) {
						[...v.list].forEach((menu, idx) => {
							if (menu.name === item) {
								v.list.splice(idx, 1)
							}
						})
					} else {
						if ((idx = v.list.indexOf(item)) > -1) {
							v.list.splice(idx, 1)
						}
					}
				})
			},
			addComponent(component) {
				if (Array.isArray(component)) {
					component.forEach(v => {
						ruleList[v.name] = v
					})
				} else {
					ruleList[component.name] = component
				}
			},
			getParent(rule) {
				let parent = rule.__fc__.parent.rule
				const config = parent.config
				if (config && config.config.inside) {
					rule = parent
					parent = parent.__fc__.parent.rule
				}
				return { root: parent, parent: rule }
			},
			makeDrag(group, tag, children, on) {
				return {
					type: 'DragBox',
					wrap: {
						show: false
					},
					col: {
						show: false
					},
					inject: true,
					props: {
						rule: {
							props: {
								tag: 'el-col',
								group: group === true ? 'default' : group,
								ghostClass: 'ghost',
								animation: 150,
								handle: '._fc-drag-btn',
								emptyInsertThreshold: 0,
								direction: 'vertical',
								itemKey: 'type'
							}
						},
						tag
					},
					children,
					on
				}
			},
			clearDragRule() {
				methods.setRule([])
			},
			makeDragRule(children) {
				return methods.makeChildren([
					methods.makeDrag(true, 'draggable', children, {
						add: (inject, evt) => methods.dragAdd(children, evt),
						end: (inject, evt) => methods.dragEnd(children, evt),
						start: (inject, evt) => methods.dragStart(children, evt),
						unchoose: (inject, evt) => methods.dragUnchoose(children, evt)
					})
				])
			},
			previewFc() {
				data.preview.state = true
				data.preview.rule = methods.getRule()
				data.preview.option = methods.getOption()
			},
			getRule() {
				return methods.parseRule(deepCopy(data.dragForm.api.rule[0].children))
			},
			getJson() {
				return designerForm.toJson(methods.getRule())
			},
			getOption() {
				const option = deepCopy(data.form.value)
				option.submitBtn = option._submitBtn
				option.resetBtn = option._resetBtn
				if (typeof option.submitBtn === 'object') {
					option.submitBtn.show = option.form.formCreateSubmitBtn
				} else {
					option.submitBtn = {
						show: option.form.formCreateSubmitBtn,
						innerText: t('form.submit')
					}
				}
				if (typeof option.resetBtn === 'object') {
					option.resetBtn.show = option.form.formCreateResetBtn
				} else {
					option.resetBtn = {
						show: option.form.formCreateResetBtn,
						innerText: t('form.reset')
					}
				}
				delete option.form.formCreateSubmitBtn
				delete option.form.formCreateResetBtn
				delete option._submitBtn
				delete option._resetBtn
				return option
			},
			getOptions() {
				methods.getOption()
			},
			setRule(rules) {
				if (!rules) {
					rules = []
				}
				data.children = methods.makeChildren(methods.loadRule(is.String(rules) ? designerForm.parseJson(rules) : deepCopy(rules)))
				methods.clearActiveRule()
				data.dragForm.rule = methods.makeDragRule(data.children)
			},
			setBaseRuleConfig(rule, append) {
				baseRule.value = { rule, append }
				data.baseForm.rule = tidyRuleConfig(field, baseRule.value, { t })
			},
			setComponentRuleConfig(name, rule, append) {
				componentRule.value[name] = { rule, append }
				data.cacheProps = {}
				const activeRule = data.activeRule
				if (activeRule) {
					const propsVal = data.propsForm.api.formData && data.propsForm.api.formData()
					data.propsForm.rule = data.cacheProps[activeRule._id] = tidyRuleConfig(
						activeRule.config.config.props,
						componentRule.value && componentRule.value[activeRule.config.config.name],
						activeRule,
						{ t, api: data.dragForm.api }
					)
					nextTick(() => {
						propsVal && data.propsForm.api.setValue(propsVal)
					})
				}
			},
			setValidateRuleConfig(rule, append) {
				validateRule.value = { rule, append }
				data.validateForm.rule = tidyRuleConfig(field, validateRule.value, { t })
			},
			setFormRuleConfig(rule, append) {
				formRule.value = { rule, append }
				data.form.rule = tidyRuleConfig(field, formRule.value, { t })
			},
			clearActiveRule() {
				data.activeRule = null
				data.activeTab = 'form'
			},
			setOption(opt) {
				let option = { ...opt }
				option.form.formCreateSubmitBtn =
					typeof option.submitBtn === 'object' ? (option.submitBtn.show === undefined ? true : !!option.submitBtn.show) : !!option.submitBtn
				option.form.formCreateResetBtn = typeof option.resetBtn === 'object' ? !!option.resetBtn.show : !!option.resetBtn
				option._resetBtn = option.resetBtn
				option.resetBtn = false
				option._submitBtn = option.submitBtn
				option.submitBtn = false
				data.form.value = option
			},
			setOptions(opt) {
				methods.setOption(opt)
			},
			loadRule(rules) {
				const loadRule = []
				rules.forEach(rule => {
					if (is.String(rule)) {
						return loadRule.push(rule)
					}
					const config = ruleList[rule._fc_drag_tag] || ruleList[rule.type]
					const _children = rule.children
					rule.children = []
					if (rule.control) {
						rule._control = rule.control
						delete rule.control
					}
					if (config) {
						rule = methods.makeRule(config, rule)
						if (_children) {
							let children = rule.children[0].children

							if (config.drag) {
								children = children[0].children
							}
							children.push(...methods.loadRule(_children))
						}
					} else if (_children) {
						rule.children = methods.loadRule(_children)
					}
					loadRule.push(rule)
				})
				return loadRule
			},
			parseRule(children) {
				return [...children].reduce((initial, rule) => {
					if (is.String(rule)) {
						initial.push(rule)
						return initial
					} else if (rule.type === 'DragBox') {
						initial.push(...methods.parseRule(rule.children))
						return initial
					} else if (rule.type === 'DragTool') {
						rule = rule.children[0]
						if (rule.type === 'DragBox') {
							initial.push(...methods.parseRule(rule.children))
							return initial
						}
					}
					if (!rule) return initial
					rule = { ...rule }
					if (rule.children.length) {
						rule.children = methods.parseRule(rule.children)
					}

					delete rule._id
					delete rule.key
					delete rule.component
					if (rule.config) {
						delete rule.config.config
					}
					if (rule.effect) {
						delete rule.effect._fc
						delete rule.effect._fc_tool
					}
					if (rule._control) {
						rule.control = rule._control
						delete rule._control
					}
					Object.keys(rule)
						.filter(k => (Array.isArray(rule[k]) && rule[k].length === 0) || (is.Object(rule[k]) && Object.keys(rule[k]).length === 0))
						.forEach(k => {
							delete rule[k]
						})
					initial.push(rule)
					return initial
				}, [])
			},
			baseChange(field, value, _, fapi) {
				if (data.activeRule && fapi[data.activeRule._id] === data.activeRule) {
					methods.unWatchActiveRule()
					data.activeRule[field] = value
					methods.watchActiveRule()
					data.activeRule.config.config?.watch?.['$' + field]?.({
						field,
						value,
						api: fapi,
						rule: data.activeRule
					})
				}
			},
			propRemoveField(field, _, fapi) {
				if (data.activeRule && fapi[data.activeRule._id] === data.activeRule) {
					methods.unWatchActiveRule()
					const org = field
					data.dragForm.api.sync(data.activeRule)
					if (field.indexOf('formCreate') === 0) {
						field = field.replace('formCreate', '')
						if (!field) return
						field = lower(field)
						if (field.indexOf('effect') === 0 && field.indexOf('>') > -1) {
							delete data.activeRule.effect[field.split('>')[1]]
						} else if (field.indexOf('props') === 0 && field.indexOf('>') > -1) {
							delete data.activeRule.props[field.split('>')[1]]
						} else if (field.indexOf('attrs') === 0 && field.indexOf('>') > -1) {
							data.activeRule.attrs[field.split('>')[1]] = value
						} else if (field === 'child') {
							delete data.activeRule.children[0]
						} else if (field) {
							data.activeRule[field] = undefined
						}
					} else {
						delete data.activeRule.props[field]
					}
					methods.watchActiveRule()
					data.activeRule.config.config?.watch?.[org]?.({
						field: org,
						value: undefined,
						api: fapi,
						rule: data.activeRule
					})
				}
			},
			propChange(field, value, _, fapi) {
				if (data.activeRule && fapi[data.activeRule._id] === data.activeRule) {
					methods.unWatchActiveRule()
					const org = field
					if (field.indexOf('formCreate') === 0) {
						field = field.replace('formCreate', '')
						if (!field) return
						field = lower(field)
						if (field.indexOf('effect') === 0 && field.indexOf('>') > -1) {
							data.activeRule.effect[field.split('>')[1]] = value
						} else if (field.indexOf('props') === 0 && field.indexOf('>') > -1) {
							data.activeRule.props[field.split('>')[1]] = value
						} else if (field.indexOf('attrs') === 0 && field.indexOf('>') > -1) {
							data.activeRule.attrs[field.split('>')[1]] = value
						} else if (field === 'child') {
							data.activeRule.children[0] = value
						} else {
							data.activeRule[field] = value
						}
					} else {
						data.activeRule.props[field] = value
					}
					methods.watchActiveRule()
					data.activeRule.config.config?.watch?.[org]?.({
						field: org,
						value,
						api: fapi,
						rule: data.activeRule
					})
				}
			},
			validateChange(formData) {
				if (!data.activeRule || data.validateForm.api[data.activeRule._id] !== data.activeRule) return
				data.activeRule.validate = formData.validate || []
				data.dragForm.api.refreshValidate()
				data.dragForm.api.nextTick()()()()()()()()()()()()()()()()()()()(() => {
					data.dragForm.api.clearValidateState(data.activeRule.__fc__.id)
				})
			},
			toolActive(rule) {
				methods.unWatchActiveRule()
				if (data.activeRule) {
					delete data.propsForm.api[data.activeRule._id]
					delete data.baseForm.api[data.activeRule._id]
					delete data.validateForm.api[data.activeRule._id]
					delete data.dragForm.api.activeRule
				}
				data.activeRule = rule
				data.dragForm.api.activeRule = rule

				nextTick(() => {
					data.activeTab = 'props'
					nextTick(() => {
						data.propsForm.api[data.activeRule._id] = data.activeRule
						data.baseForm.api[data.activeRule._id] = data.activeRule
						data.validateForm.api[data.activeRule._id] = data.activeRule
					})
				})
				if (!data.cacheProps[rule._id]) {
					data.cacheProps[rule._id] = tidyRuleConfig(
						rule.config.config.props,
						componentRule.value && componentRule.value[rule.config.config.name],
						rule,
						{ t, api: data.dragForm.api }
					) // rule.config.config.props(rule, {t, api: data.dragForm.api});
				}

				data.propsForm.rule = data.cacheProps[rule._id]
				methods.updateRuleFormData()
				methods.watchActiveRule()
			},
			updateRuleFormData() {
				const rule = data.activeRule
				const formData = { ...rule.props, formCreateChild: deepCopy(rule.children[0]) }
				Object.keys(rule).forEach(k => {
					if (['effect', 'config', 'payload', 'id', 'type'].indexOf(k) < 0) formData['formCreate' + upper(k)] = deepCopy(rule[k])
				})
				;['props', 'effect', 'attrs'].forEach(name => {
					rule[name] &&
						Object.keys(rule[name]).forEach(k => {
							formData['formCreate' + upper(name) + '>' + k] = deepCopy(rule[name][k])
						})
				})
				data.propsForm.value = formData

				data.showBaseRule = hasProperty(rule, 'field') && rule.input !== false && (!config.value || config.value.showBaseForm !== false)

				if (data.showBaseRule) {
					data.baseForm.value = {
						field: rule.field,
						title: rule.title || '',
						info: rule.info,
						_control: rule._control
					}
					data.validateForm.value = { validate: rule.validate ? [...rule.validate] : [] }
					data.dragForm.api.refreshValidate()
					data.dragForm.api.nextTick()()()()()()()()()()()()()()()()()()()(() => {
						data.dragForm.api.clearValidateState(rule.__fc__.id)
					})
				}
			},
			dragStart(children) {
				data.moveRule = children
				data.added = false
			},
			dragUnchoose(children, evt) {
				data.addRule = {
					children,
					oldIndex: evt.oldIndex
				}
			},
			dragAdd(children, evt) {
				const newIndex = evt.newIndex
				const menu = evt.item._underlying_vm_
				if (!menu || menu.__fc__) {
					if (data.addRule) {
						const rule = data.addRule.children.splice(data.addRule.oldIndex, 1)
						children.splice(newIndex, 0, rule[0])
					}
				} else {
					const rule = methods.makeRule(ruleList[menu.name])
					children.splice(newIndex, 0, rule)
				}
				data.added = true
				// data.dragForm.api.refresh();
			},
			dragEnd(children, { newIndex, oldIndex }) {
				if (!data.added && !(data.moveRule === children && newIndex === oldIndex)) {
					const rule = data.moveRule.splice(oldIndex, 1)
					children.splice(newIndex, 0, rule[0])
				}
				data.moveRule = null
				data.addRule = null
				data.added = false
				// data.dragForm.api.refresh();
			},
			makeRule(config, _rule) {
				const rule = _rule || config.rule({ t })
				rule.config = { config }
				if (config.component) {
					rule.component = markRaw(config.component)
				}
				if (!rule.effect) rule.effect = {}
				rule.effect._fc = true
				rule._fc_drag_tag = config.name

				let drag

				if (config.drag) {
					rule.children.push(
						(drag = methods.makeDrag(config.drag, rule.type, methods.makeChildren([]), {
							end: (inject, evt) => methods.dragEnd(inject.self.children, evt),
							add: (inject, evt) => methods.dragAdd(inject.self.children, evt),
							start: (inject, evt) => methods.dragStart(inject.self.children, evt),
							unchoose: (inject, evt) => methods.dragUnchoose(inject.self.children, evt)
						}))
					)
				}

				if (config.children && !_rule) {
					for (let i = 0; i < (config.childrenLen || 1); i++) {
						const child = methods.makeRule(ruleList[config.children])
						;(drag || rule).children.push(child)
					}
				}

				const dragMask = mask.value !== undefined ? mask.value !== false : config.mask !== false

				if (config.inside) {
					rule.children = methods.makeChildren([
						{
							type: 'DragTool',
							props: {
								dragBtn: config.dragBtn !== false,
								children: config.children,
								mask: dragMask
							},
							effect: {
								_fc_tool: true
							},
							inject: true,
							on: {
								delete: ({ self }) => {
									const parent = methods.getParent(self).parent
									parent.__fc__.rm()
									vm.emit('delete', parent)
									methods.clearActiveRule()
								},
								create: ({ self }) => {
									const top = methods.getParent(self)
									vm.emit('create', top.parent)
									top.root.children.splice(top.root.children.indexOf(top.parent) + 1, 0, methods.makeRule(top.parent.config.config))
								},
								addChild: ({ self }) => {
									const top = methods.getParent(self)
									const config = top.parent.config.config
									const item = ruleList[config.children]
									if (!item) return
									;(!config.drag ? top.parent : top.parent.children[0]).children[0].children.push(methods.makeRule(item))
								},
								copy: ({ self }) => {
									const top = methods.getParent(self)
									vm.emit('copy', top.parent)
									top.root.children.splice(top.root.children.indexOf(top.parent) + 1, 0, designerForm.copyRule(top.parent))
								},
								active: ({ self }) => {
									const top = methods.getParent(self)
									vm.emit('active', top.parent)
									methods.toolActive(top.parent)
								}
							},
							children: rule.children
						}
					])
					return rule
				} else {
					return {
						type: 'DragTool',
						props: {
							dragBtn: config.dragBtn !== false,
							children: config.children,
							mask: dragMask
						},
						effect: {
							_fc_tool: true
						},
						inject: true,
						on: {
							delete: ({ self }) => {
								vm.emit('delete', self.children[0])
								self.__fc__.rm()
								methods.clearActiveRule()
							},
							create: ({ self }) => {
								vm.emit('create', self.children[0])
								const top = methods.getParent(self)
								top.root.children.splice(top.root.children.indexOf(top.parent) + 1, 0, methods.makeRule(self.children[0].config.config))
							},
							addChild: ({ self }) => {
								const config = self.children[0].config.config
								const item = ruleList[config.children]
								if (!item) return
								;(!config.drag ? self : self.children[0]).children[0].children.push(methods.makeRule(item))
							},
							copy: ({ self }) => {
								vm.emit('copy', self.children[0])
								const top = methods.getParent(self)
								top.root.children.splice(top.root.children.indexOf(top.parent) + 1, 0, designerForm.copyRule(top.parent))
							},
							active: ({ self }) => {
								vm.emit('active', self.children[0])
								methods.toolActive(self.children[0])
							}
						},
						children: methods.makeChildren([rule])
					}
				}
			},
			load() {
				let val
				if (data.type === 2) {
					val = data.value
				} else if (data.type === 0) {
					val = formCreate.toJson(data.value, 2)
				} else {
					val = JSON.stringify(data.value, null, 2)
				}
				nextTick(() => {
					data.editor = CodeMirror(document.getElementById('editor'), {
						lineNumbers: true,
						mode: data.type === 2 ? { name: 'vue' } : 'application/json',
						gutters: ['CodeMirror-lint-markers'],
						// lint: true,
						line: true,
						tabSize: 2,
						lineWrapping: true,
						value: val || ''
					})
				})
			},
			showJson() {
				data.state = true
				data.type = 0
				data.value = methods.getRule()
				let val = JSON.parse(JSON.stringify(data.value))
				vm.emit('export-json', val)
			},
			setJson() {
				data.state = true
				data.type = 3
				data.value = []
			},
			onOk() {
				if (data.err) return
				const json = data.editor.getValue()
				let val = JSON.parse(json)
				if (data.type === 3) {
					if (!Array.isArray(val)) {
						data.err = true
						return
					}
					methods.setRule(formCreate.parseJson(json))
				} else {
					if (!is.Object(val) || !val.form) {
						data.err = true
						return
					}
					methods.setOption(val)
				}
				data.state = false
			},
			// 暂存
			saveAsForm() {
				data.value = methods.getRule()
				let val = JSON.parse(JSON.stringify(data.value))
				vm.emit('export-json', val)
			},
			// form回显
			initForm(json) {
				methods.setRule(formCreate.parseJson(json))
			}
		}
		data.dragForm.rule = methods.makeDragRule(methods.makeChildren(data.children))

		defineExpose({
			initForm: methods.initForm
		})
		return {
			editorRef,
			...toRefs(data),
			...methods,
			dragHeight,
			t
		}
	},
	created() {
		document.body.ondrop = e => {
			e.preventDefault()
			e.stopPropagation()
		}
	}
})
</script>
<style>
._fc-designer {
	height: 100%;
	min-height: 500px;
	overflow: hidden;
	cursor: default;
	position: relative;
}

._fc-designer > .el-main {
	position: absolute;
	top: 0;
	bottom: 0;
	left: 0;
	right: 0;
	padding: 0px;
}

._fc-m .form-create ._fc-l-item {
	background: #2e73ff;
	width: 100%;
	height: 10px;
	overflow: hidden;
	transition: all 0.3s ease;
}

._fc-l,
._fc-m,
._fc-r {
	border-top: 1px solid #ececec;
	box-sizing: border-box;
}

._fc-l-group {
	padding: 0 12px;
}

._fc-l-title {
	font-weight: 600;
	font-size: 14px;
	margin: 18px 0px 5px;
}

._fc-l-item {
	display: inline-block;
	background: #fff;
	color: #000;
	min-width: 70px;
	width: 33.33%;
	height: 70px;
	line-height: 1;
	text-align: center;
	transition: all 0.2s ease;
	cursor: pointer;
}

._fc-l-item i {
	font-size: 21px;
	display: inline-block;
}

._fc-l-item ._fc-l-name {
	font-size: 12px;
}

._fc-l-item ._fc-l-icon {
	padding: 10px 5px 12px;
}

._fc-l-item:hover {
	background: #2e73ff;
	color: #fff;
}

._fc-m-tools {
	height: 40px;
	align-items: center;
	display: flex;
	justify-content: flex-end;
	border: 1px solid #ececec;
	border-top: 0 none;
}

._fc-m-tools button.el-button {
	padding: 5px 14px;
	display: flex;
	align-items: center;
}

._fc-m-tools .fc-icon {
	font-size: 14px;
	margin-right: 2px;
}

._fc-r .el-tabs__nav-wrap::after {
	height: 1px;
	background-color: #ececec;
}

._fc-r ._fc-r-tabs {
	display: flex;
	padding: 0;
	border-bottom: 1px solid #ececec;
}

._fc-r ._fc-r-tab {
	height: 40px;
	box-sizing: border-box;
	line-height: 40px;
	display: inline-block;
	list-style: none;
	font-size: 14px;
	font-weight: 600;
	color: #303133;
	position: relative;
	flex: 1;
	text-align: center;
}

._fc-r ._fc-r-tab.active {
	color: #409eff;
	border-bottom: 2px solid #409eff;
}

.drag-box {
	min-height: 60px;
	width: 100%;
}

._fc-m-drag {
	overflow: auto;
	padding: 2px;
	box-sizing: border-box;
}

._fc-m-drag,
.draggable-drag {
	background: #fff;
	height: 100%;
	position: relative;
}

._fc-m-drag > form,
._fc-m-drag > form > .el-row {
	height: 100%;
}
</style>
