<script lang="tsx">
import Bubble from './xt-bubble.vue';
import BubbleContextProvider from './context';
import useDisplayData from './hooks/useDisplayData';
import useListData from './hooks/useListData';
import useState from '@/hooks/useState';
import pickAttrs from '@/utils/pick-attrs';
import { useEventCallback } from '@/hooks/useEventCallback';
import type { BubbleListProps, BubbleRef, RolesType } from './types';
import {
  computed,
  defineComponent,
  mergeProps,
  nextTick,
  onWatcherCleanup,
  ref,
  unref,
  useAttrs,
  watch,
  watchPostEffect,
} from 'vue';

export default defineComponent({
  name: 'BubbleList',
  inheritAttrs: false,
  // 正确声明 props，提供默认值，确保 TSX 下默认值生效
  props: {
    autoScroll: {
      type: Boolean,
      default: true,
    },
    items: {
      type: Array as () => BubbleListProps['items'],
      required: true,
    },
    roles: {
      type: Object as () => RolesType,
      default: () => ({} as RolesType),
    },
    rootClassName: {
      type: String,
      default: '',
    },
    style: {
      type: Object as () => Record<string, any>,
      default: () => ({}),
    },
    class: {
      type: [String, Array, Object] as unknown as () => any,
      default: '',
    },
  },
  setup(props, { attrs, slots, expose }) {
    const passThroughAttrs = useAttrs();

    const TOLERANCE = 1;

    const domProps = computed(() => pickAttrs(mergeProps(props as any, passThroughAttrs)));

    const items = ref(props.items);
    const roles = ref(props.roles as RolesType);
    watch(
      () => props.items,
      (val) => (items.value = val),
      { immediate: true },
    );
    watch(
      () => props.roles,
      (val) => (roles.value = val as RolesType),
      { immediate: true },
    );

    const listRef = ref<HTMLDivElement | null>(null);
    const bubbleRefs = ref<Record<string | number, BubbleRef>>({});

    const listPrefixCls = 'xt-bubble-list';

    const [initialized, setInitialized] = useState(false);
    watchPostEffect(() => {
      setInitialized(true);
      onWatcherCleanup(() => setInitialized(false));
    });

    // data
    const mergedData = useListData(items as any, roles as any);
    const [displayData, onTypingComplete] = useDisplayData(mergedData);

    // scroll
    const [scrollReachEnd, setScrollReachEnd] = useState(true);
    const [updateCount, setUpdateCount] = useState(0);
    // 首次挂载后仅自动滚动一次
    const didInitialAutoScroll = ref(false);

    const onInternalScroll = (e: Event) => {
      const target = e.target as HTMLElement;
      setScrollReachEnd(target.scrollHeight - Math.abs(target.scrollTop) - target.clientHeight <= TOLERANCE);
    };

    watch([updateCount, scrollReachEnd, listRef], () => {
      if (props.autoScroll && unref(listRef) && unref(scrollReachEnd)) {
        nextTick(() => {
          console.log('自然滚动');
          unref(listRef)!.scrollTo({ top: unref(listRef)!.scrollHeight });
        });
      }
    });

    watch(
      () => unref(displayData).length,
      (newLen, oldLen) => {
        if (!props.autoScroll) return;
        // 首次渲染：当有内容时滚到底部一次
        if (!didInitialAutoScroll.value && newLen > 0) {
          console.log('首次渲染滚动到底部-----');
          scrollToBottom('auto');
          didInitialAutoScroll.value = true;
          return;
        }
        // 新增内容且当前在底部：继续粘底
        if (oldLen !== undefined && newLen > (oldLen ?? 0) && unref(scrollReachEnd)) {
          scrollToBottom();
        }
      },
      { immediate: true },
    );

    const onBubbleUpdate = useEventCallback<void>(() => {
      if (props.autoScroll) setUpdateCount(unref(updateCount) + 1);
    });
    const context = computed(() => ({ onUpdate: onBubbleUpdate }));

    // 暴露控制方法
    const abortTypingByKey = (key: string | number) => {
      bubbleRefs.value[key]?.abortTyping?.();
    };
    // 通用：滚动到底部
    const scrollToBottom = (behavior: ScrollBehavior = 'smooth') => {
      nextTick(() => {
        requestAnimationFrame(() => {
          const el = unref(listRef);
          if (el) {
            el.scrollTo({ top: el.scrollHeight, behavior });
            setScrollReachEnd(true);
          }
        });
      });
    };
    // 对外暴露能力
    expose({
      nativeElement: listRef,
      abortTypingByKey,
      scrollTo: (info: any) => {
        unref(listRef)?.scrollTo?.(info);
      },
      scrollToBottom,
    });

    return () => (
      <BubbleContextProvider value={context.value}>
        <div
          {...domProps.value}
          class={[
            listPrefixCls,
            props.rootClassName,
            props.class,
            { [`${listPrefixCls}-reach-end`]: unref(scrollReachEnd) },
          ]}
          style={props.style}
          ref={listRef}
          onScroll={onInternalScroll}
        >
          {unref(displayData).map(({ key, onTypingComplete: onTypingCompleteBubble, ...bubble }) => (
            <Bubble
              {...bubble}
              avatar={slots.avatar ? () => slots.avatar?.({ item: { key, ...bubble } }) : bubble.avatar}
              header={slots.header?.({ item: { key, ...bubble } }) ?? bubble.header}
              footer={slots.footer?.({ item: { key, ...bubble } }) ?? bubble.footer}
              loadingRender={slots.loading ? () => slots.loading({ item: { key, ...bubble } }) : bubble.loadingRender}
              content={slots.message?.({ item: { key, ...bubble } }) ?? bubble.content}
              key={key}
              ref={(node: any) => {
                if (node) {
                  bubbleRefs.value[key] = node as BubbleRef;
                } else {
                  delete bubbleRefs.value[key];
                }
              }}
              typing={unref(initialized) ? bubble.typing : false}
              onTypingComplete={() => {
                onTypingCompleteBubble?.();
                onTypingComplete(key);
              }}
            />
          ))}
        </div>
      </BubbleContextProvider>
    );
  },
});
</script>

<style scoped lang="scss">
@import './style.scss';
</style>
