<script lang="ts" setup>
/**
 * 公告轮播条（单行）：看板卡内嵌 / 独立卡两种形态
 * 为什么做成单行轮播：公告曝光频率需求低但要常驻，列表卡占一整列太浪费；
 * 压成一行自动轮播（5s 一条，hover 暂停、可手动翻页），点击弹详情，
 * 把首屏空间让给快捷入口与日历。取代原 WidgetNotice 列表卡（该组件已弃用保留）
 * 形态：默认内嵌（驾驶舱卡底部，带上分隔线）；standalone 独立卡（无驾驶舱角色全宽一行）
 */
import { computed, onMounted, onUnmounted, ref } from 'vue';

import { useVbenModal } from '@vben/common-ui';
import { IconifyIcon as VbenIcon } from '@vben/icons';

import { getLatestNotice } from '#/views/system/notice/api';

interface Props {
  /** 独立卡形态：无驾驶舱角色时公告条单独成行（带卡片壳） */
  standalone?: boolean;
}
withDefaults(defineProps<Props>(), { standalone: false });

/** 公告项（announcement/latest 返回结构） */
type NoticeItem = {
  avatar: string;
  content: string;
  date: string;
  title: string;
};

/** 展示用公告项：附加前端推导的类型标签 */
type DisplayNoticeItem = NoticeItem & {
  typeLabel: string;
  typeTone: 'danger' | 'info' | 'primary' | 'warning';
};

/**
 * 按标题关键词推导公告类型（纯前端展示，后端无类型字段）
 * 顺序：维护类优先（最需要注意）→ 更新 → 活动 → 默认公告
 */
function resolveNoticeType(title: string): Pick<DisplayNoticeItem, 'typeLabel' | 'typeTone'> {
  const t = title || '';
  if (/维护|停机|故障/.test(t)) return { typeLabel: '维护', typeTone: 'danger' };
  if (/更新|升级|版本|上线/.test(t)) return { typeLabel: '更新', typeTone: 'info' };
  if (/活动|福利|优惠|节/.test(t)) return { typeLabel: '活动', typeTone: 'warning' };
  return { typeLabel: '公告', typeTone: 'primary' };
}

const items = ref<DisplayNoticeItem[]>([]);
/** 当前轮播下标 */
const idx = ref(0);
/** hover 时暂停自动轮播，避免用户正在读时被切走 */
const paused = ref(false);
let timer: null | ReturnType<typeof setInterval> = null;

const current = computed(() => items.value[idx.value] || null);

/** 详情弹层当前展示的公告 */
const activeItem = ref<DisplayNoticeItem | null>(null);

/** 公告详情弹层：只读展示，关闭默认页脚按钮 */
const [DetailModal, detailModalApi] = useVbenModal({
  footer: false,
  header: false,
  closable: true,
  class: 'w-[560px]',
});

/** 弹层类型 pill 的 tone class（避免模板里拼接字符串） */
const activeToneClass = computed(() =>
  activeItem.value ? `pill--${activeItem.value.typeTone}` : '',
);

/**
 * 拉取最新公告并附加类型标签；失败静默隐藏整条（空数组时组件不渲染）
 */
async function fetchLatest() {
  try {
    const res = await getLatestNotice(10);
    const list: NoticeItem[] = Array.isArray(res) ? res : [];
    items.value = list.map((it) => ({ ...it, ...resolveNoticeType(it.title) }));
  } catch {
    items.value = [];
  }
}

/** 下一条（自动轮播与手动共用，循环） */
function next() {
  if (items.value.length === 0) return;
  idx.value = (idx.value + 1) % items.value.length;
}

/** 上一条（循环） */
function prev() {
  if (items.value.length === 0) return;
  idx.value = (idx.value - 1 + items.value.length) % items.value.length;
}

/** 打开详情弹层（content 已全量在手，无需再请求） */
function openDetail(item: DisplayNoticeItem) {
  activeItem.value = item;
  detailModalApi.open();
}

onMounted(async () => {
  await fetchLatest();
  // 只有多于一条才需要轮播
  if (items.value.length > 1) {
    timer = setInterval(() => {
      if (!paused.value) next();
    }, 5000);
  }
});

onUnmounted(() => {
  if (timer) clearInterval(timer);
});
</script>

<template>
  <div
    v-if="items.length > 0"
    class="notice-ticker"
    :class="{ standalone }"
    @mouseenter="paused = true"
    @mouseleave="paused = false"
  >
    <span class="nt-megaphone">
      <VbenIcon icon="lucide:megaphone" />
    </span>
    <!-- 轮播视口：固定一行高，新条从下滚入、旧条向上滚出 -->
    <div class="nt-viewport" role="button" tabindex="0" @click="current && openDetail(current)">
      <Transition name="nt-roll">
        <div v-if="current" :key="idx" class="nt-line">
          <span class="pill" :class="`pill--${current.typeTone}`">{{ current.typeLabel }}</span>
          <span class="nt-title">{{ current.title }}</span>
          <!-- 公告内容后端可控，沿用原工作台富文本渲染（单行截断）；用 div 避免 span 内嵌块级标签 -->
          <!-- eslint-disable-next-line vue/no-v-html -->
          <div class="nt-summary" v-html="current.content"></div>
          <span class="nt-date">{{ current.date }}</span>
        </div>
      </Transition>
    </div>
    <div class="nt-ctrl">
      <button class="nt-ctrl-btn" title="上一条" type="button" @click.stop="prev">
        <VbenIcon icon="lucide:chevron-up" />
      </button>
      <span class="nt-ctrl-count">{{ idx + 1 }}/{{ items.length }}</span>
      <button class="nt-ctrl-btn" title="下一条" type="button" @click.stop="next">
        <VbenIcon icon="lucide:chevron-down" />
      </button>
    </div>

    <!-- 公告详情弹层：premium 风格 hero 头 + 富文本正文（沿用原列表卡的弹层设计） -->
    <DetailModal>
      <div v-if="activeItem" class="notice-detail">
        <div class="notice-detail__hero">
          <span class="notice-detail__hero-icon">
            <VbenIcon :icon="activeItem.avatar || 'lucide:megaphone'" />
          </span>
          <div class="notice-detail__hero-meta">
            <div class="notice-detail__hero-top">
              <span class="pill" :class="activeToneClass">
                {{ activeItem.typeLabel }}
              </span>
              <span class="notice-detail__date">{{ activeItem.date }}</span>
            </div>
            <div class="notice-detail__title">{{ activeItem.title }}</div>
          </div>
        </div>
        <!-- 公告内容后端可控，沿用列表同源富文本渲染 -->
        <!-- eslint-disable-next-line vue/no-v-html -->
        <div class="notice-detail__content" v-html="activeItem.content"></div>
      </div>
    </DetailModal>
  </div>
</template>

<style scoped>
/* 内嵌形态（默认）：驾驶舱卡底部一行，上分隔线与数据区分层 */
.notice-ticker {
  display: flex;
  gap: 10px;
  align-items: center;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid hsl(var(--border) / 0.6);
}

/* 独立卡形态：无驾驶舱角色时单独成行 */
.notice-ticker.standalone {
  margin-top: 0;
  padding: 12px 20px;
  background: hsl(var(--card));
  border: 1px solid hsl(var(--border) / 0.7);
  border-radius: 12px;
}

.nt-megaphone {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 26px;
  height: 26px;
  color: hsl(var(--primary));
  background: hsl(var(--primary) / 0.09);
  border-radius: 8px;
}

.nt-megaphone :deep(svg) {
  width: 14px;
  height: 14px;
}

/* 轮播视口：固定一行高 + 裁剪滚动动画 */
.nt-viewport {
  position: relative;
  flex: 1;
  height: 22px;
  min-width: 0;
  overflow: hidden;
  cursor: pointer;
  outline: none;
}

.nt-line {
  position: absolute;
  inset: 0;
  display: flex;
  gap: 8px;
  align-items: center;
  min-width: 0;
}

.nt-title {
  flex-shrink: 0;
  max-width: 40%;
  overflow: hidden;
  font-size: 13px;
  font-weight: 550;
  color: hsl(var(--foreground));
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nt-viewport:hover .nt-title {
  color: hsl(var(--primary));
}

/* 摘要吃掉剩余宽度单行截断 */
.nt-summary {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  font-size: 12px;
  color: hsl(var(--muted-foreground));
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nt-summary :deep(a) {
  color: hsl(var(--primary));
}

/* 富文本里的段落压平成行内，避免默认 margin 撑破单行视口 */
.nt-summary :deep(p) {
  display: inline;
  margin: 0;
}

.nt-date {
  flex-shrink: 0;
  font-size: 12px;
  color: hsl(var(--muted-foreground) / 0.8);
  white-space: nowrap;
}

/* 翻页控制：上一条/序号/下一条 */
.nt-ctrl {
  display: flex;
  flex-shrink: 0;
  gap: 2px;
  align-items: center;
}

.nt-ctrl-btn {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  color: hsl(var(--muted-foreground));
  cursor: pointer;
  background: transparent;
  border: none;
  border-radius: 6px;
  transition: color 0.15s ease, background-color 0.15s ease;
}

.nt-ctrl-btn:hover {
  color: hsl(var(--primary));
  background: hsl(var(--primary) / 0.08);
}

.nt-ctrl-btn :deep(svg) {
  width: 13px;
  height: 13px;
}

.nt-ctrl-count {
  min-width: 32px;
  font-size: 11px;
  color: hsl(var(--muted-foreground) / 0.8);
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* 垂直滚动动画：新条从下滚入、旧条向上滚出（广播 ticker 惯例） */
.nt-roll-enter-active,
.nt-roll-leave-active {
  transition: transform 0.4s ease, opacity 0.4s ease;
}

.nt-roll-enter-from {
  opacity: 0;
  transform: translateY(100%);
}

.nt-roll-leave-to {
  opacity: 0;
  transform: translateY(-100%);
}

/* ===== 类型 pill：主题变量着色，亮/暗自适应 ===== */
.pill {
  flex-shrink: 0;
  padding: 1px 7px;
  font-size: 11px;
  font-weight: 600;
  line-height: 16px;
  border-radius: 999px;
}

.pill--primary {
  color: hsl(var(--primary));
  background: hsl(var(--primary) / 0.1);
}

.pill--info {
  color: hsl(210 90% 45%);
  background: hsl(210 90% 50% / 0.12);
}

.pill--warning {
  color: hsl(var(--warning, 38 92% 40%));
  background: hsl(var(--warning, 38 92% 50%) / 0.14);
}

.pill--danger {
  color: hsl(var(--destructive));
  background: hsl(var(--destructive) / 0.1);
}

/* ===== 详情弹层（沿用原列表卡设计语言） ===== */
.notice-detail {
  padding: 4px 2px 8px;
}

/* hero 头：柔和主色光斑背景 */
.notice-detail__hero {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 16px 18px;
  margin-bottom: 16px;
  background:
    radial-gradient(
      80% 120% at 0% 0%,
      hsl(var(--primary) / 0.14),
      transparent 60%
    ),
    linear-gradient(
      165deg,
      hsl(var(--muted) / 0.4),
      hsl(var(--card, var(--background)))
    );
  border: 1px solid hsl(var(--border));
  border-radius: 14px;
}

.notice-detail__hero-icon {
  display: grid;
  flex-shrink: 0;
  place-items: center;
  width: 42px;
  height: 42px;
  color: hsl(var(--primary));
  background: hsl(var(--primary) / 0.12);
  border-radius: 12px;
}

.notice-detail__hero-icon :deep(svg) {
  width: 20px;
  height: 20px;
}

.notice-detail__hero-meta {
  flex: 1;
  min-width: 0;
}

.notice-detail__hero-top {
  display: flex;
  gap: 8px;
  align-items: center;
}

.notice-detail__date {
  font-size: 12px;
  color: hsl(var(--muted-foreground));
}

.notice-detail__title {
  margin-top: 6px;
  font-size: 17px;
  font-weight: 700;
  color: hsl(var(--foreground));
  letter-spacing: -0.02em;
  overflow-wrap: break-word;
}

.notice-detail__content {
  max-height: 52vh;
  padding: 0 2px;
  overflow-y: auto;
  font-size: 13.5px;
  line-height: 1.75;
  color: hsl(var(--foreground) / 0.88);
  overflow-wrap: break-word;
}

.notice-detail__content :deep(a) {
  color: hsl(var(--primary));
}

.notice-detail__content :deep(img) {
  max-width: 100%;
  border-radius: 10px;
}

@media (prefers-reduced-motion: reduce) {
  .nt-roll-enter-active,
  .nt-roll-leave-active {
    transition: none;
  }
}
</style>
