<template>
  <Form
    ref="formEditor"
    :name="name"
    v-bind="finalOptions.formAdditionaProps"
    :model="model || {}"
    :rules="finalOptions.formRules"
    :disabled="finalOptions.disabled"
    :readonly="finalOptions.readonly"
    @submit="(e) => emit('submit', e)"
    @submitFailed="() => emit('finishFailed')"
  >
    <DynamicFormRoot 
      :options="finalOptions"
      :model="model"
      :name="name"
    />
  </Form>
</template>

<script setup lang="ts">
import { computed, onMounted, provide, ref, shallowRef, toRef, toRefs, type PropType } from 'vue';
import Form, { type FormInstance } from '../form/Form.vue';
import { 
  type IDynamicFormOptions, type IDynamicFormItem, type IDynamicFormRef, 
  type IDynamicFormObject, defaultDynamicFormOptions, 
  type IDynamicFormMessageCenter,
  type IDynamicFormMessageCenterCallback,
  MESSAGE_RELOAD,
  type IDynamicFormWidgetRef
} from '.';
import DynamicFormRoot from './nest/DynamicFormRoot.vue';

const props = defineProps({	
  /**
   * 动态表单选项
   */
  options: {
    type: Object as PropType<IDynamicFormOptions>,
    default: null
  },
  /**
   * 表单数据模型
   */
  model: {
    type: Object,
    default: null
  },
  /**
   * 表单名称, 设置到表单组件上。
   */
  name: {
    type: String,
    default: ''
  },
  /**
   * 全局参数。用于向每个表单项的参数中添加额外的参数，可以在回调中的 formGlobalParams 中访问。
   */
  globalParams: {
    type: Object as PropType<IDynamicFormObject>,
    default: null
  },
});
const emit = defineEmits(['ready', 'submit', 'finish', 'finishFailed']);

const { options, model, name } = toRefs(props);
const finalOptions = computed<IDynamicFormOptions>(() => ({
  ...defaultDynamicFormOptions,
  ...options.value,
}));

provide('rawModel', model);
provide('globalParams', toRef(props, 'globalParams'));
provide('finalOptions', finalOptions);


const formEditor = ref<FormInstance>();
const widgetsRefMap = new Map<string, IDynamicFormWidgetRef>();
const widgetsRefTypesMap = new Map<string, IDynamicFormWidgetRef[]>();
const messageCenterMap = new Map<string, IDynamicFormMessageCenterCallback>();

provide('messageCenter', {
  addInstance: (name: string, fn: IDynamicFormMessageCenterCallback) => messageCenterMap.set(name, fn),
  removeInstance: (name: string) => messageCenterMap.delete(name),
  addWidgetRef: (name: string, type: string, ref: IDynamicFormWidgetRef) => {
    const refs = widgetsRefTypesMap.get(name) || [];
    refs.push(ref);
    widgetsRefTypesMap.set(type, refs);
  },
  removeWidgetRef: (name: string, type: string, ref: IDynamicFormWidgetRef) => {
    const refs = widgetsRefTypesMap.get(name) || [];
    widgetsRefTypesMap.set(type, refs.filter((r) => r !== ref));
  },
} as IDynamicFormMessageCenter);

//获取组件引用
function getFormItemControlRef(key: string) {
  return widgetsRefMap.get(key)?.();
}
//获取组件引用组
function getFormItemControlRefsByType(type: string) {
  return (widgetsRefTypesMap.get(type) || []).map((ref) => ref());
}

//通过路径访问
function accessFormModel(keyName: string, isSet: boolean, setValue: unknown) : unknown {
  const keys = keyName.split('.');
  let ret : unknown = undefined;
  let obj = model.value as Record<string, unknown>;
  let keyIndex = 0;
  let key = keys[keyIndex];
  while (obj) {
    const leftIndex = key.indexOf('[');
    if (leftIndex > 0 && key.endsWith(']')) {
      const arr = obj[key.substring(0, leftIndex)] as Record<string, unknown>[];
      const index = parseInt(key.substring(leftIndex + 1, key.length - 1))    
      obj = arr[index];
      if (keyIndex >= keys.length - 1) {
        ret = obj;
        if (isSet) arr[index] = setValue as Record<string, unknown>;
      }
    } else {
      const newObj = obj[key] as Record<string, unknown>;
      if (keyIndex >= keys.length - 1) {
        ret = newObj;
        if (isSet)
          obj[key] = setValue as Record<string, unknown>;
      }
      obj = newObj;
    }
    if (keyIndex < keys.length - 1)
      key = keys[++keyIndex];
    else
      break;
  }
  return ret;
} 

//发送通知消息
function dispatchMessage(messageName: string, data?: unknown, receiveFilter?: RegExp) {
  for (const iterator of messageCenterMap) {
    if (!receiveFilter || receiveFilter.test(iterator[0]))
      iterator[1](messageName, data);
  }
}

//发送重新加载消息
function dispatchReload() {
  dispatchMessage(MESSAGE_RELOAD);
}

//初始化默认值到模型
function initDefaultValuesToModel() {
  function loopItems(key: string, parentKey: string, type: string, items: IDynamicFormItem[]) {
    let i = 0;
    for (const item of items) {
      let currentKey = key;
      switch (type) {
        case 'flat-simple':
        case 'flat-group':
          currentKey = (parentKey ? parentKey + '.' : '') + item.name;
          break;
        default:
        case 'object':
        case 'object-group':
          currentKey = (key ? key + '.' : '') + item.name;
          break
        case 'array':
          currentKey = (parentKey ? parentKey + '.' : '') + `[${i}]`;
          break;
        case 'array-object':
          currentKey = (parentKey ? parentKey + '.' : '') + `[${i}]` + item.name;
          break;
      }
      if (item.children) {
        loopItems(currentKey, key, item.type || '', item.children);
      }
      //console.log(currentKey);
      if (item.defaultValue !== undefined) {
        const oldValue = accessFormModel(currentKey, false, undefined);
        if (oldValue !== undefined && oldValue !== null)
          continue;
        accessFormModel(currentKey, true, typeof item.defaultValue === 'function' ? item.defaultValue() : item.defaultValue);
      }
      i++;
    }
  }
  loopItems('', '', '', finalOptions.value.formItems);
}

//获取当前表单中可见的所有字段名
function getVisibleFormNames() {
  return Array.from(messageCenterMap.keys());
}

onMounted(() => {
  setTimeout(() => {
    emit('ready');
  }, 400);
});

const formRef : IDynamicFormRef = {
  initDefaultValuesToModel,
  getVisibleFormNames,
  getFormRef() {
    if (!formEditor.value)
      throw new Error('Form instance is not create.');
    return formEditor.value
  },
  getFormItemControlRefsByType: getFormItemControlRefsByType as any,
  getFormItemControlRef: getFormItemControlRef as any,
  submit() { return this.getFormRef().validate(); },
  validate() { return this.getFormRef().validate(); },
  setValueByPath: (path: string|string[], value: unknown) => {
    if (Array.isArray(path))
      path = path.join('.');
    return accessFormModel(path, true, value);
  },
  getValueByPath: (path: string|string[]) => {
    if (Array.isArray(path))
      path = path.join('.');
    return accessFormModel(path, false, undefined);
  },
  dispatchMessage,
  dispatchReload,
};

provide('formRef', formRef);
provide('formName', name.value || 'unnamed');

defineExpose(formRef);

</script>