<script setup lang="ts">
import type { FormInstance } from 'vant';
import type { Field, Fields, Option, RegisterModel } from '@/request/model';

import { Toast } from '@/platform';
import { useCaptcha, useRequest, useSerialRequest } from 'alova/client';
import { getCaptchaMethod, registerFieldsMethod, dictionariesMethod, searchAccountMethod } from '@/request/api';

import PickerDialog from '@/components/PickerDialog.vue';
import CascaderDialog from '@/components/CascaderDialog.vue';

const { searchable = true, searchForbiddenField = true } = defineProps<{
  searchable?: boolean;
  searchForbiddenField?: boolean;
}>();
const emit = defineEmits<{
  submit: [payload: { model: RegisterModel; modelLabel: Partial<RegisterModel> }];
}>();

const formRef = useTemplateRef<FormInstance>('register-form');
const modelLabel = ref<Partial<RegisterModel>>({ });
const modelValue = ref<Partial<RegisterModel>>({ code: '' });
const model = computed(() => ({ ...modelLabel.value, ...modelValue.value }));

const { data: fields, loading } = useSerialRequest([dictionariesMethod, (dictionaries) => registerFieldsMethod(dictionaries)]).onSuccess(({ data }) => {
  const sex = data.find((field) => field.name === 'sex');
  if (sex) {
    const unknown = (<any>sex).component?.options?.find((option: any) => option.value === '2');
    modelLabel.value.sex = unknown?.label;
    modelValue.value.sex = unknown?.value;
  }
});

const forbiddenFields = shallowRef<Record<string, boolean>>({});
const { loading: searching, send: search } = useRequest((data) => searchAccountMethod(data), {
  immediate: false,
}).onSuccess(({ data }) => {
  if (!fields.value.some((field) => field.name === 'phone')) Reflect.deleteProperty(data, 'phone');
  setValues(data);
});

let captchaLoaded = false;
const {
  loading: captchaLoading,
  countdown,
  send: getCaptcha,
} = useCaptcha(() => getCaptchaMethod(model.value.phone!), { initialCountdown: 60 }).onSuccess(({ data }) => {
  captchaLoaded = true;
  Toast.success(data ?? '获取成功');
});
const getCaptchaHandle = async () => {
  try {
    await formRef.value?.validate('phone');
    if (!model.value.phone) throw { message: `请输入手机号码` };
    await getCaptcha();
    const field = fields.value.find((field) => field.name === 'code');
    if (field?.keyboard) {
      field.keyboard.show = true;
    }
  } catch (e: any) {
    Toast.warning(e?.message);
  }
};

const searchHandle = async (key: 'cardno' | 'code') => {
  if (!searchable) return;
  const forbidden = { cardno: 'phone', code: 'cardno' }[key];
  try {
    await formRef.value?.validate(key);
    forbiddenFields.value = {};
    const { cardno, phone, code } = model.value;
    await search({ cardno, phone, code })
      .then((data) => {
        forbiddenFields.value[forbidden] = !!(data as any)[forbidden];
        triggerRef(forbiddenFields);
      })
      .catch();
  } catch (e: any) {
    Toast.warning(e?.message);
  }
};

function onKeyboardBlur(field: Fields[number]) {
  if ( field?.name === 'phone' && !captchaLoaded ) { getCaptchaHandle(); }
  if ( field?.name === 'cardno' && model.value.cardno ) { searchHandle('cardno'); }
  if ( field?.name === 'code' && model.value.phone ) { searchHandle('code'); }
}

function onSubmitHandle() {
  emit('submit', { model: model.value as RegisterModel, modelLabel: modelLabel.value });
}

function fix(key: string) {
  for (const field of fields.value) {
    if (field.keyboard?.show && field.name !== key) field.keyboard.show = false;
  }
}

const keyboardProps = reactive({
  key: '',
  props: {},
  show: false,
});
const pickerProps = reactive({
  key: '',
  props: { options: [], selected: [] },
  show: false,
});
const cascaderProps = reactive({
  key: '',
  props: { options: [], loading: false },
  show: false,
});

const handle = (value: any, field: Field | string) => {
  field = ((key: Field | string): Field => (typeof key === 'string' ? fields.value.find((field) => field.name === key)! : key))(field);
  const key = field.name!;
  if (field.component?.name === 'radio') {
    const option = field?.component?.options?.find((option) => option.value === value);
    (modelLabel.value as any)[key] = option?.label;
    (modelValue.value as any)[key] = option?.value;
  } else if (field.component?.name === 'picker') {
    if (typeof value === 'string') {
      value = value.split(',').map((value) => {
        const [v, l] = value.split(':');
        return { value, label: l ?? (field.component as any).options?.find((option: Option) => option.value === v)?.label ?? v };
      });
    }
    (modelLabel.value as any)[key] = value.map((option: Option) => option.label).join(',');
    (modelValue.value as any)[key] = value.map((option: Option) => option.value).join(',');
  } else if (field.component?.name === 'cascader') {
    (modelLabel.value as any)[key] = value.map((option: Option) => option.label).join('/');
    (modelValue.value as any)[key] = value;
  } else if (field.component?.name === 'code') {
    (modelValue.value as any)[key] = value;
  } else {
    (modelLabel.value as any)[key] = value;
    (modelValue.value as any)[key] = value;
  }
};

/** 父页扫码等在 fields 未返回前调用 setValues 时，先合并到此，待 fields 就绪后一次性按 field 类型写回 */
const pendingSetValues = shallowRef<Record<string, any> | null>(null);

function applySetValues(values: Record<string, any> | null | undefined) {
  forbiddenFields.value = {};
  if (!values) {
    modelLabel.value = {};
    modelValue.value = {};
    return;
  }
  for (const [key, value] of Object.entries(values)) {
    const field = fields.value?.find((field) => field.name === key);
    if (field) {
      forbiddenFields.value[key] = searchForbiddenField && !!value && !['phone', 'cardno'].includes(key);
      handle(value, field as any);
    } else {
      (modelValue as any).value[key] = value;
    }
  }
}

const setValues = (values: Record<string, any>) => {
  if (!values) {
    pendingSetValues.value = null;
    applySetValues(null);
    return;
  }
  if (!fields.value?.length) {
    pendingSetValues.value = { ...(pendingSetValues.value ?? {}), ...values };
    for (const [key, value] of Object.entries(values)) {
      (modelValue as any).value[key] = value;
    }
    return;
  }
  pendingSetValues.value = null;
  applySetValues(values);
};

watch(
  () => fields.value?.length ?? 0,
  (len) => {
    if (len > 0 && pendingSetValues.value) {
      const payload = pendingSetValues.value;
      pendingSetValues.value = null;
      applySetValues(payload);
    }
  },
);

function onFieldFocus(field: any) {
  if (forbiddenFields.value[field.name]) return;
  if (field.keyboard) {
    keyboardProps.key = field.name;
    keyboardProps.show = true;
    keyboardProps.props = {
      ...field.keyboard,
      maxlength: field.control?.maxlength ?? Number.POSITIVE_INFINITY,
      onBlur() {
        keyboardProps.show = false;
        onKeyboardBlur(field);
      },
    };
  } else if (field.component?.name === 'picker') {
    pickerProps.key = field.name;
    pickerProps.show = true;
    pickerProps.props = {
      ...field.component.props,
      title: field.control.label,
      options: field.component.options,
      selected: (modelValue.value as Record<string, string>)[field.name]?.split(','),
    };
  } else if (field.component?.name === 'cascader') {
    cascaderProps.key = field.name;
    cascaderProps.show = true;
    if (typeof field.component.options === 'function') {
      cascaderProps.props = {
        ...field.component.props,
        title: field.control.label,
        options: [],
        loading: true,
      };
      (async function () {
        field.component.options = await field.component.options();
        cascaderProps.props = {
          ...field.component.props,
          title: field.control.label,
          options: field.component.options,
          loading: false,
          selected: (modelValue.value as Record<string, { value: string }[]>)[field.name]?.map((option) => option.value),
        };
      })();
    } else {
      cascaderProps.props = {
        ...field.component.props,
        title: field.control.label,
        options: field.component.options,
      };
    }
  }
}
function onFieldBlur(_field: any) {
  keyboardProps.show = false;
  pickerProps.show = false;
  cascaderProps.show = false;
}

defineExpose({
  submit() {
    formRef.value?.submit();
  },
  setValues,
  loading,
  searching,
});
</script>

<template>
  <van-form class="register-form" ref="register-form" colon required="auto" scroll-to-error scroll-to-error-position="center" @submit="onSubmitHandle()">
    <van-cell-group :border="false">
      <template v-for="field in fields" :key="field.name">
        <template v-if="!field.control?.hide || (typeof field.control?.hide === 'function' && !field.control.hide(model))">
          <van-field
            :model-value="field.control?.readonly ? modelLabel[field.name!] : model[field.name!]"
            @update:model-value="handle($event, field)"
            :name="field.name"
            :id="field.name"
            :rules="field.rules"
            v-bind="(field.control ?? {}) as any"
            :class="{ 'no-border': field.control?.border === false }"
            @focus="onFieldFocus(field)"
            @blur="onFieldBlur(field)"
            :readonly="field.control?.readonly"
            @click="onFieldFocus(field)"
            :disabled="forbiddenFields[field.name!]"
          >
            <template #input v-if="field.component?.name === 'radio'">
              <van-radio-group direction="horizontal" shape="dot" :disabled="forbiddenFields[field.name!]" v-model="modelValue[field.name!]" @change="handle($event, field)">
                <van-radio v-for="option in field.component?.options" :key="option.value" :name="option.value">
                  {{ option.label }}
                </van-radio>
              </van-radio-group>
            </template>
            <template #input v-else-if="field.component?.name === 'code'">
              <van-password-input
                style="width: 100%"
                v-model:value="modelValue[field.name!]"
                v-bind="(field.component as any)!.props"
                :focused="field.keyboard?.show"
                @focus="
                  field.keyboard && (field.keyboard.show = true);
                  fix('code');
                "
              />
            </template>
            <template #button>
              <div class="text-primary cursor-pointer">
                <template v-if="field.component?.name === 'code'">
                  <div class="text-primary cursor-pointer" @click="getCaptchaHandle()">
                    {{ captchaLoading ? '发送中...' : countdown > 0 ? `${countdown}后可重发` : '获取验证码' }}
                  </div>
                </template>
                <template v-else>{{ field.suffix }}</template>
              </div>
            </template>
          </van-field>
        </template>
      </template>
    </van-cell-group>
  </van-form>
  <van-number-keyboard v-bind="keyboardProps.props" :show="keyboardProps.show" v-model="modelValue[keyboardProps.key]" @update:model-value="handle($event, keyboardProps.key)" />
  <PickerDialog v-bind="pickerProps.props" v-model:show="pickerProps.show" @selected="handle($event, pickerProps.key)" />
  <CascaderDialog v-bind="cascaderProps.props" v-model:show="cascaderProps.show" @selected="handle($event, cascaderProps.key)" />
</template>

<style scoped lang="scss">
.register-form {
  .van-field {
    margin: 0;
    padding: 0;
  }

  .van-field.no-border {
    :deep(.van-field__control) {
      padding: 0;
      border: none;
      border-radius: 8px;
      text-align: center;
    }
  }

  :deep(.van-field--disabled) {
    --tw-text-opacity: 0.5;
    --van-radio-checked-icon-color: rgba(56, 255, 110, var(--tw-text-opacity, 1));
    .text-primary {
      --tw-text-opacity: 0.5;
    }
  }

  :deep(.van-field__label) {
    margin-bottom: 24px;
    padding: 8px 0;
    min-width: 100px;
    font-size: 18px;
  }

  :deep(.van-field__control) {
    margin-bottom: 24px;
    padding: 8px;
    border: 1px solid var(--van-radio-checked-icon-color);
    border-radius: 8px;
    text-align: center;
  }

  :deep(.van-field__clear) {
    align-self: flex-start;
    display: flex;
    align-items: center;
    height: 42px;
  }

  :deep(.van-field__button) {
    margin-bottom: 24px;
    padding: 8px var(--van-padding-xs);
    min-width: 100px;
    font-size: 18px;
    text-align: left;
  }

  :deep(.van-field__error-message) {
    position: absolute;
    top: 40px + 2px;
  }

  :deep(.van-password-input) {
    margin: 0;
  }

  :deep(.van-password-input__security) {
    justify-content: space-between;
    align-items: center;
    text-align: center;
    $size: 40px;
    height: $size + 2px;

    &::after {
      display: none;
    }

    li {
      height: $size;
      width: $size;
      flex: none;
      border: 1px solid #38ff6e;
      border-radius: 8px;
    }
  }
}

.van-radio-group {
  height: 40px + 2px;
}
</style>