<template>
  <FixedVirtualList
    :data="groupedData.list"
    dataKey="id"
    :containerStyle="innerStyle"
    :itemSize="itemHeight"
    :bufferSize="2"
    :scrollTop="scrollCurrentValue"
    @scroll="handleScroll"
    v-bind="$attrs"
  >
    <template #list="{ visibleItems, className, itemStyle }">
      <slot 
        name="list" 
        :visibleItems="visibleItems"
        :className="className"
        :itemStyle="itemStyle"
        :onItemPress="onItemPress"
      >
        <view 
          v-for="(row, index) in visibleItems" 
          :key="row.key"
          :style="itemStyle"
          :class="className"
        > 
          <slot v-if="row.item.isHeader" name="header" :header="row.item.header" :id="row.item.id">
            <text 
              :id="row.item.id"
              :style="{ 
                ...themeStyles.header.value,
                ...headerStyle,
              }"
            >
              {{ row.item.header }}
            </text>
          </slot>
          <slot v-else name="item" :item="row.item.data" :index="index" :id="row.item.id">
            <SimpleListItem
              :id="row.item.id"
              :item="row.item.data"
              :index="index"
              :dataDisplayProp="dataDisplayProp"
              :colorProp="colorProp"
              :disabledProp="disabledProp"
              :showCheck="mode !== 'select'"
              :checked="checkedList.indexOf(row.item) >= 0"
              @click="onItemPress(row.item, index)"
            >
              <template v-if="$slots.itemContent" #itemContent>
                <slot name="itemContent" :item="row.item.data" :index="index" :id="row.item.id" />
              </template>
            </SimpleListItem>
          </slot>
        </view>
      </slot>
    </template>
    <template #empty>
      <slot name="empty">
        <Empty :description="emptyText" />
      </slot>
    </template>
    <template #inner>
      <IndexBar
        :activeIndex="activeIndex"
        :data="groupedData.index"
        @drag="handleDrag"
      />
    </template>
  </FixedVirtualList>
</template>

<script setup lang="ts" generic="T">
import { computed, nextTick, provide, ref, type Ref } from 'vue';
import { useTheme, type TextStyle, type ViewStyle } from '../theme/ThemeDefine';
import { DynamicColor, DynamicSize, DynamicSize2, DynamicVar } from '../theme/ThemeTools';
import type { CheckBoxDefaultButtonProps } from '../form/CheckBoxDefaultButton.vue';
import type { SimpleListContext } from './SimpleList.vue';
import Empty from '../feedback/Empty.vue';
import FixedVirtualList from './FixedVirtualList.vue';
import SimpleListItem from './SimpleListItem.vue';
import IndexBar from '../nav/IndexBar.vue';

export interface IndexedListGrouperedData<T> {
  isHeader?: boolean,
  id: string,
  header?: string,
  headerIndex?: number,
  data?: T
}

export interface SimpleListProps<T> {
  /**
   * 分组标题的自定义样式
   */
  groupStyle?: ViewStyle;
  /**
   * 分组标题文字的自定义样式
   */
  groupTextStyle?: TextStyle;
  /**
   * 条目的自定义样式
   */
  itemStyle?: ViewStyle;
  /**
   * 条目文字的自定义样式
   */
  textStyle?: TextStyle;
  /**
   * 选中的条目的自定义样式
   */
  checkedItemStyle?: ViewStyle;
  /**
   * 选中的条目文字的自定义样式
   */
  checkedTextStyle?: TextStyle;
  /**
   * 空数据时显示的文字
   */
  emptyText?: string,
  /**
   * 源数据
   */
  data: T[],
  /**
   * 数据项的唯一键名，用于vue循环优化
   */
  dataKey?: string,
  /**
   * 显示数据的prop，如果为空，则尝试直接把数据当 string 显示。
   */
  dataDisplayProp?: string,
  /**
   * 控制数据条目颜色的字段名称，为空则使用默认颜色。
   */
  colorProp?: string,
  /**
   * 控制是否禁用数据条目的字段名称，为空则不禁用。
   */
  disabledProp?: string,
  /**
   * 每个条目的高度px
   * @default 40
   */
  itemHeight?: number;
  /**
   * 分组标题的高度px
   * @default 30
   */
  headerHeight?: number;
  
  /**
   * 对数据进行分组。每个条目会调用一次这个回调，你需要在此回调中返回此条目所在分组的名字。
   */
  groupDataBy: (item: T) => string,
  /**
   * 对组进行排序
   */
  sortGroup?: (headers: string[]) => string[],

  /**
   * 列表的选择模式
   *
   * * select 点击选择模式
   * * single-check 单选选择模式，条目右边有选择框
   * * mulit-check 多选选择模式，条目右边有选择框
   * @default 'select'
   */
  mode?: 'select'|'single-check'|'mulit-check',
  /**
   * 当列表显示选择框时，选择框的自定义属性
   * @default {
   *   borderColor: 'border.cell',
   *   checkColor: 'white',
   *   color: 'primary',
   *   size: 20,
   *   iconSize: mode === 'single-check' ? 10 : 16,
   *   type: mode === 'single-check' ? 'radio' : 'icon',
   *   disabled: false,
   * }
   */
  checkProps?: CheckBoxDefaultButtonProps,
  /**
   * 当用使用选择框模式时，默认选中条目
   * @default []
   */
  defaultSelect?: any[],

  innerStyle?: ViewStyle;
}

const props = withDefaults(defineProps<SimpleListProps<T>>(), {
  mode: 'select',
  virtual: false,
  itemHeight: 40,
  headerHeight: 30,
});
const emit = defineEmits([ 'itemClick', 'selectedItemChanged' ]);

const themeContext = useTheme();
const themeStyles = themeContext.useThemeStyles({
  list: {
    backgroundColor: DynamicColor('IndexedListBackgroundColor', 'white'),
  },
  header: {
    display: 'flex',
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: DynamicColor('IndexedListHeaderBackgroundColor', 'background.page'),
    padding: DynamicSize2('IndexedListHeaderPaddingVertical', 'IndexedListHeaderPaddingHorizontal', 0, 30),
    fontSize: DynamicSize('IndexedListHeaderFontSize', 24),
    color: DynamicColor('IndexedListHeaderColor', 'text.second'),
    position: 'sticky',
  },
  item: {
    display: 'flex',
    flexDirection: 'row',
    alignItems: 'center',
    paddingLeft: DynamicSize('IndexedListItemPaddingHorizontal', 30),
    paddingRight: DynamicSize('IndexedListItemPaddingHorizontal', 30),
    fontSize: DynamicSize('IndexedListItemFontSize', 28),
    borderTopWidth: DynamicSize('IndexedListItemBorderTopWidth', 0),
    borderBottomStyle: 'solid',
    borderBottomWidth: DynamicSize('IndexedListItemBorderBottomWidth', 2),
    borderColor: DynamicColor('IndexedListItemBorderColor', 'border.cell'),
    color: DynamicColor('IndexedListItemColor', 'text.content'),
    backgroundColor: DynamicColor('IndexedListItemBackgroundColor', 'white'),
  },
  itemText: {

  },
  itemTextChecked: {
    fontSize: DynamicSize('IndexedListItemCheckedTextFontSize', 28),
    fontWeight: DynamicVar('IndexedListItemCheckedTextFontWeight', 'bold'),
    color: DynamicColor('IndexedListItemCheckedTextColor', 'primary'),
  },
});

let flushLock = false;


const activeIndex = ref(-1);
const scrollCurrentValue = ref(0);

function handleScroll(e: any) {
  if (scrollCurrentValue.value != e.detail.scrollTop) {
    scrollCurrentValue.value = e.detail.scrollTop;
    if (!flushLock) {
      const index = Math.floor(scrollCurrentValue.value / props.itemHeight);
      for (let i = index; i >= 0; i--) {
        if (groupedData.value.list[i].isHeader) {
          activeIndex.value = groupedData.value.list[i].headerIndex || -1;
          break;
        }
      }
    }
  }
}

function handleDrag(v: number) {
  flushLock = true;
  nextTick(() => {
    activeIndex.value = v;
    const index = groupedData.value.list.findIndex(p => p.header === groupedData.value.index[v])
    if (index >= 0)
      scrollCurrentValue.value = index * props.itemHeight;
    nextTick(() => {
      flushLock = false;
    });
  });
}

const headerStyle = computed(() => ({
  ...themeStyles.header.value,
  height: `${props.headerHeight}px`,
} as ViewStyle));
const itemStyle = computed(() => ({
  ...themeStyles.item.value,
  height: `${props.itemHeight}px`,
  ...props.itemStyle,
} as ViewStyle));
const textStyle = computed(() => ({
  ...themeStyles.itemText.value,
  ...props.textStyle,
} as ViewStyle));
const checkedItemStyle = computed(() => ({
  ...themeStyles.item.value,
  height: themeContext.resolveSize(props.itemHeight),
  ...props.checkedItemStyle,
} as ViewStyle));
const checkedTextStyle = computed(() => ({
  ...themeStyles.itemText.value,
  ...themeStyles.itemTextChecked.value,
  ...props.checkedTextStyle,
} as ViewStyle));
const checkProps = computed(() => ({
  borderColor: 'border.cell',
  checkColor: 'white',
  color: 'primary',
  size: 40,
  iconSize: props.mode === 'single-check' ? 20 : 32,
  type: props.mode === 'single-check' ? 'radio' : 'icon',
  disabled: false,
  ...props.checkProps,
} as CheckBoxDefaultButtonProps));
const pressedColor = computed(() => {
  return themeContext.resolveThemeColor('SimpleListItemPressedColor', 'pressed.white')!;
});

const groupedData = computed(() => {
  const map = new Map<string, T[]>();
  //归组数据
  props.data.forEach((model) => {
    const groupKey = props.groupDataBy(model);
    let arr = map.get(groupKey);
    if (!arr){
      arr = [];
      map.set(groupKey, arr);
    }
    arr.push(model);
  });

  //填充数据
  const arr = [] as IndexedListGrouperedData<T>[];
  const arrIndex = [] as string[];

  //排序分组
  const mapKeys = Array.from(map.keys());
  const groups = props.sortGroup ? props.sortGroup(mapKeys) : mapKeys;

  let i = 0, j = 0;
  for (const k of groups) {
    const v = map.get(k);
    if (!v) continue;
    arr.push({
      id: `indexListNav${k}`,
      isHeader: true,
      headerIndex: j,
      header: k,
    });
    v.forEach((c) => {
      arr.push({
        id: `indexListItem${i++}`,
        data: c,
      });
    });
    arrIndex.push(k);
    j++;
  }
  return {
    list: arr,
    index: arrIndex,
  };
});

provide<SimpleListContext>('SimpleListContext', {
  itemStyle,
  textStyle,
  checkedItemStyle,
  checkedTextStyle,
  checkProps,
  pressedColor,
});

const checkedList = ref(props.defaultSelect || []) as Ref<IndexedListGrouperedData<T>[]>;

function onItemPress(item: IndexedListGrouperedData<T>, index: number) {
  if (props.mode === 'single-check') {
    checkedList.value = ([ item ]);
    emit('selectedItemChanged', [ item.data ]);
  } else if (props.mode === 'mulit-check') {
    checkedList.value = ((prev) => {
      let arr : IndexedListGrouperedData<T>[];
      if (prev.indexOf(item) >= 0)
        arr = prev.filter(k => k !== item);
      else
        arr = prev.concat([ item ]);

      setTimeout(() => {
        emit('selectedItemChanged', arr.map(p => p.data));
      }, 100);
      return arr;
    })(checkedList.value);
  }
  emit('itemClick', item.data, index);
}
</script>