<template>
  <view class="nana-form-cascader">
    <Tabs 
      :currentIndex="headerTabCurrent"
      :tabs="headerTabs"
      :autoItemWidth="false"
      :innerStyle="headerStyle"
      v-bind="tabProps"
      @update:currentIndex="handleTabChange"
    />
    <slot name="header" :currentIndex="headerTabCurrent" />
    <SimpleList 
      mode="single-check"
      virtual
      :data="currentDataDynamicLoading ? [] : currentData"
      :dataDisplayProp="textKey"
      colorProp="color"
      disabledProp="disabled"
      :innerStyle="{
        width: '100%',
        height: themeContext.resolveThemeSize(props.listHeight),
      }"
      :checkedItems="[ currentCheckedItem ]"
      @itemClick="handleItemClick"
    >
      <template #empty>
        <LoadingPage v-if="currentDataDynamicLoading" />
        <Empty v-else :description="currentDataDynamicLoadErrorText || '暂无数据'" :image="currentDataDynamicLoadErrorText ? 'error' : 'default'" />
      </template>
    </SimpleList>
  </view>
</template>

<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue';
import { useTheme } from '../theme/ThemeDefine';
import Tabs, { type TabsItemData, type TabsProps } from '../nav/Tabs.vue';
import SimpleList from '../list/SimpleList.vue';
import { DynamicSize } from '../theme/ThemeTools';
import LoadingPage from '../display/loading/LoadingPage.vue';
import Empty from '../feedback/Empty.vue';
import { getCascaderText } from './CascaderUtils';

const themeContext = useTheme();

const headerStyle = themeContext.useThemeStyle({
  marginLeft: DynamicSize('SimpleListItemPaddingHorizontal', 35),
  marginRight: DynamicSize('SimpleListItemPaddingHorizontal', 35),
})

export interface CascaderItem extends Record<string, any> {
  /**
   * 选项的文本
   */
  text: string;
  /**
   * 选项的值
   */
  value: string|number;
  /**
   * 选项是否禁用
   */
  disabled?: boolean;
  /**
   * 选项的颜色
   */
  color?: string;
  /**
   * 子选项
   */
  children?: CascaderItem[];
}

export interface CascaderProps {
  /**
   * 选中的值
   */
  modelValue: (number|string)[],
  /**
   * 数据
   */
  data: CascaderItem[];
  /**
   * 是否在选择完成后直接关闭。
   * @default true
   */
  autoConfirm?: boolean,
  /**
   * 列表的高度rpx
   * @default 700
   */
  listHeight?: number|string;
  /**
   * 选项的文本键名
   * @default 'text'
   */
  textKey?: string,
  /**
   * 选项的值键名
   * @default 'value'
   */
  valueKey?: string,
  /**
   * 选项的子选项键名
   * @default 'children'
   */
  childrenKey?: string,
  /**
   * Tab组件的自定义选项
   */
  tabProps?: Omit<TabsProps, 'tabs' | 'currentIndex'>,
  /**
   * 异步加载数据。
   * 如果你需要异步加载数据，当选项打开后并且 children 为空数组时，会调用该函数加载数据。
   * * 注：当一个条目的数据已加载后，它会缓存到 children 数组中再次点击不会再加载数据，若要刷新，可手动清空 children 数组。
   */
  asyncLoadData?: (group: CascaderItem, level: number) => Promise<CascaderItem[]>;
  /**
   * 最大选择层级。在异步加载数据时建议设置最大层级，否则将尝试每一级都继续加载数据。
   * @default undefined
   */
  maxSelectLevel?: number;
}

const emit = defineEmits([ 'update:modelValue', 'selectTextChange', 'pickEnd' ]);
const props = withDefaults(defineProps<CascaderProps>(), {
  autoConfirm: true,
  listHeight: 700,
  tabProps: () => ({
    width: 750 - 70,
  }),
  textKey: 'text',
  valueKey: 'value',
  childrenKey: 'children',
});

const headerTabCurrent = ref(0);
const headerTabs = computed<TabsItemData[]>(() => {
  const tabs: TabsItemData[] = [];
  let currentGroup : CascaderItem[]|undefined = props.data;
  let i = 0;
  for (; i < props.modelValue.length; i++) {
    const item : CascaderItem|undefined = currentGroup?.find(item => item[props.valueKey] === props.modelValue[i]);
    if (item) {
      tabs.push({
        text: item[props.textKey],
        data: item[props.valueKey],
        width: -1,
      })
      currentGroup = item[props.childrenKey];
    }
  }
  if ((currentGroup !== undefined && currentGroup.length > 0) || canLoadLevel(i)) {
    tabs.push({
      text: '请选择',
      data: null,
      width: -1,
    });
  }
  return tabs;
});

function canLoadLevel(level: number) {
  return (props.maxSelectLevel === undefined || level < props.maxSelectLevel) && props.asyncLoadData;
}

const currentDataDynamicLoading = ref(false);
const currentDataDynamicLoadErrorText = ref('');
const currentDataDynamicLoad = ref<CascaderItem[]>([]);
const currentData = computed(() => {
  let currentGroup = props.data;
  for (let i = 0; i < headerTabCurrent.value; i++) {
    const item = currentGroup.find(item => item[props.valueKey] === props.modelValue[i]);
    if (item) {
      currentGroup = item[props.childrenKey] || [];
      if (currentGroup.length === 0 && props.asyncLoadData) {
        loadAsyncData(item);
        return currentDataDynamicLoad.value;
      }
    }
  }
  return currentGroup;
});
const currentCheckedItem = computed(() => {
  return currentData.value.find(item => item[props.valueKey] === props.modelValue[headerTabCurrent.value]);
});

function updateText(value: (number|string)[]) {
  emit('selectTextChange', getCascaderText(
    value, 
    props.valueKey, 
    props.textKey, 
    props.childrenKey, 
    props.data
  ));
}
async function loadAsyncData(group: CascaderItem) {
  if (props.asyncLoadData) {
    currentDataDynamicLoading.value = true;
    props.asyncLoadData(group, headerTabCurrent.value)
      .then(data => {
        group[props.childrenKey] = data;
        currentDataDynamicLoad.value = data;
        currentDataDynamicLoadErrorText.value = '';
        console.log(group);
      }).catch((e) => {
        console.error('asyncLoadData failed', e);
        currentDataDynamicLoadErrorText.value = '' + e;
      })
      .finally(() => {
        currentDataDynamicLoading.value = false;
      });
  }
}
function loadValue() {
  headerTabCurrent.value = Math.min(props.modelValue.length, headerTabs.value.length - 1);
}
function handleItemClick(item: CascaderItem) {
  let values = props.modelValue;
  if (values.length > headerTabCurrent.value)
    values = values.slice(0, headerTabCurrent.value);
  values = values.concat([item[props.valueKey]]);
  emit('update:modelValue', values);
  updateText(values);
  const nextGroup = item[props.childrenKey];
  if (
    (!nextGroup || nextGroup.length === 0)
    && !canLoadLevel(headerTabCurrent.value + 1)
  )
    emit('pickEnd');
}
function handleTabChange(v: number) {
  headerTabCurrent.value = v;
}

watch(() => props.modelValue, (v) => {
  loadValue();
  updateText(v);
})
onMounted(() => {
  loadValue();
  updateText(props.modelValue);
})

defineOptions({
  options: {
    styleIsolation: "shared",
    virtualHost: true,
  }
})
</script>

<style lang="scss">
.nana-form-cascader {
  position: relative;
  display: flex;
  flex-direction: column;
}
</style>