import { useEffect, useMemo, useRef, useState } from "react";
import { Alert, Button, Empty, Input, Result, Skeleton, Tabs, message } from "antd";
import { useParams, useSearchParams } from "react-router-dom";
import {
  CalendarOutlined,
  CaretRightFilled,
  ClockCircleOutlined,
  CopyOutlined,
  FileTextOutlined,
  LockOutlined,
  PauseOutlined,
  RobotOutlined,
  ShareAltOutlined,
  TeamOutlined,
  UserOutlined,
  DownOutlined,
  UpOutlined,
  LinkOutlined,
} from "@ant-design/icons";
import dayjs from "dayjs";
import ReactMarkdown from "react-markdown";
import {
  getMeetingPreviewAccess,
  getPublicMeetingPreview,
  resolveMeetingPlaybackAudioUrl,
  type MeetingChapterVO,
  type MeetingTranscriptVO,
  type MeetingVO,
} from "../../api/business/meeting";
import { buildMeetingAnalysis } from "./meetingAnalysis";
import "./MeetingPreview.css";

type PreviewPageTab = "summary" | "catalog" | "transcript";

const TEXT = {
  statusTranscribing: "转写中",
  statusSummarizing: "总结中",
  statusCompleted: "已完成",
  statusPending: "待处理",
  hintTranscribing: "会议内容仍在整理中，预览会持续补全。",
  hintSummarizing: "AI 正在生成会议总结，已完成内容会优先展示。",
  hintCompleted: "会议纪要、分析和转录内容已生成完成。",
  hintPending: "当前会议尚未生成完整内容，请稍后重试。",
  missingMeetingId: "未提供会议编号",
  loadFailed: "会议预览加载失败",
  noMeetingData: "未找到会议数据",
  previewLabel: "会议预览",
  untitledMeeting: "未命名会议",
  meetingTime: "会议时间",
  hostCreator: "主持/创建",
  participantsCount: "参会人数",
  tagsCount: "标签数量",
  notSet: "未设置",
  notFilled: "未填写",
  pageSummary: "AI 纪要",
  pageCatalog: "AI 目录",
  pageTranscript: "转录原文",
  copyLink: "复制链接",
  shareNow: "立即分享",
  shareCopied: "预览链接已复制",
  shareFallbackCopied: "当前设备不支持系统分享，已为你复制链接",
  shareFailed: "分享失败，请先复制链接",
  accessCheck: "访问校验",
  passwordRequired: "该会议需要访问密码",
  passwordHint: "请输入会议的 访问密码 后继续访问预览内容。",
  passwordPlaceholder: "请输入 访问密码",
  openPreview: "进入预览",
  invalidPassword: "访问密码错误",
  basicInfo: "基本信息",
  meetingOverview: "会议概况",
  creator: "创建人",
  host: "主持人",
  createdAt: "创建时间",
  audioStatus: "音频状态",
  participants: "人",
  tags: "会议标签",
  aiAnalysis: "AI 目录",
  analysis: "会议分析",
  previewExtra: "预览页仅读展示",
  audioPlaybackWarning: "音频保存失败，可能影响回放。",
  summaryOverview: "全文概要",
  summaryEmpty: "暂无概要内容",
  analysisChapters: "章节",
  analysisSpeakers: "发言人",
  analysisKeyPoints: "关键要点",
  analysisTodos: "待办事项",
  noChapterAnalysis: "暂无章节分析",
  noSpeakerAnalysis: "暂无发言人分析",
  noKeyPoints: "暂无关键要点",
  noTodos: "暂无待办事项",
  chapterFallback: "章节",
  speakerFallback: "发言人",
  speakerSummary: "发言概述",
  keyPointFallback: "要点",
  noChapterSummary: "暂无章节描述",
  noSpeakerSummary: "暂无发言总结",
  noKeyPointSummary: "暂无要点说明",
  summarySection: "会议纪要",
  fullSummary: "完整纪要",
  noSummary: "暂无会议纪要",
  transcriptSection: "会议转录",
  transcriptTitle: "逐段转录",
  noDuration: "暂无时长",
  audioUnavailable: "音频文件不可用，仅展示转录内容。",
  noTranscript: "暂无转录内容",
  unknownSpeaker: "未知发言人",
  disclaimer: "智能内容由用户会议内容 + AI 模型生成，我们不对内容准确性和完整性做任何保证，亦不代表我们的观点或态度",
  shareText: "我向你分享了一个会议预览链接",
  audioSaved: "已保存",
  audioSaveFailed: "保存失败",
  audioUploaded: "已上传",
  audioNotSaved: "未保存",
  linkToTranscript: "关联原文",
  noCatalog: "暂无 AI 目录",
};

type ChapterTranscriptLink = {
  key: string;
  title: string;
  timeLabel: string;
  transcriptIds: number[];
  firstTranscriptId: number | null;
  firstTranscriptStartTime: number | null;
};

function parseChapterTimeToMs(value?: string) {
  const raw = String(value || "").trim();
  if (!raw) return null;

  const matched = raw.match(/(\d{1,2}:\d{2}(?::\d{2})?)/)?.[1];
  if (!matched) return null;

  const parts = matched.split(":").map((item) => Number(item));
  if (parts.some((item) => Number.isNaN(item))) {
    return null;
  }

  const totalSeconds =
    parts.length === 3 ? parts[0] * 3600 + parts[1] * 60 + parts[2] : parts[0] * 60 + parts[1];

  return totalSeconds * 1000;
}

const STATUS_META: Record<number, { label: string; className: string; hint: string }> = {
  1: { label: TEXT.statusTranscribing, className: "is-processing", hint: TEXT.hintTranscribing },
  2: { label: TEXT.statusSummarizing, className: "is-processing", hint: TEXT.hintSummarizing },
  3: { label: TEXT.statusCompleted, className: "is-complete", hint: TEXT.hintCompleted },
};

function formatDurationRange(startTime?: number, endTime?: number) {
  const format = (milliseconds?: number) => {
    const safeMs = Math.max(0, milliseconds || 0);
    const totalSeconds = Math.floor(safeMs / 1000);
    const minutes = Math.floor(totalSeconds / 60);
    const seconds = totalSeconds % 60;
    return `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
  };

  return `${format(startTime)} - ${format(endTime)}`;
}

function splitDisplayItems(value?: string) {
  return (value || "")
    .split(",")
    .map((item) => item.trim())
    .filter(Boolean);
}

function transcriptColorSeed(speakerKey: string) {
  const palette = ["#315f8b", "#b86432", "#557a46", "#6d4fa7", "#a33f57", "#0f766e"];
  const score = Array.from(speakerKey).reduce((sum, char) => sum + char.charCodeAt(0), 0);
  return palette[score % palette.length];
}

async function copyText(text: string) {
  if (navigator.clipboard?.writeText) {
    await navigator.clipboard.writeText(text);
    return;
  }

  const textarea = document.createElement("textarea");
  textarea.value = text;
  textarea.setAttribute("readonly", "true");
  textarea.style.position = "fixed";
  textarea.style.opacity = "0";
  document.body.appendChild(textarea);
  textarea.select();
  document.execCommand("copy");
  document.body.removeChild(textarea);
}

export default function MeetingPreview() {
  const { id } = useParams();
  const [searchParams] = useSearchParams();
  const audioRef = useRef<HTMLAudioElement | null>(null);
  const audioPlaybackErrorShownRef = useRef<string | null>(null);
  const transcriptItemRefs = useRef<Record<number, HTMLDivElement | null>>({});
  const [meeting, setMeeting] = useState<MeetingVO | null>(null);
  const [transcripts, setTranscripts] = useState<MeetingTranscriptVO[]>([]);
  const [meetingChapters, setMeetingChapters] = useState<MeetingChapterVO[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");
  const [pageTab, setPageTab] = useState<PreviewPageTab>("summary");
  const [activeTranscriptId, setActiveTranscriptId] = useState<number | null>(null);
  const [passwordRequired, setPasswordRequired] = useState(false);
  const [passwordVerified, setPasswordVerified] = useState(false);
  const [accessPassword, setAccessPassword] = useState("");
  const [passwordError, setPasswordError] = useState("");
  const [audioPlaying, setAudioPlaying] = useState(false);
  const [audioCurrentTime, setAudioCurrentTime] = useState(0);
  const [audioDuration, setAudioDuration] = useState(0);
  const [audioPlaybackRate, setAudioPlaybackRate] = useState(1);
  const [isMetricsExpanded, setIsMetricsExpanded] = useState(false);
  const [linkedTranscriptIds, setLinkedTranscriptIds] = useState<number[]>([]);
  const [linkedChapterKey, setLinkedChapterKey] = useState<string | null>(null);
  const [isMobile, setIsMobile] = useState(() =>
    typeof window !== "undefined" ? window.matchMedia("(max-width: 767px)").matches : false,
  );
  const presetAccessPassword = useMemo(() => (searchParams.get("accessPassword") || "").trim(), [searchParams]);

  useEffect(() => {
    let mounted = true;

    const load = async () => {
      if (!id) {
        setError(TEXT.missingMeetingId);
        setLoading(false);
        return;
      }

      setLoading(true);
      setError("");
        setMeeting(null);
        setTranscripts([]);
        setMeetingChapters([]);
        setPasswordRequired(false);
        setPasswordVerified(false);
        setAccessPassword(presetAccessPassword);
        setPasswordError("");

        try {
          const meetingId = Number(id);
          const accessRes = await getMeetingPreviewAccess(meetingId);
        if (!mounted) {
          return;
        }

          const requiresPassword = !!accessRes.data.data.passwordRequired;
          setPasswordRequired(requiresPassword);
          if (requiresPassword) {
            if (!presetAccessPassword) {
              setLoading(false);
              return;
            }
            try {
              const previewRes = await getPublicMeetingPreview(meetingId, presetAccessPassword);
              if (!mounted) {
                return;
              }
              setMeeting(previewRes.data.data.meeting);
              setTranscripts(previewRes.data.data.transcripts || []);
              setMeetingChapters(previewRes.data.data.chapters || []);
              setPasswordVerified(true);
              return;
            } catch (requestError: any) {
              if (!mounted) {
                return;
              }
              setPasswordError(requestError?.response?.data?.msg || requestError?.msg || TEXT.invalidPassword);
              setPasswordVerified(false);
              setLoading(false);
              return;
            }
          }

          const previewRes = await getPublicMeetingPreview(meetingId);
        if (!mounted) {
          return;
        }

        setMeeting(previewRes.data.data.meeting);
        setTranscripts(previewRes.data.data.transcripts || []);
        setMeetingChapters(previewRes.data.data.chapters || []);
        setPasswordVerified(true);
      } catch (requestError: any) {
        if (!mounted) {
          return;
        }

        setError(requestError?.response?.data?.msg || requestError?.msg || TEXT.loadFailed);
      } finally {
        if (mounted) {
          setLoading(false);
        }
      }
    };

    load();

    return () => {
      mounted = false;
    };
  }, [id, presetAccessPassword]);

  useEffect(() => {
    if (typeof window === "undefined") {
      return;
    }

    const mediaQuery = window.matchMedia("(max-width: 767px)");
    const handleChange = (event: MediaQueryListEvent) => {
      setIsMobile(event.matches);
    };

    setIsMobile(mediaQuery.matches);
    mediaQuery.addEventListener("change", handleChange);
    return () => {
      mediaQuery.removeEventListener("change", handleChange);
    };
  }, []);

  const analysis = useMemo(
    () => buildMeetingAnalysis(meeting?.analysis, meeting?.summaryContent, meeting?.tags || ""),
    [meeting?.analysis, meeting?.summaryContent, meeting?.tags],
  );

  const participants = useMemo(() => splitDisplayItems(meeting?.participants), [meeting?.participants]);
  const transcriptSpeakers = useMemo(() => {
    const speakers = transcripts
      .map((item) => item.speakerName || item.speakerLabel || item.speakerId || "")
      .map((item) => item.trim())
      .filter(Boolean);
    return Array.from(new Set(speakers));
  }, [transcripts]);
  const tags = useMemo(() => splitDisplayItems(meeting?.tags), [meeting?.tags]);
  const keywords = useMemo(() => analysis.keywords || [], [analysis.keywords]);
  const playbackAudioUrl = useMemo(() => resolveMeetingPlaybackAudioUrl(meeting), [meeting]);
  const aiCatalogEnabled = meeting?.aiCatalogEnabled !== false;
  const statusMeta = STATUS_META[meeting?.status || 0] || {
    label: TEXT.statusPending,
    className: "is-warning",
    hint: TEXT.hintPending,
  };
  const shareUrl = typeof window !== "undefined" ? window.location.href : "";
  const participantCountValue =
    isMobile && transcriptSpeakers.length > 0 ? transcriptSpeakers.length : participants.length;

  const meetingDuration = useMemo(() => {
    if (transcripts.length > 0) {
      const last = transcripts[transcripts.length - 1];
      return last.endTime || 0;
    }
    return 0;
  }, [transcripts]);

  const catalogChapterLinks = useMemo<ChapterTranscriptLink[]>(() => {
    const transcriptIdToIndex = new Map(transcripts.map((item, index) => [item.id, index]));
    const sourceChapters: MeetingChapterVO[] = meetingChapters.length
      ? meetingChapters
      : analysis.chapters.map((item) => ({
          title: item.title,
          time: item.time,
        }));

    return sourceChapters.map((chapter, index) => {
      let matchedTranscripts: MeetingTranscriptVO[] = [];
      const sourceTranscriptIds = Array.isArray(chapter.sourceTranscriptIds)
        ? chapter.sourceTranscriptIds
            .map((item) => Number(item))
            .filter((item) => Number.isFinite(item) && transcriptIdToIndex.has(item))
        : [];

      if (sourceTranscriptIds.length) {
        matchedTranscripts = sourceTranscriptIds
          .map((item) => transcripts[transcriptIdToIndex.get(item)!])
          .filter(Boolean);
      } else if (chapter.startTranscriptId && chapter.endTranscriptId) {
        const startIndex = transcriptIdToIndex.get(Number(chapter.startTranscriptId));
        const endIndex = transcriptIdToIndex.get(Number(chapter.endTranscriptId));
        if (startIndex !== undefined && endIndex !== undefined) {
          matchedTranscripts = transcripts.slice(Math.min(startIndex, endIndex), Math.max(startIndex, endIndex) + 1);
        }
      } else {
        const startMs = typeof chapter.startTime === "number" ? chapter.startTime : parseChapterTimeToMs(chapter.time);
        const nextChapterStartMs = sourceChapters
          .slice(index + 1)
          .map((item) => (typeof item.startTime === "number" ? item.startTime : parseChapterTimeToMs(item.time)))
          .find((item): item is number => item !== null && startMs !== null && item > startMs);

        if (startMs !== null) {
          const firstTranscriptIndex = transcripts.findIndex((item) => item.endTime > startMs);
          if (firstTranscriptIndex >= 0) {
            const lastTranscriptIndex =
              nextChapterStartMs === undefined
                ? transcripts.length
                : transcripts.findIndex((item) => item.startTime >= nextChapterStartMs);
            matchedTranscripts = transcripts.slice(
              firstTranscriptIndex,
              lastTranscriptIndex >= 0 ? lastTranscriptIndex : transcripts.length,
            );
          }
        }
      }

      return {
        key: `${chapter.chapterNo ?? index}-${chapter.title || "chapter"}`,
        title: chapter.title || `章节 ${index + 1}`,
        timeLabel: chapter.time || "--:--",
        transcriptIds: matchedTranscripts.map((item) => item.id),
        firstTranscriptId: matchedTranscripts[0]?.id ?? null,
        firstTranscriptStartTime: matchedTranscripts[0]?.startTime ?? null,
      };
    });
  }, [analysis.chapters, meetingChapters, transcripts]);

  useEffect(() => {
    if (!aiCatalogEnabled && pageTab === "catalog") {
      setPageTab("summary");
    }
  }, [aiCatalogEnabled, pageTab]);

  useEffect(() => {
    if (!activeTranscriptId) {
      return;
    }

    const target = transcriptItemRefs.current[activeTranscriptId];
    if (!target) {
      return;
    }

    // 使用 center 模式确保当前说话段落始终位于视口中央，避免被底部的浮动控件遮挡
    target.scrollIntoView({ behavior: "smooth", block: "center" });
  }, [activeTranscriptId]);

  const handleTranscriptSeek = (item: MeetingTranscriptVO) => {
    if (!audioRef.current) {
      return;
    }

    audioRef.current.currentTime = Math.max(0, (item.startTime || 0) / 1000);
    audioRef.current.play().catch(() => {});
  };

  const handleLocateChapterTranscript = (index: number) => {
    const link = catalogChapterLinks[index];
    if (link && link.firstTranscriptId) {
      setPageTab("transcript");
      setLinkedTranscriptIds(link.transcriptIds);
      setLinkedChapterKey(link.key);
      setActiveTranscriptId(link.firstTranscriptId);

      // 自动跳转并播放音频
      if (audioRef.current && link.firstTranscriptStartTime !== null) {
        audioRef.current.currentTime = Math.max(0, link.firstTranscriptStartTime / 1000);
        audioRef.current.play().catch(() => {
          // 部分浏览器（尤其是移动端）可能会拦截非直接交互触发的播放
          // 但由于这是由用户点击目录项触发的，通常会被允许
        });
      }
    }
  };

  const toggleAudioPlayback = () => {
    if (!audioRef.current) return;
    if (audioPlaying) {
      audioRef.current.pause();
    } else {
      audioRef.current.play().catch(() => {});
    }
  };

  const handleAudioProgressChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (!audioRef.current) return;
    const time = parseFloat(e.target.value);
    audioRef.current.currentTime = time;
    setAudioCurrentTime(time);
  };

  const cyclePlaybackRate = () => {
    if (!audioRef.current) return;
    const nextRate = audioPlaybackRate === 1 ? 1.5 : audioPlaybackRate === 1.5 ? 2 : 1;
    audioRef.current.playbackRate = nextRate;
    setAudioPlaybackRate(nextRate);
  };

  const formatPlayerTime = (seconds: number) => {
    if (!seconds || isNaN(seconds)) return '00:00';
    const m = Math.floor(seconds / 60);
    const s = Math.floor(seconds % 60);
    return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
  };

  const handleAudioTimeUpdate = () => {
    if (!audioRef.current) return;
    const currentSeconds = audioRef.current.currentTime;
    setAudioCurrentTime(currentSeconds);
    
    // Also update duration if it's available now
    if (audioRef.current.duration && audioDuration !== audioRef.current.duration) {
      setAudioDuration(audioRef.current.duration);
    }

    if (transcripts.length === 0) return;

    const currentMs = currentSeconds * 1000;
    const currentItem = transcripts.find(
      (item) => currentMs >= (item.startTime || 0) && currentMs <= (item.endTime || 0),
    );

    setActiveTranscriptId(currentItem?.id || null);
  };

  const handleAudioEnded = () => {
    setAudioPlaying(false);
  };

  const handleAudioPlay = () => setAudioPlaying(true);
  const handleAudioPause = () => setAudioPlaying(false);
  const handleAudioLoadedMetadata = () => {
    if (audioRef.current) {
      setAudioDuration(audioRef.current.duration);
    }
  };

  useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;

    audio.pause();
    audio.load();
    audio.playbackRate = 1;
    setAudioPlaybackRate(1);
    setAudioCurrentTime(0);
    setAudioDuration(0);
    setAudioPlaying(false);
    setActiveTranscriptId(null);
  }, [playbackAudioUrl]);

  const handleAudioError = () => {
    const currentAudioUrl = playbackAudioUrl || "";
    if (!currentAudioUrl || audioPlaybackErrorShownRef.current === currentAudioUrl) {
      return;
    }
    const normalizedUrl = currentAudioUrl.split("#")[0]?.split("?")[0]?.toLowerCase() || "";
    const isM4a = normalizedUrl.endsWith(".m4a");
    message.warning(
      isM4a
        ? "当前 m4a 文件在本机浏览器中无法直接播放。已确认文件与服务端响应基本正常，更可能是浏览器对该录音参数或容器实现的兼容性问题。建议优先使用 mp3、wav，或下载到本地播放。"
        : TEXT.audioUnavailable,
    );
    audioPlaybackErrorShownRef.current = currentAudioUrl;
    setAudioPlaying(false);
  };

  const handlePasswordSubmit = async () => {
    if (!id) {
      return;
    }

    setLoading(true);
    setPasswordError("");
    try {
      const previewRes = await getPublicMeetingPreview(Number(id), accessPassword.trim());
      setMeeting(previewRes.data.data.meeting);
      setTranscripts(previewRes.data.data.transcripts || []);
      setMeetingChapters(previewRes.data.data.chapters || []);
      setPasswordVerified(true);
    } catch (requestError: any) {
      setPasswordError(requestError?.response?.data?.msg || requestError?.msg || TEXT.invalidPassword);
    } finally {
      setLoading(false);
    }
  };

  const handleCopyLink = async () => {
    try {
      await copyText(shareUrl);
      message.success(TEXT.shareCopied);
    } catch {
      message.error(TEXT.shareFailed);
    }
  };

  const handleShareNow = async () => {
    try {
      if (navigator.share) {
        await navigator.share({
          title: meeting?.title || TEXT.previewLabel,
          text: TEXT.shareText,
          url: shareUrl,
        });
        return;
      }

      await copyText(shareUrl);
      message.success(TEXT.shareFallbackCopied);
    } catch {
      message.error(TEXT.shareFailed);
    }
  };

  if (loading && (!passwordRequired || passwordVerified)) {
    return (
      <div className="meeting-preview-page">
        <div className="meeting-preview-shell meeting-preview-loading">
          <div className="meeting-preview-card meeting-preview-hero">
            <Skeleton active paragraph={{ rows: 4 }} />
          </div>
          <div className="meeting-preview-card meeting-preview-section">
            <Skeleton active paragraph={{ rows: 8 }} />
          </div>
          <div className="meeting-preview-card meeting-preview-section">
            <Skeleton active paragraph={{ rows: 10 }} />
          </div>
        </div>
      </div>
    );
  }

  if (passwordRequired && !passwordVerified) {
    return (
      <div className="meeting-preview-page is-password-gate">
        <div className="meeting-preview-shell">
          <div className="meeting-preview-password-card">
            <div className="password-card-header">
              <div className="password-icon-wrapper">
                <LockOutlined />
              </div>
              <h1 className="password-card-title">{TEXT.passwordRequired}</h1>
              <p className="password-card-subtitle">{TEXT.passwordHint}</p>
            </div>

            <div className="password-card-form">
              <div className="password-input-wrapper">
                <Input.Password
                  size="large"
                  value={accessPassword}
                  placeholder={TEXT.passwordPlaceholder}
                  onChange={(event) => setAccessPassword(event.target.value)}
                  onPressEnter={handlePasswordSubmit}
                  prefix={<LockOutlined className="meeting-preview-muted-icon" />}
                  className="modern-password-input"
                />
              </div>
              <Button 
                type="primary" 
                size="large"
                onClick={handlePasswordSubmit} 
                loading={loading} 
                disabled={!accessPassword.trim()}
                className="password-submit-btn"
                block
              >
                {TEXT.openPreview}
              </Button>
            </div>

            {passwordError && (
              <div className="password-error-message">
                <Alert type="error" showIcon message={passwordError} />
              </div>
            )}
          </div>
        </div>
        
        <div className="password-gate-footer">
          <div className="footer-disclaimer">
            <RobotOutlined />
            <span>Secure Access • Powered by iMeeting AI</span>
          </div>
        </div>
      </div>
    );
  }

  if (error) {
    return (
      <div className="meeting-preview-page">
        <div className="meeting-preview-shell meeting-preview-empty">
          <div className="meeting-preview-card meeting-preview-section">
            <Result status="error" title={TEXT.loadFailed} subTitle={error} />
          </div>
        </div>
      </div>
    );
  }

  if (!meeting) {
    return (
      <div className="meeting-preview-page">
        <div className="meeting-preview-shell meeting-preview-empty">
          <div className="meeting-preview-card meeting-preview-section">
            <Empty description={TEXT.noMeetingData} />
          </div>
        </div>
      </div>
    );
  }

  const summaryTabContent = (
    <div className="meeting-preview-tab-panel">
      <section className="meeting-preview-card meeting-preview-section">
        <div className="meeting-preview-summary-box">
          <div className="meeting-preview-summary-section">
            <div className="meeting-preview-summary-section-title">关键词</div>
            <div className="meeting-preview-record-tags">
              {keywords.length ? (
                keywords.map((item) => (
                  <div key={item} className="meeting-preview-record-tag">
                    <span>#{item}</span>
                  </div>
                ))
              ) : (
                <span className="meeting-preview-keywords-empty">暂无关键词</span>
              )}
            </div>
          </div>
        </div>

        <div className="meeting-preview-markdown">
          {meeting.summaryContent ? (
            <ReactMarkdown>{meeting.summaryContent}</ReactMarkdown>
          ) : (
            <Empty description={TEXT.noSummary} />
          )}
        </div>
      </section>
    </div>
  );

  const catalogTabContent = (
    <div className="meeting-preview-tab-panel">
      <section className="meeting-preview-card meeting-preview-section">
        <div className="meeting-preview-section-header">
          <div>
            {/*<div className="meeting-preview-section-kicker">*/}
            {/*  <RobotOutlined />*/}
            {/*  {TEXT.aiAnalysis}*/}
            {/*</div>*/}
            <h2 className="meeting-preview-section-title">{TEXT.pageCatalog}</h2>
          </div>
        </div>

        <div className="meeting-preview-catalog-list">
          {catalogChapterLinks.length ? (
            catalogChapterLinks.map((chapter, index) => (
              <div
                key={chapter.key}
                className={`meeting-preview-catalog-item-container ${linkedChapterKey === chapter.key ? 'active' : ''}`}
              >
                <div className="meeting-preview-catalog-timeline-axis">
                  <div className="meeting-preview-catalog-timeline-dot" />
                  <div className="meeting-preview-catalog-timeline-line" />
                </div>
                <div 
                  className="meeting-preview-catalog-item-card"
                  onClick={() => handleLocateChapterTranscript(index)}
                >
                  <div className="meeting-preview-catalog-item-time">{chapter.timeLabel}</div>
                  <div className="meeting-preview-catalog-item-title-row">
                    <div className="meeting-preview-catalog-item-title">{chapter.title}</div>
                    <button
                      type="button"
                      className="meeting-preview-catalog-item-link"
                      onClick={(e) => {
                        e.stopPropagation();
                        handleLocateChapterTranscript(index);
                      }}
                    >
                      <LinkOutlined /> {TEXT.linkToTranscript}
                    </button>
                  </div>
                </div>
              </div>
            ))
          ) : (
            <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={TEXT.noCatalog} />
          )}
        </div>
      </section>
    </div>
  );

  const transcriptTabContent = (
    <div className="meeting-preview-tab-panel">
      <section className="meeting-preview-card meeting-preview-section">
        <div className="meeting-preview-section-header">
          <div>
            {/*<div className="meeting-preview-section-kicker">*/}
            {/*  <AudioOutlined />*/}
            {/*  {TEXT.transcriptSection}*/}
            {/*</div>*/}
            <h2 className="meeting-preview-section-title">{TEXT.transcriptTitle}</h2>
          </div>
          <div className="meeting-preview-section-extra">
            <ClockCircleOutlined className="meeting-preview-inline-icon" />
            {meetingDuration > 0 ? formatDurationRange(0, meetingDuration) : TEXT.noDuration}
          </div>
        </div>

        {meeting.audioSaveStatus === "FAILED" ? (
          <Alert
            className="meeting-preview-alert"
            type="warning"
            showIcon
            message={meeting.audioSaveMessage || TEXT.audioUnavailable}
          />
        ) : null}

        <div className="meeting-preview-transcript-list">
          {transcripts.length > 0 ? (
            transcripts.map((item) => {
              const speakerKey = item.speakerName || item.speakerLabel || item.speakerId || "speaker";
              const isLinked = linkedTranscriptIds.includes(item.id);
              const isActive = activeTranscriptId === item.id;
              
              return (
                <div
                  key={item.id}
                  ref={(node) => {
                    transcriptItemRefs.current[item.id] = node;
                  }}
                  className={`meeting-preview-transcript-item ${isActive ? "is-active" : ""} ${isLinked ? "is-linked" : ""}`}
                  onClick={() => {
                    handleTranscriptSeek(item);
                    setLinkedTranscriptIds([]); // Clear linked highlight on manual seek
                    setLinkedChapterKey(null);
                  }}
                >
                  <div
                    className="meeting-preview-transcript-avatar"
                    style={{ backgroundColor: transcriptColorSeed(speakerKey) }}
                  >
                    {(speakerKey || "S").slice(0, 1)}
                  </div>
                  <div className="meeting-preview-transcript-content">
                    <div className="meeting-preview-transcript-meta">
                      <span className="meeting-preview-transcript-speaker">{speakerKey}</span>
                      <span className="meeting-preview-transcript-time">
                        {formatDurationRange(item.startTime, item.endTime)}
                      </span>
                    </div>
                    <div className="meeting-preview-transcript-text">
                      {item.content || TEXT.noTranscript}
                    </div>
                  </div>
                </div>
              );
            })
          ) : (
            <Empty description={TEXT.noTranscript} />
          )}
        </div>
      </section>
    </div>
  );

  const formatTotalDuration = (ms: number) => {
    const totalSeconds = Math.floor(ms / 1000);
    const hours = Math.floor(totalSeconds / 3600);
    const minutes = Math.floor((totalSeconds % 3600) / 60);
    const seconds = totalSeconds % 60;

    if (hours > 0) {
      return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
    }
    return `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
  };

  return (
    <div className={`meeting-preview-page ${isMobile ? "is-mobile" : "is-desktop"}`}>
      <div className="meeting-preview-container">
        <div className="meeting-preview-shell">
          {/* Header Title Section */}
          <div className="meeting-preview-top-hero">
            <div className="meeting-preview-hero-logo">
              <RobotOutlined />
            </div>
            <div className="meeting-preview-hero-content">
              <h1 className="meeting-preview-hero-title">{meeting.title || TEXT.untitledMeeting}</h1>
              <div className="meeting-preview-hero-meta">
                <span className={`meeting-preview-status-tag ${statusMeta.className}`}>
                  {statusMeta.label}
                </span>
                {/*<span className="meeting-preview-hero-id">ID: {meeting.id}</span>*/}
              </div>
            </div>
          </div>

          {/* Collapsible Basic Info Section */}
          <div className="meeting-preview-collapsible-section">
            <div 
              className="meeting-preview-collapsible-trigger"
              onClick={() => setIsMetricsExpanded(!isMetricsExpanded)}
            >
              <div className="trigger-left">
                <FileTextOutlined />
                <span>{TEXT.basicInfo}</span>
              </div>
              <div className="trigger-right">
                {isMetricsExpanded ? <UpOutlined /> : <DownOutlined />}
              </div>
            </div>
            
            <div className={`meeting-preview-collapsible-content ${isMetricsExpanded ? 'is-expanded' : ''}`}>
              <div className="meeting-preview-metrics-grid">
                <div className="metric-item">
                  <div className="metric-label">{TEXT.meetingTime}</div>
                  <div className="metric-value">
                    <CalendarOutlined className="metric-value__icon" />
                    {meeting.meetingTime ? dayjs(meeting.meetingTime).format("YYYY-MM-DD HH:mm") : TEXT.notSet}
                  </div>
                </div>
                <div className="metric-item">
                  <div className="metric-label">{TEXT.hostCreator}</div>
                  <div className="metric-value">
                    <UserOutlined className="metric-value__icon" />
                    {meeting.creatorName || TEXT.notSet}
                  </div>
                </div>
                <div className="metric-item">
                  <div className="metric-label">{TEXT.participantsCount}</div>
                  <div className="metric-value">
                    <TeamOutlined className="metric-value__icon" />
                    {participantCountValue} {TEXT.participants}
                  </div>
                </div>
                <div className="metric-item">
                  <div className="metric-label">会议时长</div>
                  <div className="metric-value">
                    <ClockCircleOutlined className="metric-value__icon" />
                    {meetingDuration > 0 ? formatTotalDuration(meetingDuration) : TEXT.notSet}
                  </div>
                </div>
                {tags.length > 0 && (
                  <div className="metric-item metric-item-full">
                    <div className="metric-label">{TEXT.tags}</div>
                    <div className="metric-tags">
                      {tags.map(tag => (
                        <span key={tag} className="metric-tag">#{tag}</span>
                      ))}
                    </div>
                  </div>
                )}
              </div>
            </div>
          </div>

          {/* Sharing Buttons Bar */}
          <div className="meeting-preview-share-bar">
            <Button 
              type="primary" 
              size="large"
              icon={<ShareAltOutlined />} 
              onClick={handleShareNow}
              className="share-btn-primary"
            >
              {TEXT.shareNow}
            </Button>
            <Button 
              size="large"
              icon={<CopyOutlined />} 
              onClick={handleCopyLink}
              className="share-btn-ghost"
            >
              {TEXT.copyLink}
            </Button>
          </div>

          <div className="meeting-preview-layout-full">
            {/* Main Content Area */}
            <main className="meeting-preview-main">
              <div className="meeting-preview-content-card">
                <div className="meeting-preview-tabs-container">
                  <Tabs
                    activeKey={pageTab}
                    onChange={(key) => setPageTab(key as PreviewPageTab)}
                    items={[
                      { key: "summary", label: TEXT.pageSummary },
                      ...(aiCatalogEnabled ? [{ key: "catalog", label: TEXT.pageCatalog }] : []),
                      { key: "transcript", label: TEXT.pageTranscript },
                    ]}
                  />
                </div>

                <div className="meeting-preview-tab-content">
                  {pageTab === "summary" ? summaryTabContent : null}
                  {aiCatalogEnabled && pageTab === "catalog" ? catalogTabContent : null}
                  {pageTab === "transcript" ? transcriptTabContent : null}
                </div>
              </div>
            </main>
          </div>

          <div className="meeting-preview-footer">
            <div className="meeting-preview-disclaimer">
              <RobotOutlined />
              <span>{TEXT.disclaimer}</span>
            </div>
          </div>
        </div>
      </div>

      {/* Floating Audio Player - Permanent mount, visibility controlled */}
      {playbackAudioUrl && (
        <div 
          className="meeting-preview-audio-player-inline"
          style={{ display: pageTab === "transcript" ? "flex" : "none" }}
        >
          <audio
            ref={audioRef}
            src={playbackAudioUrl}
            onTimeUpdate={handleAudioTimeUpdate}
            onPlay={handleAudioPlay}
            onPause={handleAudioPause}
            onEnded={handleAudioEnded}
            onLoadedMetadata={handleAudioLoadedMetadata}
            onError={handleAudioError}
            style={{ display: "none" }}
            preload="auto"
          />

          <div className="audio-player-content">
            <button type="button" className="audio-play-btn" onClick={toggleAudioPlayback}>
              {audioPlaying ? <PauseOutlined /> : <CaretRightFilled />}
            </button>
            <div className="audio-progress-container">
              <div className="audio-time">{formatPlayerTime(audioCurrentTime)}</div>
              <input
                className="audio-range"
                type="range"
                min={0}
                max={audioDuration || 0}
                step={0.1}
                value={Math.min(audioCurrentTime, audioDuration || 0)}
                onChange={handleAudioProgressChange}
                style={{ backgroundSize: `${audioDuration ? (audioCurrentTime / audioDuration) * 100 : 0}% 100%` }}
              />
              <div className="audio-time">{formatPlayerTime(audioDuration)}</div>
            </div>
            <button type="button" className="audio-speed-btn" onClick={cyclePlaybackRate}>
              {audioPlaybackRate}x
            </button>
          </div>
        </div>
      )}
    </div>
  );
}
