<template>
  <form class="nana-form" :name="name">
    <view :style="innerStyle">
      <slot />
    </view>
  </form>
</template>

<script setup lang="ts">
import { onMounted, provide, ref, toRefs } from 'vue';
import { ObjectUtils, waitTimeOut } from '@imengyu/imengyu-utils';
import Schema from 'async-validator';
import type { Rule, RuleItem, Rules, ValidateError } from 'async-validator';
import type { ViewStyle } from '../theme/ThemeDefine';
import type { FieldProps } from './Field.vue';
import type { FormContext, FormItemInternalContext } from './FormContext';


export type FormValueType = Date|null|number|string|boolean|number[]|string[]|boolean[]|null[]|FormValueType[];

export interface FormValues {
  [index: string]: FormValueType;
}
export interface FormState {
  /**
   * 数据存储/表单条目校验状态
   */
  [index: string]: any;
}
export interface FormProps {
  /**
   * 表单的值
   */
  model: FormState,
  /**
   * 表单验证数据
   */
  rules?: Rules;
  /**
   * 表单名称。透传给 `<form>` 元素的 `name` 属性。
   */
  name?: string,
  /**
   * 标签的flex宽度占比值
   */
  labelFlex?: number, 
  /**
   * 标签宽度
   */
  labelWidth?: string|number;
  /**
   * 标签位置
   */
  labelPosition?: 'top'|'left';
  /**
   * 标签对齐方式
   */
  labelAlign?: "left" | "center" | "right";
  /**
   * 输入框的flex宽度占比值
   */
  inputFlex?: number,
  /**
   * 添加冒号
   * @default true
   */
  colon?: boolean;
  /**
   * 设置字段校验的时机
   * * onBlur 文本框失去焦点时校验
   * * onValueChange 数值更改时校验
   * * onSubmit 提交时校验(默认)
   */
  validateTrigger?: 'blur'|'change'|'submit';
  /**
   * 显示label
   * @default false
   */
  showLabel?: boolean;
  /**
   * 是否只读
   * @default false
   */
  readonly?: boolean;
  /**
   * 组件是否禁用
   * @default false
   */
  disabled?: boolean;
  /**
   * 是否在提交时清除之前错误的验证结果
   * @default true
   */
  clearValidResult?: boolean;
  /**
   * 是否自动根据表单校验规则为 Field 设置必填星号
   * @default true
   */
  addRequireMark?: boolean;
  /**
   * fieldProps
   */
  fieldProps?: FieldProps,

  innerStyle?: ViewStyle,
}

defineOptions({
  options: {
    styleIsolation: "shared",
    virtualHost: true,
  }
})

const emit = defineEmits<{
  /**
   * 表单重置回调，表单数据会重置到 `intitalValue`
   */
  (e: 'reset'): void,
  /**
   * 调用 `formApi.submit()`，数据验证成功后的回调函数
   */
  (e: 'submit', value: FormValues): void,
  /**
   * 调用 `formApi.submit()`，数据验证失败后的回调函数
   */
  (e: 'submitFail', err: ValidateError[]): void
}>()

const props = withDefaults(defineProps<FormProps>(), {
  showLabel: true,
  addRequireMark: true,
  clearValidResult: true,
  colon: true,
  model: () => ({}),
});

const currentFocusField = ref<FormItemInternalContext>();
const intitalModel = ref<Record<string, unknown>|null>(null);
const formItems = ref(new Map<string, FormItemInternalContext>());

function accessFormModel(keyName: string, isSet: boolean|undefined, setValue: unknown) : unknown {
  const keys = keyName.split('.');
  let ret : unknown = undefined;
  let obj = props.model 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;
} 
const { 
  labelFlex, inputFlex, colon, labelAlign, labelPosition, labelWidth, model, rules,
  validateTrigger, showLabel, addRequireMark, name, fieldProps, disabled, readonly,
} = toRefs(props);

//Context
const formContext : FormContext = {
  onFieldFocus: (item: FormItemInternalContext) => {
    currentFocusField.value = item;

    if (item.getValidateTrigger() === 'blur')
      validate(item.getFieldName());
  },
  onFieldBlur: (item: FormItemInternalContext) => {
    if (currentFocusField.value == item)
      currentFocusField.value = undefined;
    //validate
    if (item.getValidateTrigger() === 'blur')
      validate(item.getFieldName());
  },
  onFieldChange: (item: FormItemInternalContext, newValue: unknown) => {
    accessFormModel(item.getFieldName(), true, newValue);
    //validate
    if (item.getValidateTrigger() === 'change')
      validate(item.getFieldName());
  },
  addFormItemField: (item: FormItemInternalContext) => {
    formItems.value.set(item.getFieldName(), item);
    return formItems.value.size;
  },
  removeFormItemField: (item: FormItemInternalContext) => {
    formItems.value.delete(item.getFieldName());
  },
  getItemValue: (item: FormItemInternalContext) => {
    return accessFormModel(item.getFieldName(), false, undefined);
  },
  getItemRequieed: (item: FormItemInternalContext) => {
    return checkRuleRequired(getItemRule(item.getFieldName()));
  },
  clearValidate: (item: FormItemInternalContext) => {
    clearValidate(item.getFieldName());
  },
  name,
  validateTrigger,
  addRequireMark,
  colon,
  labelAlign,
  labelPosition,
  labelFlex,
  labelWidth,
  inputFlex,
  showLabel,
  fieldProps,
  disabled,
  readonly,
};

function getItemRule(name: string) {
  const rule = props.rules?.[name];
  const itemRules = formItems.value.get(name)?.getItemRules() || [];
  return [
    ...itemRules, 
    ...(rule instanceof Array ? rule : [rule])
  ].filter((r): r is RuleItem => r !== undefined) as RuleItem[];
}
function checkRuleRequired(rule: Rule|undefined) {
  if (rule instanceof Array)
    return rule.find((r) => r.required === true) !== undefined;
  else if (rule)
    return rule.required === true;
  return false;
}

onMounted(() => {
  //Save intital model
  intitalModel.value = props.model ? 
    ObjectUtils.clone(props.model) as Record<string, unknown> : {};
});

provide('formContext', formContext);

/**
 * 取消表单内部的输入框激活（通常在提交时，可以调用此方法，关闭输入框）
 */
function blur() {
  if (currentFocusField.value) {
    currentFocusField.value.setBlurState();
    currentFocusField.value = undefined;
  }
}
//Clear valid error state
function clearValidate(name?: string|string[]) {
  if (name instanceof Array) {
    name.forEach(k => resetFields(k));
    return;
  }

  if (name) {
    const item = formItems.value.get(name);
    if (item)
      item.setErrorState(null);
  } else {
    for (const v of formItems.value)
      v[1].setErrorState(null);
  }
}
//Reset model to intital state
function resetFields(name?: string|string[]) {
  if (name) {
    if (name instanceof Array) {
      name.forEach(k => resetFields(k));
      return;
    }
    model.value[name] = intitalModel.value?.[name] || null;
  } else {
    for (const v of formItems.value)
      model.value[v[0]] = intitalModel.value?.[v[0]] || null;
  }
  clearValidate(name);
}
//Valid
function validate(name?: string|string[]) {
  const filteredRules = {} as Record<string, Rule|undefined>;

  clearValidate();

  //筛选需要验证的字段
  formItems.value.forEach((_, key) => {
    const rule = getItemRule(key);
    if (rule) {
      if (typeof name === 'string') {
        if (name === key) filteredRules[key] = rule;
      } else if (typeof name === 'object') {
        if (name.indexOf(key) >= 0) filteredRules[key] = rule;
      } else
        filteredRules[key] = rule;
    }
  });
  
  //获取当前参数
  const nowValues = model.value;
  //开始验证
  return new Promise<void>((resolve, reject) => {
    const validator = new Schema(filteredRules as Rules);
    validator.validate(nowValues, {}, (errors) => {
      if (errors) {
        //验证失败，把错误字段显示
        for (const key in errors) {
          if (Object.prototype.hasOwnProperty.call(errors, key)) {
            const err = errors[key];
            const k = formItems.value.get(err.field as string);
            if (k)
              k.setErrorState(err.message || `Valid ${err.field} failed!`);
          }
        }
        reject(errors);
      } else {
        //验证成功，去除之前的验证错误信息
        clearValidate();
        resolve();
      }
    });
  });
}
//Submit form
async function submit(valid = true) {
  if (props.clearValidResult) {
    clearValidate();
    await waitTimeOut(200);
  }
  if (valid) {
    //验证
    try {
      await validate();
    } catch(e) {
      console.warn('submit validate failed: ', e);
      emit('submitFail', e as ValidateError[]);
      return;
    }
    emit('submit', model.value);
  } else {
    //提交
    emit('submit', model.value);
  }
}


export interface FormInstance {
  /**
   * 取消表单内部的输入框激活（通常在提交时，可以调用此方法，关闭输入框）
   */
  blur(): void;
  /**
   * 清除表单验证错误信息
   * @param name 要清除验证错误信息的字段名，不指定则清除所有字段
   */
  clearValidate: (name?: string | string[]) => void;
  /**
   * 重置表单字段值
   * @param name 要重置的字段名，不指定则重置所有字段
   */
  resetFields: (name?: string | string[]) => void;
  /**
   * 验证表单字段值
   * @param name 要验证的字段名，不指定则验证所有字段
   * @returns 验证结果
   */
  validate(name?: string | string[]): Promise<void>;
  /**
   * 提交表单
   * @param valid 是否验证表单字段值，默认值为 true
   * @returns 提交结果
   */
  submit(valid?: boolean): Promise<void>;
  /**
   * 访问表单字段值
   * @param name 要访问的字段名
   * @param isSet 是否设置字段值，否则为读取，默认值为 false
   * @param defaultValue 字段不存在时的默认值，默认值为 null
   * @returns 字段值
   */
  accessFormModel: (name: string, isSet?: boolean | undefined, defaultValue?: any) => any;
  /**
   * 获取当前激活的输入框字段上下文
   * @returns 当前激活的输入框字段上下文
   */
  getCurrentFocusField: () => FormItemInternalContext | undefined;
  /**
   * 获取表单字段上下文
   * @param name 要获取的字段名
   * @returns 表单字段上下文
   */
  getField: (name: string) => FormItemInternalContext | undefined;
}

defineExpose<FormInstance>({
  accessFormModel,
  getCurrentFocusField: () => currentFocusField.value,
  getField: (name: string) => formItems.value.get(name),
  blur,
  clearValidate,
  resetFields,
  validate,
  submit,
})
</script>

<style>
.nana-form {
  width: auto;
  align-self: stretch;
}
</style>