<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
import { useDebounceFn, useInfiniteScroll } from '@vueuse/core';
import {
  fetchGetMyNotifyMessagePage,
  fetchGetUnreadNotifyCount,
  fetchUpdateAllNotifyMessageRead,
  fetchUpdateNotifyMessageRead
} from '@/service/api';
import { formatRelativeTime } from '@/utils/datetime';

defineOptions({ name: 'NotificationBell' });

const PAGE_SIZE = 10;
const UNREAD_COUNT_POLL_INTERVAL = 30 * 1000;

type TabKey = 'unread' | 'read';

interface MessageListState {
  items: Api.NotifyMessage.NotifyMessage[];
  pageNo: number;
  total: number;
  loading: boolean;
  /** 是否已按当前关键字拉过第一页（tab 懒加载 / 失效重拉用） */
  loaded: boolean;
  /** 竞态令牌：重置后递增，过期响应直接丢弃 */
  token: number;
}

function createListState(): MessageListState {
  return { items: [], pageNo: 1, total: 0, loading: false, loaded: false, token: 0 };
}

const listStates = reactive<Record<TabKey, MessageListState>>({
  unread: createListState(),
  read: createListState()
});

const unreadCount = ref(0);
const badgeLabel = computed(() => (unreadCount.value > 99 ? '99+' : String(unreadCount.value)));

const drawerOpen = ref(false);
const activeTab = ref<TabKey>('unread');
const searchKeyword = ref('');

function keywordParam() {
  return searchKeyword.value.trim() || undefined;
}

async function refreshUnreadCount() {
  const { data, error } = await fetchGetUnreadNotifyCount();
  if (!error && typeof data === 'number') {
    unreadCount.value = data;
  }
}

function resetList(tab: TabKey) {
  const state = listStates[tab];
  state.token += 1;
  state.items = [];
  state.pageNo = 1;
  state.total = 0;
  state.loading = false;
  state.loaded = false;
}

async function loadPage(tab: TabKey) {
  const state = listStates[tab];
  if (state.loading) return;

  const token = state.token;
  state.loading = true;

  const { data, error } = await fetchGetMyNotifyMessagePage({
    pageNo: state.pageNo,
    pageSize: PAGE_SIZE,
    readStatus: tab === 'read',
    keyword: keywordParam()
  });

  if (token !== state.token) return;

  state.loading = false;
  state.loaded = true;

  if (error || !data) return;

  state.items.push(...data.list);
  state.total = data.total;
  state.pageNo += 1;
}

function hasMore(tab: TabKey) {
  const state = listStates[tab];
  return state.loaded && state.items.length < state.total;
}

function ensureLoaded(tab: TabKey) {
  const state = listStates[tab];
  if (!state.loaded && !state.loading) {
    loadPage(tab);
  }
}

const applyKeywordSearch = useDebounceFn(() => {
  if (!drawerOpen.value) return;
  resetList('unread');
  resetList('read');
  loadPage(activeTab.value);
}, 300);

watch(searchKeyword, () => {
  applyKeywordSearch();
});

watch(activeTab, tab => {
  ensureLoaded(tab);
});

type ScrollbarRefValue = { wrapRef?: HTMLElement } | null;
const unreadScrollbar = ref<ScrollbarRefValue>(null);
const readScrollbar = ref<ScrollbarRefValue>(null);

useInfiniteScroll(
  () => unreadScrollbar.value?.wrapRef,
  () => {
    if (drawerOpen.value && hasMore('unread') && !listStates.unread.loading) {
      loadPage('unread');
    }
  },
  { distance: 48 }
);

useInfiniteScroll(
  () => readScrollbar.value?.wrapRef,
  () => {
    if (drawerOpen.value && hasMore('read') && !listStates.read.loading) {
      loadPage('read');
    }
  },
  { distance: 48 }
);

function openDrawer() {
  drawerOpen.value = true;
  // 每次打开面板都从第 1 页重拉（与后端对齐的消费口径）
  resetList('unread');
  resetList('read');
  loadPage(activeTab.value);
  refreshUnreadCount();
}

function closeDrawer() {
  drawerOpen.value = false;
}

function onDrawerClosed() {
  searchKeyword.value = '';
}

async function markRead(item: Api.NotifyMessage.NotifyMessage) {
  const { error } = await fetchUpdateNotifyMessageRead([item.id]);
  if (error) return;

  // 本地移除、不按原页号回拉，避免未读集合收缩导致的分页漂移
  const state = listStates.unread;
  const index = state.items.findIndex(row => row.id === item.id);
  if (index >= 0) {
    state.items.splice(index, 1);
    state.total = Math.max(0, state.total - 1);
  }
  unreadCount.value = Math.max(0, unreadCount.value - 1);

  // 已读列表失效，下次进入已读 tab 时从第 1 页重拉
  resetList('read');

  // 移除后剩余条目不足一页且还有更多时补拉，防止列表不再触发滚动加载
  if (state.items.length < PAGE_SIZE && hasMore('unread')) {
    loadPage('unread');
  }
}

async function markAllRead() {
  const { error } = await fetchUpdateAllNotifyMessageRead();
  if (error) return;

  unreadCount.value = 0;
  resetList('unread');
  resetList('read');
  loadPage(activeTab.value);
}

let pollTimer: ReturnType<typeof setInterval> | null = null;

onMounted(() => {
  refreshUnreadCount();
  pollTimer = setInterval(() => {
    if (document.hidden) return;
    refreshUnreadCount();
  }, UNREAD_COUNT_POLL_INTERVAL);
});

onBeforeUnmount(() => {
  if (pollTimer) {
    clearInterval(pollTimer);
    pollTimer = null;
  }
});
</script>

<template>
  <button
    class="notification-bell__trigger"
    type="button"
    :aria-label="unreadCount > 0 ? `通知，${unreadCount} 条未读` : '通知'"
    @click="openDrawer"
  >
    <SvgIcon icon="mdi:bell-outline" class="notification-bell__icon" />
    <span v-if="unreadCount > 0" class="notification-bell__badge">{{ badgeLabel }}</span>
  </button>

  <ElDrawer v-model="drawerOpen" size="480px" @closed="onDrawerClosed">
    <template #header>
      <div class="notification-bell__header-main">
        <span class="notification-bell__title">
          通知
          <span v-if="unreadCount > 0" class="notification-bell__title-count">未读 {{ unreadCount }}</span>
        </span>
        <ElButton v-if="unreadCount > 0" link size="small" @click="markAllRead">全部已读</ElButton>
      </div>
    </template>

    <div class="notification-bell__panel">
      <div class="notification-bell__search">
        <ElInput v-model="searchKeyword" placeholder="搜索通知" clearable>
          <template #prefix>
            <SvgIcon icon="mdi:magnify" />
          </template>
        </ElInput>
      </div>

      <ElTabs v-model="activeTab" class="notification-bell__tabs">
        <ElTabPane name="unread">
          <template #label>
            <span class="notification-bell__tab-label">
              未读
              <span class="notification-bell__tab-count">{{ listStates.unread.total }}</span>
            </span>
          </template>
          <ElScrollbar ref="unreadScrollbar" class="notification-bell__scroll">
            <ul v-if="listStates.unread.items.length > 0" class="notification-bell__list">
              <li
                v-for="row in listStates.unread.items"
                :key="row.id"
                class="notification-bell__row is-unread"
                @click="markRead(row)"
              >
                <span class="notification-bell__row-dot" />
                <div class="notification-bell__row-body">
                  <div class="notification-bell__row-title">{{ row.templateContent }}</div>
                  <div class="notification-bell__row-time">{{ formatRelativeTime(row.createTime) }}</div>
                </div>
              </li>
            </ul>
            <div v-else class="notification-bell__empty">
              {{ listStates.unread.loading ? '加载中…' : searchKeyword ? '没有匹配的通知' : '暂无未读通知' }}
            </div>
            <div v-if="listStates.unread.items.length > 0" class="notification-bell__footer-hint">
              {{ listStates.unread.loading ? '加载中…' : hasMore('unread') ? '滚动加载更多…' : '— 已经到底了 —' }}
            </div>
          </ElScrollbar>
        </ElTabPane>

        <ElTabPane name="read">
          <template #label>
            <span class="notification-bell__tab-label">
              已读
              <span class="notification-bell__tab-count">{{ listStates.read.total }}</span>
            </span>
          </template>
          <ElScrollbar ref="readScrollbar" class="notification-bell__scroll">
            <ul v-if="listStates.read.items.length > 0" class="notification-bell__list">
              <li v-for="row in listStates.read.items" :key="row.id" class="notification-bell__row">
                <span class="notification-bell__row-dot" />
                <div class="notification-bell__row-body">
                  <div class="notification-bell__row-title">{{ row.templateContent }}</div>
                  <div class="notification-bell__row-time">{{ formatRelativeTime(row.createTime) }}</div>
                </div>
              </li>
            </ul>
            <div v-else class="notification-bell__empty">
              {{ listStates.read.loading ? '加载中…' : searchKeyword ? '没有匹配的通知' : '暂无已读通知' }}
            </div>
            <div v-if="listStates.read.items.length > 0" class="notification-bell__footer-hint">
              {{ listStates.read.loading ? '加载中…' : hasMore('read') ? '滚动加载更多…' : '— 已经到底了 —' }}
            </div>
          </ElScrollbar>
        </ElTabPane>
      </ElTabs>
    </div>

    <template #footer>
      <ElButton @click="closeDrawer">关闭</ElButton>
    </template>
  </ElDrawer>
</template>

<style scoped>
.notification-bell__trigger {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  margin: 0 4px;
  border: none;
  border-radius: 8px;
  background-color: transparent;
  color: var(--el-text-color-regular);
  cursor: pointer;
  transition:
    background-color 160ms ease,
    color 160ms ease;
}

.notification-bell__trigger:hover {
  background-color: var(--el-fill-color-light);
  color: var(--el-color-primary);
}

.notification-bell__trigger:focus-visible {
  outline: 2px solid var(--el-color-primary);
  outline-offset: 2px;
}

.notification-bell__icon {
  font-size: 20px;
}

.notification-bell__badge {
  position: absolute;
  top: 4px;
  right: 4px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background-color: var(--el-color-danger);
  color: #fff;
  font-size: 10px;
  font-weight: 600;
  line-height: 16px;
  text-align: center;
}

.notification-bell__panel {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.notification-bell__header-main {
  display: flex;
  flex: 1;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-width: 0;
  margin-right: 8px;
}

.notification-bell__title {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--el-text-color-primary);
  font-size: 16px;
  font-weight: 600;
}

.notification-bell__title-count {
  padding: 1px 8px;
  border-radius: 999px;
  background-color: var(--el-color-danger);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
}

.notification-bell__search {
  padding: 0 0 4px;
}

.notification-bell__tabs {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-height: 0;
}

.notification-bell__tabs :deep(.el-tabs__content) {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

.notification-bell__tabs :deep(.el-tab-pane) {
  height: 100%;
}

.notification-bell__tab-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.notification-bell__tab-count {
  padding: 0 7px;
  border-radius: 999px;
  background-color: var(--el-fill-color);
  color: var(--el-text-color-secondary);
  font-size: 10px;
  font-weight: 600;
  line-height: 16px;
}

.notification-bell__tabs :deep(.el-tabs__item.is-active) .notification-bell__tab-count {
  background-color: var(--el-color-primary-light-9);
  color: var(--el-color-primary);
}

.notification-bell__scroll {
  height: 100%;
}

.notification-bell__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.notification-bell__row {
  position: relative;
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr);
  align-items: flex-start;
  gap: 10px;
  padding: 12px 4px;
  border-radius: 8px;
}

.notification-bell__row + .notification-bell__row {
  border-top: 1px dashed var(--el-border-color-lighter);
}

.notification-bell__row.is-unread {
  cursor: pointer;
  transition: background-color 120ms ease;
}

.notification-bell__row.is-unread:hover {
  background-color: var(--el-fill-color-light);
}

.notification-bell__row-dot {
  width: 8px;
  height: 8px;
  margin-top: 6px;
  border-radius: 50%;
  background-color: transparent;
  justify-self: center;
}

.notification-bell__row.is-unread .notification-bell__row-dot {
  background-color: var(--el-color-primary);
}

.notification-bell__row-body {
  min-width: 0;
}

.notification-bell__row-title {
  color: var(--el-text-color-regular);
  font-size: 14px;
  line-height: 1.5;
}

.notification-bell__row.is-unread .notification-bell__row-title {
  color: var(--el-text-color-primary);
  font-weight: 500;
}

.notification-bell__row-time {
  margin-top: 4px;
  color: var(--el-text-color-secondary);
  font-size: 12px;
}

.notification-bell__empty {
  padding: 48px 16px;
  text-align: center;
  color: var(--el-text-color-secondary);
  font-size: 13px;
}

.notification-bell__footer-hint {
  padding: 12px 0 4px;
  text-align: center;
  color: var(--el-text-color-secondary);
  font-size: 12px;
  user-select: none;
}
</style>
