<template>
  <Cascader
    :class="[style && Object.keys(style).length > 0 ? 'customStyle' : '']"
    :style="style && Object.keys(style).length > 0 ? {} : { width: width }"
    v-model:value="emitData"
    :disabled="disabled"
    :options="options"
    :load-data="loadData"
    change-on-select
    @change="handleChange"
    :displayRender="handleRenderDisplay"
  >
    <template #suffixIcon v-if="loading">
      <LoadingOutlined spin />
    </template>
    <template #notFoundContent v-if="loading">
      <span>
        <LoadingOutlined spin class="mr-1" />
        {{ t('component.form.apiSelectNotFound') }}
      </span>
    </template>
  </Cascader>
</template>
<script lang="ts" setup>
  import { ref, unref, watch, computed } from 'vue';
  import { Cascader } from 'ant-design-vue';
  import { get, omit } from 'lodash-es';
  import { LoadingOutlined } from '@ant-design/icons-vue';
  import { useI18n } from '/@/hooks/web/useI18n';
  import { getAreaList } from '/@/api/system/area';

  interface Option {
    value: string;
    label: string;
    loading?: boolean;
    isLeaf?: boolean;
    children?: Option[];
  }

  const props = defineProps({
    value: String,
    style: Object,
    width: String,
    disabled: Boolean,
  });

  const emit = defineEmits(['change', 'defaultChange', 'update:value']);

  const apiData = ref<any[]>([]);
  const options = ref<Option[]>([]);
  const loading = ref<boolean>(false);
  const emitData = ref<any[]>([]);
  const isChange = ref(false);
  const labelField = 'name';
  const valueField = 'id';
  const childrenField = 'children';
  const resultField = 'data';

  const asyncFetchParamKey = 'id';
  const { t } = useI18n();

  watch(
    () => props.value,
    async (val, oldVal) => {
      if (!val && !!oldVal && val !== oldVal) {
        //重置
        emitData.value = [];
        return;
      }
      //选择时不重复请求接口
      if (isChange.value) return;

      if (val) {
        emitData.value = val.split(',');
      } else {
        emitData.value = [];
      }
      await initialFetch();
    },
    {
      immediate: true,
    },
  );

  watch(
    apiData,
    (data) => {
      options.value = generatorOptions(data);
      if (emitData.value.length > 1) {
        getParentNodes(emitData.value, options.value);
      }
    },
    { deep: true },
  );

  function generatorOptions(options: any[]): Option[] {
    return options.reduce((prev, next: Recordable) => {
      if (next) {
        const value = next[valueField];
        const item = {
          ...omit(next, [labelField, valueField]),
          label: next[labelField],
          value: `${value}`,
          isLeaf: false,
        };
        const children = Reflect.get(next, childrenField);
        if (children) {
          Reflect.set(item, childrenField, generatorOptions(children));
        }
        prev.push(item);
      }
      return prev;
    }, [] as Option[]);
  }

  async function initialFetch() {
    apiData.value = [];
    loading.value = true;
    try {
      const res = await getAreaList({ id: '0' });
      if (Array.isArray(res)) {
        apiData.value = res;
        return;
      }
      if (resultField) {
        apiData.value = get(res, resultField) || [];
      }
    } catch (error) {
      console.warn(error);
    } finally {
      loading.value = false;
    }
  }

  async function loadData(selectedOptions: Option[]) {
    const targetOption = selectedOptions[selectedOptions.length - 1];
    targetOption.loading = true;

    try {
      const res = await getAreaList({
        [asyncFetchParamKey]: Reflect.get(targetOption, 'value'),
      });

      if (Array.isArray(res)) {
        const children = generatorOptions(res);
        targetOption.children = children;
        return;
      }
      if (resultField) {
        const children = generatorOptions(get(res, resultField) || []);
        targetOption.children = children;
      }
    } catch (e) {
      console.error(e);
    } finally {
      targetOption.loading = false;
    }
  }

  //数据回显
  function getParentNodes(ids: any[], tree: any[]) {
    ids.forEach((id) => {
      tree.forEach(async (item: any) => {
        if (item.value === id) {
          item.children = [];
          const res = await getAreaList({ id });
          item.children = res.map((child) => {
            return {
              label: child.name,
              value: child.id,
              isLeaf: false,
            };
          });
          getParentNodes(ids, item.children);
        }
      });
    });
  }

  function handleChange(keys) {
    isChange.value = true;
    emit('change', keys ? keys.join(',') : '');
    emit('update:value', keys ? keys.join(',') : '');
    emitData.value = props.value === undefined ? keys : emitData.value;
  }

  function handleRenderDisplay({ labels, selectedOptions }) {
    if (unref(emitData).length === selectedOptions.length) {
      return labels.join(` / `);
    }
  }

  const color = computed(() => {
    return props.style?.color;
  });
  const backgroundcolor = computed(() => {
    return props.style
      ? props.style['background-color'] || (props.disabled ? '#f5f5f5' : '#fff')
      : '#fff';
  });
  const border = computed(() => {
    return props.style?.border || '1px solid #d9d9d9';
  });
  const fontsize = computed(() => {
    return props.style ? props.style['font-size'] : '14px';
  });
  const fontstyle = computed(() => {
    return props.style ? props.style['font-style'] : 'normal';
  });
  const customwidth = computed(() => {
    return props.style?.width;
  });
  const height = computed(() => {
    return props.style?.height || '32px';
  });
  const textalign = computed(() => {
    return props.style ? props.style['text-align'] : 'left';
  });
</script>
<style scoped lang="less">
  .customStyle {
    width: v-bind(customwidth);

    &.ant-select-disabled {
      :deep(.ant-select-selector) {
        background-color: v-bind(backgroundcolor);
      }
    }

    :deep(.ant-select-selector) {
      border: v-bind(border);
      background-color: v-bind(backgroundcolor);
      height: v-bind(height);
      font-size: v-bind(fontsize);
      font-style: v-bind(fontstyle);
    }

    :deep(.ant-select-selection-item) {
      color: v-bind(color);
      line-height: v-bind(height);
      text-align: v-bind(textalign);
    }

    :deep(.ant-select-selection-placeholder) {
      line-height: v-bind(height);
      text-align: v-bind(textalign);
    }
  }
</style>
