<template>
  <div class="flex flex-1 flex-col min-w-0 bg-[var(--el-fill-color-light)]">
    <template v-if="conversationStore.activeConversation">
      <!-- 顶部：会话名 + 右侧功能图标 -->
      <div
        class="flex items-center justify-between h-14 px-5 bg-[var(--el-fill-color-light)] border-b border-[var(--el-border-color-light)]"
      >
        <span class="text-base font-medium text-[var(--el-text-color-primary)]">
          {{ conversationStore.activeConversation?.name || '' }}
        </span>
        <div class="flex gap-3 items-center">
          <!-- 聊天历史：从输入区底部工具栏挪到顶部右上角，对齐微信 PC（点击弹窗承接历史消息） -->
          <el-tooltip content="聊天历史" placement="bottom">
            <Icon
              icon="ant-design:profile-outlined"
              :size="20"
              class="message-panel__header-icon cursor-pointer"
              @click="historyVisible = true"
            />
          </el-tooltip>
          <!-- 信息抽屉入口：群聊 / 私聊统一用 3 点图标，对齐微信 PC；
               sideVisible 在群 / 私聊两个抽屉之间共用一个 ref，由 v-if-else 决定挂哪个 -->
          <el-tooltip :content="isGroup ? '群聊信息' : '聊天信息'" placement="bottom">
            <Icon
              icon="ant-design:more-outlined"
              :size="20"
              class="message-panel__header-icon cursor-pointer"
              @click="toggleSide"
            />
          </el-tooltip>
        </div>
      </div>

      <!-- 中间：消息列表 -->
      <div
        ref="listRef"
        class="relative flex-1 py-2 overflow-y-auto bg-[var(--el-bg-color-page)]"
        @scroll="handleScroll"
      >
        <div
          v-if="messages.length === 0"
          class="flex items-center justify-center h-full text-sm text-[var(--el-text-color-secondary)]"
        >
          暂无消息
        </div>
        <!-- data-message-id 给 MessageHistory "定位到聊天位置" 用：父级通过 querySelector
             找到这层 wrapper，scrollIntoView + 加高亮 class；id=0 的本地占位消息跳过 -->
        <div
          v-for="msg in messages"
          :key="msg.id || msg.clientMessageId"
          :data-message-id="msg.id || ''"
          class="message-panel__message-anchor"
        >
          <MessageItem :message="msg" />
        </div>

        <!-- 回到底部浮动按钮（滚动不在底部时显示） -->
        <transition name="message-panel__jump-fade">
          <div
            v-if="showJumpToBottom"
            class="message-panel__jump-bottom sticky bottom-3 left-1/2 inline-flex gap-1.5 items-center w-fit mx-auto px-3.5 py-1.5 text-xs text-[#409eff] bg-[var(--el-bg-color-overlay)] rounded-2xl shadow-[0_2px_8px_rgba(0,0,0,0.12)] cursor-pointer hover:text-white hover:bg-[#409eff]"
            @click="scrollToBottom(true)"
          >
            <Icon icon="ant-design:down-outlined" :size="14" />
            <span v-if="newMessageCount > 0" class="font-medium">
              {{ newMessageCount > 99 ? '99+' : newMessageCount }} 条新消息
            </span>
            <span v-else>回到底部</span>
          </div>
        </transition>
      </div>

      <!-- 底部：输入框
        :key 绑会话标识，切换 A → B 时强制重建组件 → editor / mention range / pendingAtUserIds
        全部清零，避免上一会话的草稿和 @ 被发到新会话 -->
      <!-- TODO @AI：切换时，之前的要被保留！ -->
      <!-- TODO @AI：切换时，用户如果有输入信息，需要把 lastContent 变成输入信息； -->
      <MessageInput :key="messageInputKey" />

      <!-- 右侧信息抽屉：群聊 / 私聊各自一份 -->
      <ConversationGroupSide
        v-if="isGroup"
        v-model="sideVisible"
        :group="groupInfo"
        :conversation="conversationStore.activeConversation"
        :members="groupMembers"
        :friends="friends"
        @reload="reloadGroupData"
        @open-history="historyVisible = true"
      />
      <ConversationPrivateSide
        v-else
        v-model="sideVisible"
        :conversation="conversationStore.activeConversation"
        :friend="privateFriend"
        :friends="friends"
        @open-history="historyVisible = true"
      />

      <!-- 历史消息抽屉 -->
      <MessageHistory v-model="historyVisible" @locate="handleLocate" />
    </template>
    <div
      v-else
      class="flex items-center justify-center h-full text-sm text-[var(--el-text-color-secondary)]"
    >
      <span>选择一个会话开始聊天</span>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, watch, nextTick, computed } from 'vue'
import Icon from '@/components/Icon/src/Icon.vue'

import { useConversationStore } from '../../../../store/conversationStore'
import { useFriendStore } from '../../../../store/friendStore'
import { getMemberDisplayName } from '@/views/im/utils/user'
import { useGroupStore } from '../../../../store/groupStore'
import { ImConversationType } from '@/views/im/utils/constants'
import { getConversationKey } from '@/views/im/utils/conversation'
import { CommonStatusEnum } from '@/utils/constants'
import MessageItem from './MessageItem.vue'
import MessageInput from '../input/MessageInput.vue'
import MessageHistory from './MessageHistory.vue'
import ConversationGroupSide from '../conversation/ConversationGroupSide.vue'
import ConversationPrivateSide from '../conversation/ConversationPrivateSide.vue'
import type { FriendLite, GroupLite } from '../../../../types'
import type { GroupMemberLite } from '../../../../components/group/GroupMember.vue'

defineOptions({ name: 'ImMessagePanel' })

const conversationStore = useConversationStore()
const friendStore = useFriendStore()
const groupStore = useGroupStore()
const listRef = ref<HTMLElement>()

const messages = computed(() => conversationStore.getActiveMessages)
const isGroup = computed(
  () => conversationStore.activeConversation?.type === ImConversationType.GROUP
)

/**
 * MessageInput 的 :key —— 切群时强制 unmount + remount，让 editor / mention range /
 * 上一会话草稿全部归零；用 fallback 'none' 避开 activeConversation 短暂为 null 的窗口
 */
const messageInputKey = computed(() => {
  const conv = conversationStore.activeConversation
  return conv ? getConversationKey(conv) : 'none'
})

/** "是否停留在底部"的阈值：距离底部 < 80px 视为底部 */
const BOTTOM_THRESHOLD = 80

/** 当前是否已不在底部（显示"回到底部"按钮） */
const showJumpToBottom = ref(false)
/** 不在底部期间累计的新消息数 */
const newMessageCount = ref(0)

/**
 * 当前激活的群详情：优先 groupStore（带详细字段），未加载完时用 activeConversation 兜底
 *
 * groupStore 是按需懒加载的，初次进群时 ensureGroupData 触发后才会有完整数据；
 * 兜底字段（name / avatar）保证 header 不会"闪空"，notice / ownerId / memberCount
 * 必须等 store 就位才有值（这些字段在 conversation 里没有）
 */
const groupInfo = computed<
  (GroupLite & { notice?: string; remarkNickName?: string; ownerId?: number }) | undefined
>(() => {
  const conversation = conversationStore.activeConversation
  if (!conversation || conversation.type !== ImConversationType.GROUP) {
    return undefined
  }
  const group = groupStore.getGroup(conversation.targetId)
  return {
    id: conversation.targetId,
    name: group?.name || conversation.name,
    showGroupName: group?.name || conversation.name,
    showImage: group?.avatar || conversation.avatar,
    notice: group?.notice,
    ownerId: group?.ownerUserId,
    memberCount: group?.memberCount
  }
})

// 群成员列表：直接取 groupStore 缓存，map 成 GroupMemberLite 给下游消费（@-mention / 邀请等）
const groupMembers = computed<GroupMemberLite[]>(() => {
  const conversation = conversationStore.activeConversation
  if (!conversation || conversation.type !== ImConversationType.GROUP) {
    return []
  }
  const group = groupStore.getGroup(conversation.targetId)
  return (group?.members || []).map((member) => {
    // 显示名走「好友备注 > 群备注 > 真实昵称」三级；头像走 nickname 保稳定
    const friend = friendStore.getFriend(member.userId)
    return {
      userId: member.userId,
      showName: getMemberDisplayName(member, friend),
      nickname: member.nickname,
      avatar: member.avatar,
      status: member.status
    }
  })
})

/** 好友列表：群侧用于"邀请入群"，私聊侧用于"+创建群"，统一从 friendStore 映射成 FriendLite 窄接口 */
const friends = computed<FriendLite[]>(() =>
  friendStore.getActiveFriends.map((friend) => ({
    id: friend.friendUserId,
    nickname: friend.nickname,
    avatar: friend.avatar,
    deleted: friend.status === CommonStatusEnum.DISABLE
  }))
)

/** 切换到群会话时同步群信息 + 成员；各自 fire-and-forget + catch，任何一项失败不牵连其它 */
async function ensureGroupData(groupId: number) {
  // 远程异步拉群信息（群名 / 公告 / 群主等元数据）
  groupStore.fetchGroupInfo(groupId).catch((error) => {
    console.warn('[IM MessagePanel] fetchGroupInfo 失败', { groupId }, error)
  })

  // 先从 IDB 同步加载群成员，让首帧立即出成员名 / 头像
  await groupStore.loadGroupMembers(groupId).catch((error) => {
    console.warn('[IM MessagePanel] loadGroupMembers 失败', { groupId }, error)
    return null
  })
  // 再从远程异步拉成员，强刷以跳过 in-memory 短路，每次进群都能拿到最新成员状态
  groupStore.fetchGroupMembers(groupId, true).catch((error) => {
    console.warn('[IM MessagePanel] fetchGroupMembers 失败', { groupId }, error)
  })
}

/** 群信息抽屉里点"刷新"：强拉一次最新群元数据 + 群成员 */
function reloadGroupData() {
  const conversation = conversationStore.activeConversation
  if (!conversation || conversation.type !== ImConversationType.GROUP) {
    return
  }
  groupStore.fetchGroupInfo(conversation.targetId)
  groupStore.fetchGroupMembers(conversation.targetId, true)
}

const historyVisible = ref(false)
const sideVisible = ref(false) // 信息抽屉开关：群聊 / 私聊共用一个 ref

/** 信息抽屉的 toggle：跟 header 上 3 点图标按钮共用 */
function toggleSide() {
  sideVisible.value = !sideVisible.value
}

/** 当前私聊对应的好友（抽屉头部展示用） */
const privateFriend = computed(() => {
  const conversation = conversationStore.activeConversation
  if (!conversation || conversation.type !== ImConversationType.PRIVATE) {
    return undefined
  }
  return friendStore.getFriend(conversation.targetId)
})

/** 计算距离底部的像素 */
function distanceFromBottom(): number {
  const el = listRef.value
  if (!el) {
    return 0
  }
  return el.scrollHeight - el.scrollTop - el.clientHeight
}

/**
 * 消息列表滚动事件：刷新"是否在底部"状态
 * - 在底部：隐藏"回到底部"浮窗 + 清掉"未读新消息"计数
 * - 不在底部：显示"回到底部"浮窗，新消息会累计到 newMessageCount
 */
function handleScroll() {
  const dist = distanceFromBottom()
  const atBottom = dist <= BOTTOM_THRESHOLD
  showJumpToBottom.value = !atBottom
  if (atBottom) {
    newMessageCount.value = 0
  }
}

/**
 * 滚到底部：切会话 / 收到新消息（且当前在底部）/ 用户主动点"回到底部" 都走这里
 *
 * 包 nextTick 是为了等 v-for 把新消息真正渲染进 DOM 后再算 scrollHeight，
 * 否则可能滚到的还是旧高度（差最后一条的位置）。smooth=true 走平滑动画，
 * 适合用户主动点击；初始 / 自动滚动用 auto，避免用户感知到动画拖拽
 */
function scrollToBottom(smooth = false) {
  nextTick(() => {
    if (!listRef.value) {
      return
    }
    listRef.value.scrollTo({
      top: listRef.value.scrollHeight,
      behavior: smooth ? 'smooth' : 'auto'
    })
    newMessageCount.value = 0
    showJumpToBottom.value = false
  })
}

/**
 * 定位到聊天位置：MessageHistory 行上"定位"按钮触发
 *
 * 1. 先关掉历史弹窗（避免 scroll 时遮挡 + dialog 关闭后让聊天面板拿回焦点）
 * 2. nextTick 等弹窗 leave 动画 / 列表渲染稳定后再查 DOM
 * 3. 按 data-message-id 找 wrapper，scrollIntoView({ block: center }) 让消息落到视口中部
 * 4. 加 --highlight class 短暂高亮，提示用户"就是这条"
 */
async function handleLocate(messageId: number) {
  if (!messageId) {
    return
  }
  await nextTick()
  if (!listRef.value) {
    return
  }
  const target = listRef.value.querySelector<HTMLElement>(`[data-message-id="${messageId}"]`)
  if (!target) {
    return
  }
  target.scrollIntoView({ behavior: 'smooth', block: 'center' })
  target.classList.add('message-panel__message-anchor--highlight')
  setTimeout(() => {
    target.classList.remove('message-panel__message-anchor--highlight')
  }, 1600)
}

/**
 * 消息数量变化时的滚动跟进策略
 * - 当前在底部 → 自动滚到新底部（用户在"实时跟读"，体验上跟微信一致）
 * - 不在底部 → 累计 newMessageCount，让 sticky 浮窗显示"X 条新消息"，让用户主动点
 */
watch(
  () => messages.value.length,
  (newLen, oldLen) => {
    // 仅处理新增（delta > 0）；删除 / 撤回让 length 减少时不动滚动状态
    const delta = (newLen || 0) - (oldLen || 0)
    if (delta <= 0) {
      return
    }
    // 用 BOTTOM_THRESHOLD（80px）做容差：用户稍微往上翻几行就视作"不在底部"，
    // 否则一直 auto-scroll 会把人正在读的内容顶走，体验糟糕
    const dist = distanceFromBottom()
    if (dist <= BOTTOM_THRESHOLD) {
      scrollToBottom()
    } else {
      newMessageCount.value += delta
      showJumpToBottom.value = true
    }
  }
)

/**
 * 切换会话：清空"在不在底部"相关状态、强制滚到底部、群会话预拉资料
 *
 * immediate:true 让首次进入页面就能正确初始化（无需等到第一次切换）
 */
watch(
  () => conversationStore.activeConversation?.targetId,
  (targetId) => {
    // 切群时上一会话的"未读累计 + 浮窗显示"必须清掉，否则会带到新会话里看起来很突兀
    newMessageCount.value = 0
    showJumpToBottom.value = false
    scrollToBottom()
    // 仅群聊预拉详情 / 成员（私聊对端在首屏 fetchFriends 时就拉了）
    if (targetId && conversationStore.activeConversation?.type === ImConversationType.GROUP) {
      ensureGroupData(targetId)
    }
  },
  { immediate: true }
)
</script>

<style scoped>
/* el-icon 全局规则 .el-icon{color:var(--color,inherit)} 优先级胜过 UnoCSS，这里用 :deep + !important 兜底；
   颜色直接引用 Element Plus 主题变量，暗色模式自动切到更亮的灰 */
.message-panel__header-icon,
.message-panel__header-icon :deep(svg) {
  color: var(--el-text-color-regular) !important;
  fill: currentColor !important;
  transition: color 0.15s;
}
.message-panel__header-icon:hover,
.message-panel__header-icon:hover :deep(svg) {
  color: var(--el-color-primary) !important;
}

/* sticky + translate 居中：fit-content 宽度不会撑满，transform 做水平 -50% 偏移；
   UnoCSS 表达 transform+transition 多 value 不太方便，这里用最小的 scoped CSS 承接 */
.message-panel__jump-bottom {
  transform: translateX(-50%);
  transition:
    opacity 0.2s,
    transform 0.2s;
}

/* MessageHistory "定位" 跳过来时短暂高亮：1.6s 后由 JS 移除 class，配合 transition 缓出黄底 */
.message-panel__message-anchor {
  transition: background-color 0.6s ease;
}
.message-panel__message-anchor--highlight {
  background-color: var(--el-color-warning-light-9);
}

/* 回到底部按钮的 Vue transition 钩子类名 */
.message-panel__jump-fade-enter-active,
.message-panel__jump-fade-leave-active {
  transition:
    opacity 0.2s,
    transform 0.2s;
}

.message-panel__jump-fade-enter-from,
.message-panel__jump-fade-leave-to {
  opacity: 0;
  transform: translate(-50%, 20px);
}
</style>
