<template>
  <div
    :class="computedClassList"
    @click="onMessageListBackgroundClick"
  >
    <div class="tui-chat-main">
      <scroll-view
        id="messageScrollList"
        class="tui-message-list"
        scroll-y="true"
        :scroll-top="scrollTop"
        :scroll-into-view="`tui-${historyFirstMessageID}`"
        @scroll="handleScrollListScroll"
      >
        <p
          v-if="messageList.length && !isCompleted"
          class="message-more"
          @click="getHistoryMessageList"
        >
          {{ TUITranslateService.t("TUIChat.查看更多") }}
        </p>
        <li
          v-for="(item, index) in messageList"
          :id="`tui-${item.ID}`"
          :key="item.vueForRenderKey"
          :class="'message-li ' + item.flow"
        >
          <MessageTimestamp
            :currTime="item.time"
            :prevTime="index > 0 ? messageList[index - 1].time : 0"
          />
          <div
            class="message-item"
          >
            <div
              v-if="!item.isRevoked && !isPluginMessage(item)"
              :id="`msg-bubble-${item.ID}`"
              class="message-bubble-container"

            >
              <MessageBubble
                :messageItem="deepCopy(item)"
                :content="item.getMessageContent()"
                :isAudioPlayed="audioPlayedMapping[item.ID]"
                :blinkMessageIDList="blinkMessageIDList"
                @resendMessage="resendMessage(item)"
                @blinkMessage="blinkMessage"
                @scrollTo="scrollTo"
                @handleToggleMessageItem="handleToggleMessageItem($event, item, index, true)"
                @handleTouchStart="handleH5LongPress($event, item, index, 'touchstart')"
                @handleTouchEnd="handleH5LongPress($event, item, index, 'touchend')"
                :enableFeedback="props.enableFeedback"
                :enableAINote="props.enableAINote"
                @like="onLike"
                @dislike="onDislike"
              >
                <MessageThinking v-if="isThinkingMessage(item)"/>
                <MessageText
                  v-else-if="item.type === TYPES.MSG_TEXT"
                  :content="item.getMessageContent()"
                  :flow="item.flow"
                />
                <ProgressMessage
                  v-else-if="item.type === TYPES.MSG_IMAGE"
                  :content="item.getMessageContent()"
                  :messageItem="deepCopy(item)"
                >
                  <MessageImage
                    :content="item.getMessageContent()"
                    :messageItem="item"
                    @previewImage="handleImagePreview(index)"
                  />
                </ProgressMessage>
                <ProgressMessage
                  v-else-if="item.type === TYPES.MSG_VIDEO"
                  :content="item.getMessageContent()"
                  :messageItem="deepCopy(item)"
                >
                  <MessageVideo
                    :content="item.getMessageContent()"
                    :messageItem="item"
                  />
                </ProgressMessage>
                <MessageAudio
                  v-else-if="item.type === TYPES.MSG_AUDIO"
                  :content="item.getMessageContent()"
                  :messageItem="item"
                  :broadcastNewAudioSrc="broadcastNewAudioSrc"
                  @setAudioPlayed="setAudioPlayed"
                  @getGlobalAudioContext="getGlobalAudioContext"
                />
                <MessageRecord
                  v-else-if="item.type === TYPES.MSG_MERGER"
                  :renderData="item.payload"
                  :messageItem="item"
                  @assignMessageIDInUniapp="assignMessageIDInUniapp"
                />
                <MessageFile
                  v-else-if="item.type === TYPES.MSG_FILE"
                  :content="item.getMessageContent()"
                />
                <MessageFace
                  v-else-if="item.type === TYPES.MSG_FACE"
                  :content="item.getMessageContent()"
                />
                <MessageLocation
                  v-else-if="item.type === TYPES.MSG_LOCATION"
                  :content="item.getMessageContent()"
                />
                <MessageCustom
                  v-else-if="item.type === TYPES.MSG_CUSTOM"
                  :content="item.getMessageContent()"
                  :messageItem="item"
                />
              </MessageBubble>
            </div>
            <MessagePlugin
              v-else-if="!item.isRevoked && isPluginMessage(item)"
              :message="deepCopy(item)"
              :blinkMessageIDList="blinkMessageIDList"
              @resendMessage="resendMessage"
              @handleToggleMessageItem="handleToggleMessageItem"
              @handleH5LongPress="handleH5LongPress"
              @heightChanged="onHeightChanged"
              @messageSent="onMessageSent"
              :enableFeedback="props.enableFeedback"
              :enableAINote="props.enableAINote"
              @like="onLike"
              @dislike="onDislike"
            />
            <MessageRevoked
              v-else
              :isEdit="item.type === TYPES.MSG_TEXT"
              :messageItem="item"
              @messageEdit="handleEdit(item)"
            />
            <!-- message tool -->
            <MessageTool
              v-if="item.ID === toggleID"
              :class="{
                'message-tool': true,
                'message-tool-out': item.flow === 'out',
                'message-tool-in': item.flow === 'in',
              }"
              :messageItem="item"
            />
          </div>
        </li>
      </scroll-view>
      <!-- scroll button -->
      <ScrollButton
        ref="scrollButtonInstanceRef"
        @scrollToLatestMessage="scrollToLatestMessage"
        @scrollNearToBottom="scrollNearToBottom"
      />
      <Dialog
        v-if="reSendDialogShow"
        :show="reSendDialogShow"
        :isH5="true"
        :center="true"
        :isHeaderShow="false"
        @submit="resendMessageConfirm()"
        @update:show="(e) => (reSendDialogShow = e)"
      >
        <p class="delDialog-title">
          {{ TUITranslateService.t("TUIChat.确认重发该消息？") }}
        </p>
      </Dialog>
      <!-- simple message list -->
      <Drawer
        :visible="isShowSimpleMessageList"
        :overlayColor="'transparent'"
        :popDirection="'right'"
      >
        <SimpleMessageList
          :style="{ height: '100%' }"
          :isMounted="isShowSimpleMessageList"
          :messageID="simpleMessageListRenderMessageID"
          @closeOverlay="isShowSimpleMessageList = false"
        />
      </Drawer>
      <BottomQuickOrder
        class="bottom-quick-order"
        v-if="computedShowBottomQuickOrder"
        :bottomQuickOrder="props.bottomQuickOrder"
        @closeOrder="closeBottomQuickOrder"
        @sendOrder="sendBottomQuickOrder"
      />
    </div>
  </div>
</template>

<script lang="ts" setup>
import vue from '../../../adapter-vue';
import TUIChatEngine, {
  IMessageModel,
  TUIStore,
  StoreName,
  TUITranslateService,
  TUIChatService,
} from '../../../@aidesk/uikit-engine';
import {
  setInstanceMapping,
  getBoundingClientRect,
  getScrollInfo,
} from '@tencentcloud/universal-api';
import SimpleMessageList from './message-elements/simple-message-list/index.vue';
import MessageText from './message-elements/message-text.vue';
import MessageImage from './message-elements/message-image-uniapp.vue';
import MessageAudio from './message-elements/message-audio-uniapp.vue';
import MessageRecord from './message-elements/message-record/index.vue';
import MessageFile from './message-elements/message-file.vue';
import MessageFace from './message-elements/message-face.vue';
import MessageCustom from './message-elements/message-custom.vue';
import MessageBubble from './message-elements/message-bubble-uniapp.vue';
import MessageLocation from './message-elements/message-location.vue';
import MessageTimestamp from './message-elements/message-timestamp.vue';
import MessageVideo from './message-elements/message-video-uniapp.vue';
import MessageTool from './message-tool/index-uniapp.vue';
import MessageRevoked from './message-tool/message-revoked.vue';
import MessagePlugin from './message-elements/message-desk/message-plugin-uniapp.vue';
import MessageThinking from './message-elements/message-thinking.vue';
import ScrollButton from './scroll-button/index.vue';
import { isPluginMessage } from './message-elements/message-desk/index';
import Dialog from '../../common/Dialog/index.vue';
import Drawer from '../../common/Drawer/index.vue';
import { Toast, TOAST_TYPE } from '../../common/Toast/index-uniapp';
import ProgressMessage from '../../common/ProgressMessage/index.vue';
import BottomQuickOrder from './bottom-quick-order/index.vue';
import {
  isEnabledMessageReadReceiptGlobal,
  deepCopy,
  isNonEmptyObject,
  throttle,
  updateCustomStore,
} from '../../../utils/utils';
import {isH5, isTikTok, isUniFrameWork} from '../../../utils/env';
import chatStorage from '../../../utils/chatStorage';
import {IAudioContext, ToolbarDisplayType} from '../../../interface';
import {isMessageInvisible, isThinkingMessage, isThinkingMessageOverTime, JSONToObject, isTransferMessageWithoutDesc } from '../../../utils/index';
import { isCustomerConversation } from '../../../index';
import { CUSTOM_MESSAGE_SRC } from "../../../constant";
import { QuickOrderModel } from '../../../interface';
import Log from '../../../utils/logger';
import state from "../../../utils/state";

const { ref, nextTick, onMounted, onUnmounted, getCurrentInstance, computed } = vue;

interface IProps {
  bottomQuickOrder?: QuickOrderModel;
  showBottomQuickOrder: boolean;
  toolbarButtonList?: any;
  enableFeedback: number;
  enableAINote: number;
}
const props = withDefaults(defineProps<IProps>(), {
  showBottomQuickOrder: false,
  toolbarButtonList: () => [],
  enableFeedback: 0,
  enableAINote: 1,
});

interface IEmits {
  (e: 'closeInputToolBar'): void;
  (e: 'handleEditor', message: IMessageModel, type: string): void;
  (key: 'closeBottomQuickOrder'): void;
  (key: 'like', messageInfo: Object): void;
  (key: 'dislike', messageInfo: Object): void;
}

const emits = defineEmits<IEmits>();

let selfAddValue = 0;
let observer: any = null;
const sentReceiptMessageID = new Set<string>();
const thisInstance = getCurrentInstance()?.proxy || getCurrentInstance();

const messageList = ref<IMessageModel[]>([]);
const isCompleted = ref(false);
const currentConversationID = ref('');
const toggleID = ref('');
const scrollTop = ref(5000); // The initial number of messages is 15, and the maximum message height is 300.
const TYPES = ref(TUIChatEngine.TYPES);
const isLoadingMessage = ref(false);
const isLongpressing = ref(false);
const blinkMessageIDList = ref<string[]>([]);
const messageTarget = ref<IMessageModel>();
const scrollButtonInstanceRef = ref<InstanceType<typeof ScrollButton>>();
const historyFirstMessageID = ref<string>('');
const isShowSimpleMessageList = ref<boolean>(false);
const simpleMessageListRenderMessageID = ref<string>();
const audioPlayedMapping = ref<Record<string, boolean>>({});
const displayType = ref<ToolbarDisplayType>('none');
const computedClassList = computed(() => {
  // 注意！vue2 -> 小程序，:class绑定对象有兼容性问题，改为绑定数组
  const classList = ['tui-chat']; // 默认添加 'tui-chat'
  if (isUniFrameWork) {
    classList.push('tui-chat-h5'); // 条件性地添加 'tui-chat-h5'
  }
  if (isTikTok) {
    let isToolbarButtonListSet = Array.isArray(props.toolbarButtonList) && props.toolbarButtonList.length > 0;
    if (isToolbarButtonListSet) {
      if (displayType.value === 'none') {
        classList.push('adjust-height-in-tt')
      } else if (displayType.value === 'emojiPicker') {
        classList.push('adjust-height-emoji-in-tt');
      } else if (displayType.value === 'tools') {
        classList.push('adjust-height-tools-in-tt');
      }
    } else {
      if (displayType.value === 'none') {
        classList.push('adjust-height-no-toolbar-button-in-tt')
      } else if (displayType.value === 'emojiPicker') {
        classList.push('adjust-height-no-toolbar-button-emoji-in-tt');
      } else if (displayType.value === 'tools') {
        classList.push('adjust-height-no-toolbar-button-tools-in-tt');
      }
    }
  }
  return classList;
});

// audio control
const broadcastNewAudioSrc = ref<string>('');

// Resend Message Dialog
const reSendDialogShow = ref(false);
const resendMessageData = ref();

const scrollToBottom = () => {
  scrollTop.value += 300;
  // Solve the issue where swiping to the bottom for the first time after packaging Uniapp into an app has a delay,
  // which can be set to 300 ms.
  const timer = setTimeout(() => {
    scrollTop.value += 1;
    clearTimeout(timer);
  }, 300);
};

const onCurrentConversationIDUpdated = (conversationID: string) => {
  currentConversationID.value = conversationID;

  if (Object.keys(audioPlayedMapping.value).length > 0) {
    // Synchronize storage about whether the audio has been played when converstaion switched
    chatStorage.setChatStorage('audioPlayedMapping', audioPlayedMapping.value);
  }

  preloadEmoji();
};

const isUniFormShow = ref(false);
const computedShowBottomQuickOrder = computed(() => {
  // 当 BottomPopup 展示的时候，隐藏 BottomQuickOrder
  return !isUniFormShow.value && isNonEmptyObject(props.bottomQuickOrder) && props.showBottomQuickOrder
});

onMounted(() => {
  // Retrieve the information about whether the audio has been played from localStorage
  audioPlayedMapping.value
    = chatStorage.getChatStorage('audioPlayedMapping') || {};

  TUIStore.watch(StoreName.CONV, {
    currentConversationID: onCurrentConversationIDUpdated,
  });

  TUIStore.watch(StoreName.CHAT, {
    messageList: onMessageListUpdated,
    messageSource: onMessageSourceUpdated,
    isCompleted: onChatCompletedUpdated,
    newMessageList: onNewMessageList,
  });

  TUIStore.watch(StoreName.CUSTOM, {
    formPopup: handleFormPopup,
  });

  setInstanceMapping('messageList', thisInstance);

  uni.$on('scroll-to-bottom', scrollToLatestMessage);
});

onUnmounted(() => {
  TUIStore.unwatch(StoreName.CHAT, {
    messageList: onMessageListUpdated,
    messageSource: onMessageSourceUpdated,
    isCompleted: onChatCompletedUpdated,
    newMessageList: onNewMessageList,
  });

  TUIStore.unwatch(StoreName.CONV, {
    currentConversationID: onCurrentConversationIDUpdated,
  });

  TUIStore.unwatch(StoreName.CUSTOM, {
    formPopup: handleFormPopup,
  });

  observer?.disconnect();
  observer = null;

  uni.$off('scroll-to-bottom');

  if (Object.keys(audioPlayedMapping.value).length > 0) {
    // Synchronize storage about whether the audio has been played when the component is unmounted
    chatStorage.setChatStorage('audioPlayedMapping', audioPlayedMapping.value);
  }
});

const handleScrollListScroll = throttle(function (e: Event) {
    scrollButtonInstanceRef.value?.judgeScrollOverOneScreen(e);
  },500
);

function getGlobalAudioContext(
  audioMap: Map<string, IAudioContext>,
  options?: { newAudioSrc: string },
) {
  if (options?.newAudioSrc) {
    broadcastNewAudioSrc.value = options.newAudioSrc;
  }
}

function onNewMessageList(list: IMessageModel[]) {
  list.forEach((message: IMessageModel) => {
    if (message.type === TUIChatEngine.TYPES.MSG_CUSTOM) {
      const data = JSONToObject(message.payload.data);
      const conversationID = message.conversationID;
      if (data) {
        if (data.src === CUSTOM_MESSAGE_SRC.BOT_STATUS) {
          updateCustomStore("canEndConversation", { conversationID, value: true });
          if (data.content.content === 'inBot') {
            updateCustomStore("isInHumanService", { conversationID , value: false });
          }
        } else if (data.src === CUSTOM_MESSAGE_SRC.SEAT_STATUS) {
          if (data.content.command === "updateSeatStatus") {
            updateCustomStore("canEndConversation", { conversationID, value: true });
            if (data.content.content === 'queuing') {
              updateCustomStore("isQueuing", { conversationID, value: data.content.waitingQueueLength });
            } else if (data.content.content === 'inSeat') {
              updateCustomStore("isInHumanService", { conversationID , value: true });
              updateCustomStore("isQueuing", { conversationID, value: -1 });
            } else if (data.content.content === 'outSeat') {
              updateCustomStore("isInHumanService", { conversationID , value: false });
              updateCustomStore("isQueuing", { conversationID, value: -1 });
            }
          }
        } else if (data.src === CUSTOM_MESSAGE_SRC.TYPING_STATE) {
          // if (data.typingStatus === 1) {
          //   updateCustomStore("isTyping", { conversationID , value: true });
          // } else {
          //   updateCustomStore("isTyping", { conversationID , value: false });
          // }
        } else if (data.src === CUSTOM_MESSAGE_SRC.NO_SEAT_ONLINE || data.src === CUSTOM_MESSAGE_SRC.TIMEOUT || data.src === CUSTOM_MESSAGE_SRC.END) {
          updateCustomStore("canEndConversation", { conversationID, value: false });
          updateCustomStore("isQueuing", { conversationID, value: -1 });
        } else if (data.src === CUSTOM_MESSAGE_SRC.GET_FEEDBACK_MENU) {
          updateCustomStore("feedbackTags", data.content.menu);
        }
      }
    }
  });
}

async function onMessageListUpdated(list: IMessageModel[]) {
  if (!isCustomerConversation(currentConversationID.value)) {
    Log.w(`Messages filtered as they are not customer service messages. currentConversationID: ${currentConversationID.value}`);
    return;
  }
  observer?.disconnect();

  messageList.value = list
    .filter((message) => {
      if (isThinkingMessage(message)) {
        return isThinkingMessageOverTime(message);
      }
      if (isTransferMessageWithoutDesc(message)) {
        return false;
      }
      return !message.isDeleted && !isMessageInvisible(message as any);
    })
    .map((message) => {
      message.vueForRenderKey = `${message.ID}`;
      return message;
    });

  const newLastMessage = messageList.value?.[messageList.value?.length - 1];
  if (messageTarget.value) {
    // scroll to target message
    scrollAndBlinkMessage(messageTarget.value);
  } else if (
    !isLoadingMessage.value
    && !(
      scrollButtonInstanceRef.value?.isScrollButtonVisible
      && newLastMessage?.flow === 'in'
    )
  ) {
    // scroll to bottom
    nextTick(() => {
      scrollToBottom();
    });
  }
  nextTick(() => bindIntersectionObserver());
}

async function scrollToLatestMessage() {
  try {
    const { scrollHeight } = await getScrollInfo(
      '#messageScrollList',
      'messageList',
    );
    if (scrollHeight) {
      scrollTop.value === scrollHeight
        ? (scrollTop.value = scrollHeight + 1)
        : (scrollTop.value = scrollHeight);
    } else {
      scrollToBottom();
    }
  } catch (error) {
    scrollToBottom();
  }
  scrollButtonInstanceRef.value?.hideScrollButton();
  // 滚动到底部的时候，及时检查是否要上报已读回执
  bindIntersectionObserver();
}

function scrollNearToBottom() {
  // 滚动到底部的时候，及时检查是否要上报已读回执
  bindIntersectionObserver();
}

async function onMessageSourceUpdated(message: IMessageModel) {
  messageTarget.value = message;
  scrollAndBlinkMessage(messageTarget.value);
}

function scrollAndBlinkMessage(message: IMessageModel) {
  if (
    messageList.value?.some(
      messageListItem => messageListItem?.ID === message?.ID,
    )
  ) {
    nextTick(async () => {
      await scrollToTargetMessage(message);
      await blinkMessage(message?.ID);
      messageTarget.value = undefined;
    });
  }
}

function onChatCompletedUpdated(flag: boolean) {
  isCompleted.value = flag;
}

const getHistoryMessageList = () => {
  isLoadingMessage.value = true;
  const currentFirstMessageID = messageList.value?.[0]?.ID || '';
  TUIChatService.getMessageList().then(() => {
    nextTick(() => {
      // 列表更新后滚动到指定位置
      historyFirstMessageID.value = currentFirstMessageID;
      const timer = setTimeout(() => {
        historyFirstMessageID.value = '';
        isLoadingMessage.value = false;
        clearTimeout(timer);
      }, 500);
    });
  });
};

// toggle message
const handleToggleMessageItem = (
  e: any,
  message: IMessageModel,
  index: number,
  isLongpress = false,
) => {
  if (isLongpress) {
    isLongpressing.value = true;
  }
  toggleID.value = message.ID;
};

// h5 long press
let timer: number;
const handleH5LongPress = (
  e: any,
  message: IMessageModel,
  index: number,
  type: string,
) => {
  if (!isH5) return;
  function longPressHandler() {
    clearTimeout(timer);
    handleToggleMessageItem(e, message, index, true);
  }
  function touchStartHandler() {
    timer = setTimeout(longPressHandler, 500);
  }
  function touchEndHandler() {
    clearTimeout(timer);
  }
  switch (type) {
    case 'touchstart':
      touchStartHandler();
      break;
    case 'touchend':
      touchEndHandler();
      setTimeout(() => {
        isLongpressing.value = false;
      }, 50);
      break;
  }
};

// reedit message
const handleEdit = (message: IMessageModel) => {
  emits('handleEditor', message, 'reedit');
};

const resendMessage = (message: IMessageModel) => {
  reSendDialogShow.value = true;
  resendMessageData.value = message;
};

const handleImagePreview = (index: number) => {
  if (!messageList.value) {
    return;
  }
  const imageMessageIndex: number[] = [];
  const imageMessageList: IMessageModel[] = messageList.value.filter(
    (item, index) => {
      if (
        !item.isRevoked
        && !item.hasRiskContent
        && item.type === TYPES.value.MSG_IMAGE
      ) {
        imageMessageIndex.push(index);
        return true;
      }
      return false;
    },
  );
  uni.previewImage({
    current: imageMessageIndex.indexOf(index),
    urls: imageMessageList.map(
      message => message.payload.imageInfoArray?.[2].url,
    ),
    // #ifdef APP-PLUS
    indicator: 'number',
    // #endif
  });
};

const resendMessageConfirm = () => {
  reSendDialogShow.value = !reSendDialogShow.value;
  const messageModel = resendMessageData.value;
  messageModel.resendMessage();
};

function blinkMessage(messageID: string): Promise<void> {
  return new Promise((resolve) => {
    const index = blinkMessageIDList.value.indexOf(messageID);
    if (index < 0) {
      blinkMessageIDList.value.push(messageID);
      const timer = setTimeout(() => {
        blinkMessageIDList.value.splice(
          blinkMessageIDList.value.indexOf(messageID),
          1,
        );
        clearTimeout(timer);
        resolve();
      }, 3000);
    }
  });
}

function scrollTo(scrollHeight: number) {
  scrollTop.value = scrollHeight;
}

async function bindIntersectionObserver() {
  if (!messageList.value || messageList.value.length === 0) {
    return;
  }

  if (!isEnabledMessageReadReceiptGlobal()) {
    return;
  }

  if (observer) {
    observer.disconnect();
  }
  observer = uni.createIntersectionObserver(thisInstance, {
      threshold: [0.3], // 超过1/4可见时触发，提高灵敏度
      observeAll: true,
      // In Uni-app, the `safetip` is also included, so a negative margin is needed to exclude it.
    }).relativeTo('#messageScrollList', { top: -70 });

  if (observer) {
    observer.observe('.message-li.in .message-bubble-container', (res: any) => {
      // res.intersectionRatio 为0说明元素不可见
      if (sentReceiptMessageID.has(res.id) || res.intersectionRatio === 0) {
        return;
      }
      const matchingMessage = messageList.value.find((message: IMessageModel) => {
        return res.id.indexOf(message.ID) > -1;
      });
      if (matchingMessage) {
        const { needReadReceipt, readReceiptInfo, flow, ID, payload } = matchingMessage;
        // 收到的消息需要已读回执，但我尚未发送
        if (needReadReceipt && flow === 'in' && !readReceiptInfo.isPeerRead) {
          TUIChatService.sendMessageReadReceipt([matchingMessage]);
          sentReceiptMessageID.add(res.id);
          Log.l(`Receipt sent. ID:${ID} payload:${JSON.stringify(payload)}`);
        }
      }
    });
  }
}

async function scrollToTargetMessage(message: IMessageModel) {
  const targetMessageID = message.ID;
  const isTargetMessageInScreen
    = messageList.value
    && messageList.value.some(msg => msg.ID === targetMessageID);
  if (targetMessageID && isTargetMessageInScreen) {
    const timer = setTimeout(async () => {
      try {
        const scrollViewRect = await getBoundingClientRect(
          '#messageScrollList',
          'messageList',
        );
        const originalMessageRect = await getBoundingClientRect(
          '#tui-' + targetMessageID,
          'messageList',
        );
        const { scrollTop } = await getScrollInfo(
          '#messageScrollList',
          'messageList',
        );
        const finalScrollTop
          = originalMessageRect.top
          + scrollTop
          - scrollViewRect.top
          - (selfAddValue++ % 2);
        scrollTo(finalScrollTop);
        clearTimeout(timer);
      } catch (error) {
        // todo
      }
    }, 500);
  } else {
    Toast({
      message: TUITranslateService.t('TUIChat.无法定位到原消息'),
      type: TOAST_TYPE.WARNING,
    });
  }
}

function onMessageListBackgroundClick() {
  toggleID.value = ''; // 点击消息列表区隐藏 MessageTool
  emits('closeInputToolBar');
}

function assignMessageIDInUniapp(messageID: string) {
  simpleMessageListRenderMessageID.value = messageID;
  isShowSimpleMessageList.value = true;
}

function setAudioPlayed(messageID: string) {
  audioPlayedMapping.value[messageID] = true;
}

function onHeightChanged() {
  if (historyFirstMessageID.value || isLoadingMessage.value) {
    return;
  }
  scrollToBottom();
}

function onMessageSent() {
  scrollToBottom();
}

function closeBottomQuickOrder() {
  emits('closeBottomQuickOrder');
}

function sendBottomQuickOrder() {
  if (!props.bottomQuickOrder) {
    return;
  }

  // customField 的 kv 必须强转为 string
  const normalizedQuickOrder: QuickOrderModel = {
    ...props.bottomQuickOrder,
    customField: (props.bottomQuickOrder.customField || []).map(f => ({
      name: f.name ? String(f.name) : '',
      value: f.value ? String(f.value) : '',
    })),
  }

  TUIChatService.sendCustomMessage({
    to: currentConversationID.value.replace('C2C', ''),
    conversationType: 'C2C',
    payload: {
      data: JSON.stringify({
        src: CUSTOM_MESSAGE_SRC.PRODUCT_CARD,
        content: normalizedQuickOrder,
        customerServicePlugin: 0,
      }),
    },
    needReadReceipt: isEnabledMessageReadReceiptGlobal(),
  });
  closeBottomQuickOrder();
}

// 当 tiktok 小程序用户选择 emojiPicker 或者 tools 时，动态调整 messageList 高度，优化滚动列表的展示和交互体验
function adjustHeightInTikTok(type: ToolbarDisplayType) {
  displayType.value = type;
}

function onLike(messageInfo: Object) {
  emits('like', messageInfo);
}

function onDislike(messageInfo: object) {
  emits('dislike', messageInfo);
}

function handleFormPopup(displayFlag: boolean) {
  isUniFormShow.value = displayFlag;
}

// 预加载Emoji资源
async function preloadEmoji() {
  const startIndex = 0;
  const endIndex = 61;
  // #ifdef H5
  setTimeout(() => {
    // h5 用 Image 标签预加载
    let url = '';
    let img;
    for (let i = startIndex; i <= endIndex; i++) {
      url = `https://web.sdk.qcloud.com/im/assets/emoji-plugin/emoji_${i}@2x.png`;
      img = new Image();
      img.src = url;
    }
  }, 0);
  // #endif

  // #ifndef H5
  // app 和小程序先用 uni.request 预加载二进制数据，然后转成图片 base64 缓存
  let preloadedEmojiMap = state.get('preloadedEmojiMap');
  if (!preloadedEmojiMap) {
    preloadedEmojiMap = new Map();
    state.set('preloadedEmojiMap', preloadedEmojiMap);
  }

  let url = '';
  for (let i = startIndex; i <= endIndex; i++) {
    url = `https://web.sdk.qcloud.com/im/assets/emoji-plugin/emoji_${i}@2x.png`;
    // 检查是否已缓存，index 作为 key
    if (preloadedEmojiMap.has(i)) {
      continue;
    }

    try {
      // 下载图片到本地
      const res = await uni.request({
        url,
        responseType: 'arraybuffer',
        timeout: 10000,
      });
      if (res.statusCode === 200) {
        preloadedEmojiMap.set(i, 'data:image/png;base64,' + uni.arrayBufferToBase64(res.data));
      }
    } catch (error) {
      preloadedEmojiMap.delete(i);
    }
  }
  // #endif
}

defineExpose({
  scrollToLatestMessage,
  adjustHeightInTikTok,
});
</script>
<style lang="scss" scoped src="./style/index.scss">
</style>
