/**
 * PDF 预览组件（设计稿 7c-简化C-极简 · 中栏实现）
 *
 * 与 PdfPreview.tsx 对齐的 props 签名，可直接替换用于测试。
 * 变化：
 *  - 单页视口（非连续滚动）
 *  - 顶部工具栏：略缩图开关、上/下页、页码、缩放、当前高亮、全屏
 *  - 左侧 132px 略缩图面板（filtered / all 两种模式），真实 react-pdf 渲染
 *  - 状态徽标由 reviewPoints 聚合得到
 */
import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
import { Document, Page, pdfjs } from 'react-pdf';
import { toastService } from '~/components/ui/Toast';
import type { ReviewPoint, PdfBboxHighlight } from '../ReviewPointsList';

import 'react-pdf/dist/esm/Page/TextLayer.css';
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';

pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.js';

// ============================================================
// 类型
// ============================================================
type WorstStatus = 'fail' | 'warn' | 'pending' | 'pass';

interface PageAgg {
  worst: WorstStatus;
  count: number;
  issues: number;
}

interface PdfPreviewProps {
  filePath: string;
  targetPage?: number;
  charPositions?: Array<{ box: number[][]; char: string; score: number }>;
  bboxHighlight?: PdfBboxHighlight;
  isStructuredView?: boolean;
  activeReviewPointResultId?: string | null;
  pageOffset?: number;
  onNumPagesChange?: (numPages: number) => void;
  onZoomChange?: (zoomLevel: number) => void;

  // 新增（可选）：用于派生略缩图徽标和当前高亮标签
  reviewPoints?: ReviewPoint[];
}

const THUMB_WIDTH = 112;
const THUMB_ESTIMATED_HEIGHT = 210;
const THUMB_OVERSCAN = 3;
const MAIN_PAGE_MAX_DEVICE_PIXEL_RATIO = 1.5;

// ============================================================
// ReviewPoint → 状态映射
// ============================================================
const STATUS_ORDER: Record<WorstStatus, number> = { fail: 0, warn: 1, pending: 2, pass: 3 };

function getContentItemPage(item: ReviewPoint['content'][string]): number | string | undefined {
  return typeof item === 'string' ? undefined : item?.page;
}

const STATUS_BADGE: Record<WorstStatus, { cls: string; ic: string }> = {
  fail: { cls: 'bg-red-500', ic: 'ri-close-circle-fill' },
  warn: { cls: 'bg-amber-500', ic: 'ri-lightbulb-flash-fill' },
  pending: { cls: 'bg-orange-500', ic: 'ri-question-fill' },
  pass: { cls: 'bg-emerald-500', ic: 'ri-checkbox-circle-fill' },
};

function classifyReviewPoint(p: ReviewPoint): WorstStatus | 'skipped' {
  const status = p.status;
  if (status === 'notApplicable' || status === 'not_applicable') return 'skipped';
  if (p.result === true || (p.result === undefined && status === 'success')) return 'pass';
  if (p.result === false) {
    if (status === 'error') return 'fail';
    if (status === 'warning' || status === 'info') return 'warn';
  }
  if (status === 'success') return 'pass';
  if (status === 'warning' || status === 'info') return 'warn';
  if (status === 'error') return 'fail';
  return 'pass';
}

/** 把 ReviewPoint 涉及的所有页号（字段级）抽出 */
function getPointPages(p: ReviewPoint): number[] {
  const set = new Set<number>();
  const addMaybe = (v: unknown) => {
    const n = typeof v === 'string' ? parseInt(v, 10) : typeof v === 'number' ? v : NaN;
    if (Number.isFinite(n) && n > 0) set.add(n);
  };
  if (p.contentPage) Object.values(p.contentPage).forEach(addMaybe);
  if (p.content) Object.values(p.content).forEach(v => addMaybe(getContentItemPage(v)));
  return [...set].sort((a, b) => a - b);
}

/** 当前规则在某页涉及的字段名列表 */
function getFieldsOnPage(p: ReviewPoint, page: number): string[] {
  const fields: string[] = [];
  const matches = (v: unknown) => {
    const n = typeof v === 'string' ? parseInt(v, 10) : typeof v === 'number' ? v : NaN;
    return n === page;
  };
  if (p.contentPage) {
    Object.entries(p.contentPage).forEach(([k, v]) => {
      if (matches(v)) fields.push(k);
    });
  }
  if (!fields.length && p.content) {
    Object.entries(p.content).forEach(([k, v]) => {
      if (matches(getContentItemPage(v))) fields.push(k);
    });
  }
  return fields;
}

function clamp(value: number, min: number, max: number): number {
  return Math.min(max, Math.max(min, value));
}

function isValidQuad(value: unknown): value is [number, number, number, number] {
  return Array.isArray(value) && value.length === 4 && value.every(item => typeof item === 'number' && Number.isFinite(item));
}

// ============================================================
// 组件
// ============================================================
export function PdfPreviewTest({
  filePath,
  targetPage,
  charPositions,
  bboxHighlight,
  isStructuredView = false,
  activeReviewPointResultId,
  pageOffset = 0,
  onNumPagesChange,
  onZoomChange,
  reviewPoints,
}: PdfPreviewProps) {
  const [numPages, setNumPages] = useState<number | null>(null);
  const [currentPage, setCurrentPage] = useState<number>(1);
  const [zoomLevel, setZoomLevel] = useState(100);
  const [loadError, setLoadError] = useState<string | null>(null);

  const [showThumbs, setShowThumbs] = useState(true);
  const [thumbMode, setThumbMode] = useState<'filtered' | 'all'>('filtered');
  const [coordinateScale, setCoordinateScale] = useState(0.83);
  const [isScaleAutoCalculated, setIsScaleAutoCalculated] = useState(false);
  const [pageInputValue, setPageInputValue] = useState<string>('');
  const [pageRenderTick, setPageRenderTick] = useState(0);
  const [thumbsScrollTop, setThumbsScrollTop] = useState(0);
  const [thumbsViewportHeight, setThumbsViewportHeight] = useState(0);

  const viewportRef = useRef<HTMLDivElement>(null);
  const thumbsPanelRef = useRef<HTMLDivElement>(null);

  const fileUrl = useMemo(() => `/api/pdf-proxy?path=${encodeURIComponent(filePath)}`, [filePath]);

  const activePoint = useMemo<ReviewPoint | undefined>(
    () => reviewPoints?.find(p => p.id === activeReviewPointResultId),
    [reviewPoints, activeReviewPointResultId],
  );

  const rulePages = useMemo<number[]>(() => {
    if (!activePoint) return [];
    return getPointPages(activePoint).map(page => page + pageOffset);
  }, [activePoint, pageOffset]);

  const pageStatusMap = useMemo<Map<number, PageAgg>>(() => {
    const map = new Map<number, PageAgg>();
    if (!reviewPoints?.length) return map;

    reviewPoints.forEach(point => {
      const status = classifyReviewPoint(point);
      if (status === 'skipped') return;

      getPointPages(point).map(page => page + pageOffset).forEach(page => {
        const current = map.get(page) || { worst: 'pass', count: 0, issues: 0 };
        if (STATUS_ORDER[status] < STATUS_ORDER[current.worst]) current.worst = status;
        current.count += 1;
        if (status !== 'pass') current.issues += 1;
        map.set(page, current);
      });
    });

    return map;
  }, [reviewPoints, pageOffset]);

  const highlightLabel = useMemo(() => {
    if (!activePoint) return null;
    const code = activePoint.pointCode || activePoint.id;
    const name = activePoint.pointName || activePoint.title || '';
    return `${code}${name ? ' · ' + name : ''}`;
  }, [activePoint]);

  const effThumbMode: 'filtered' | 'all' =
    thumbMode === 'filtered' && rulePages.length > 0 ? 'filtered' : 'all';

  const thumbPages = useMemo<number[]>(() => {
    if (!numPages) return [];
    if (effThumbMode === 'filtered') return rulePages.filter(page => page >= 1 && page <= numPages);
    return Array.from({ length: numPages }, (_, index) => index + 1);
  }, [effThumbMode, rulePages, numPages]);

  const rulePageSet = useMemo(() => new Set(rulePages), [rulePages]);

  const totalThumbHeight = thumbPages.length * THUMB_ESTIMATED_HEIGHT;

  const visibleThumbRange = useMemo(() => {
    if (!thumbPages.length) {
      return { start: 0, end: 0 };
    }

    const viewportHeight = thumbsViewportHeight || THUMB_ESTIMATED_HEIGHT * 3;
    const start = Math.max(0, Math.floor(thumbsScrollTop / THUMB_ESTIMATED_HEIGHT) - THUMB_OVERSCAN);
    const visibleCount = Math.ceil(viewportHeight / THUMB_ESTIMATED_HEIGHT) + THUMB_OVERSCAN * 2;
    const end = Math.min(thumbPages.length, start + visibleCount);

    return { start, end };
  }, [thumbPages.length, thumbsScrollTop, thumbsViewportHeight]);

  const visibleThumbItems = useMemo(
    () => thumbPages.slice(visibleThumbRange.start, visibleThumbRange.end),
    [thumbPages, visibleThumbRange.start, visibleThumbRange.end],
  );

  const mainPageDevicePixelRatio = useMemo(() => {
    if (typeof window === 'undefined') return 1;
    return Math.min(window.devicePixelRatio || 1, MAIN_PAGE_MAX_DEVICE_PIXEL_RATIO);
  }, []);

  useEffect(() => {
    if (numPages && onNumPagesChange) onNumPagesChange(numPages);
  }, [numPages, onNumPagesChange]);

  useEffect(() => {
    if (onZoomChange) onZoomChange(zoomLevel);
  }, [zoomLevel, onZoomChange]);

  useEffect(() => {
    if (targetPage && numPages) {
      const next = Math.max(1, Math.min(numPages, targetPage + pageOffset));
      setCurrentPage(next);
    }
  }, [targetPage, numPages, pageOffset, activeReviewPointResultId]);

  useEffect(() => {
    if (activeReviewPointResultId) setThumbMode('filtered');
  }, [activeReviewPointResultId]);

  useEffect(() => {
    setIsScaleAutoCalculated(false);
  }, [filePath]);

  useEffect(() => {
    const host = thumbsPanelRef.current;
    if (!host) return;

    const currentIndex = thumbPages.indexOf(currentPage);
    if (currentIndex < 0) return;

    const itemTop = currentIndex * THUMB_ESTIMATED_HEIGHT;
    const itemBottom = itemTop + THUMB_ESTIMATED_HEIGHT;
    const viewportTop = host.scrollTop;
    const viewportBottom = viewportTop + host.clientHeight;

    if (itemTop < viewportTop) {
      host.scrollTo({ top: itemTop, behavior: 'smooth' });
      return;
    }

    if (itemBottom > viewportBottom) {
      host.scrollTo({
        top: Math.max(0, itemBottom - host.clientHeight),
        behavior: 'smooth',
      });
    }
  }, [currentPage, thumbMode, showThumbs, thumbPages]);

  useEffect(() => {
    const host = thumbsPanelRef.current;
    if (!host) return;

    const updateViewport = () => {
      setThumbsViewportHeight(host.clientHeight);
      setThumbsScrollTop(host.scrollTop);
    };

    updateViewport();

    const handleScroll = () => {
      setThumbsScrollTop(host.scrollTop);
    };

    host.addEventListener('scroll', handleScroll, { passive: true });

    let resizeObserver: ResizeObserver | null = null;
    if (typeof ResizeObserver !== 'undefined') {
      resizeObserver = new ResizeObserver(() => {
        updateViewport();
      });
      resizeObserver.observe(host);
    }

    return () => {
      host.removeEventListener('scroll', handleScroll);
      resizeObserver?.disconnect();
    };
  }, [showThumbs, thumbMode, filePath]);

  const onDocumentLoadSuccess = useCallback(
    ({ numPages: loadedNumPages }: { numPages: number }) => {
      setNumPages(loadedNumPages);
      if (targetPage) {
        setCurrentPage(Math.max(1, Math.min(loadedNumPages, targetPage + pageOffset)));
      } else {
        setCurrentPage(page => Math.max(1, Math.min(loadedNumPages, page)));
      }
    },
    [targetPage, pageOffset],
  );

  const onDocumentLoadError = useCallback((error: Error) => {
    console.error('PDF加载错误:', error);
    setLoadError('PDF文档加载失败：' + (error.message || '未知错误'));
  }, []);

  const onMainPageLoadSuccess = useCallback(
    (page: any) => {
      setPageRenderTick(tick => tick + 1);
      if (isScaleAutoCalculated) return;

      setTimeout(() => {
        const pdfOriginalWidthPt = page.view?.[2] || page.originalWidth || page.width;
        const canvas = viewportRef.current?.querySelector('.pdf-main-canvas .react-pdf__Page__canvas') as HTMLCanvasElement | null;
        if (canvas && pdfOriginalWidthPt) {
          const canvasDisplayWidth = canvas.offsetWidth;
          const currentScale = zoomLevel / 100;
          const autoScale = canvasDisplayWidth / currentScale / pdfOriginalWidthPt;
          setCoordinateScale(autoScale);
          setIsScaleAutoCalculated(true);
        }
      }, 200);
    },
    [isScaleAutoCalculated, zoomLevel],
  );

  const goPrev = () => setCurrentPage(page => Math.max(1, page - 1));
  const goNext = () => setCurrentPage(page => Math.min(numPages || page, page + 1));
  const zoomIn = () => setZoomLevel(level => Math.min(200, level + 10));
  const zoomOut = () => setZoomLevel(level => Math.max(50, level - 10));

  const jumpToHighlight = () => {
    if (!activePoint || rulePages.length === 0) {
      toastService.info('当前规则无关联页');
      return;
    }

    const firstPage = rulePages[0];
    if (numPages && firstPage >= 1 && firstPage <= numPages) setCurrentPage(firstPage);
  };

  const handlePageInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setPageInputValue(e.target.value.replace(/\D/g, ''));
  };

  const handlePageJump = () => {
    if (!pageInputValue || !numPages) return;
    const nextPage = parseInt(pageInputValue, 10);
    if (nextPage > 0 && nextPage <= numPages) {
      setCurrentPage(nextPage);
      setPageInputValue('');
      return;
    }

    toastService.warning(`请输入有效页码 (1-${numPages})`);
    setPageInputValue('');
  };

  const bboxRectHighlight = useMemo(() => {
    if (!bboxHighlight || !isValidQuad(bboxHighlight.bbox) || !isValidQuad(bboxHighlight.pageBox)) return null;

    const expectedPage = bboxHighlight.page ?? (typeof bboxHighlight.pageNum === 'number' ? bboxHighlight.pageNum + 1 : targetPage);
    if (!expectedPage || currentPage !== expectedPage + pageOffset) return null;

    const canvas = viewportRef.current?.querySelector('.pdf-main-canvas .react-pdf__Page__canvas') as HTMLCanvasElement | null;
    if (!canvas) return null;

    const [pageX0, pageY0, pageX1, pageY1] = bboxHighlight.pageBox;
    const [bboxLeft, bboxTop, bboxRight, bboxBottom] = bboxHighlight.bbox;
    const pageWidth = pageX1 - pageX0;
    const pageHeight = pageY1 - pageY0;
    if (pageWidth <= 0 || pageHeight <= 0) return null;

    const left = clamp(Math.min(bboxLeft, bboxRight), pageX0, pageX1);
    const right = clamp(Math.max(bboxLeft, bboxRight), pageX0, pageX1);
    const top = clamp(Math.min(bboxTop, bboxBottom), pageY0, pageY1);
    const bottom = clamp(Math.max(bboxTop, bboxBottom), pageY0, pageY1);
    if (right <= left || bottom <= top) return null;

    return {
      x: ((left - pageX0) / pageWidth) * canvas.offsetWidth,
      y: ((top - pageY0) / pageHeight) * canvas.offsetHeight,
      width: ((right - left) / pageWidth) * canvas.offsetWidth,
      height: ((bottom - top) / pageHeight) * canvas.offsetHeight,
      text: bboxHighlight.fieldKey,
    };
  }, [bboxHighlight, targetPage, currentPage, pageOffset, zoomLevel, pageRenderTick]);

  const charRectHighlight = useMemo(() => {
    if (bboxRectHighlight || !charPositions?.length || !targetPage) return null;
    if (currentPage !== targetPage + pageOffset) return null;

    const scale = zoomLevel / 100;
    let minX = Infinity;
    let minY = Infinity;
    let maxX = -Infinity;
    let maxY = -Infinity;
    const chars: string[] = [];

    charPositions.forEach(position => {
      chars.push(position.char);
      position.box.forEach(([x, y]) => {
        if (x < minX) minX = x;
        if (x > maxX) maxX = x;
        if (y < minY) minY = y;
        if (y > maxY) maxY = y;
      });
    });

    return {
      x: minX * coordinateScale * scale,
      y: minY * coordinateScale * scale,
      width: (maxX - minX) * coordinateScale * scale,
      height: (maxY - minY) * coordinateScale * scale,
      text: chars.join(''),
    };
  }, [bboxRectHighlight, charPositions, targetPage, currentPage, pageOffset, zoomLevel, coordinateScale, pageRenderTick]);

  const mainHighlight = bboxRectHighlight || charRectHighlight;

  if (loadError) {
    return (
      <div className="w-full h-full grid place-items-center text-red-500 p-4">{loadError}</div>
    );
  }

  return (
    <Document
      file={fileUrl}
      onLoadSuccess={onDocumentLoadSuccess}
      onLoadError={onDocumentLoadError}
      className="flex flex-col min-h-0 w-full h-full"
      loading={<div className="flex-1 grid place-items-center text-slate-400 text-sm">PDF 加载中…</div>}
      error={<div className="flex-1 grid place-items-center text-red-500">PDF 文档加载失败</div>}
      noData={<div className="flex-1 grid place-items-center text-slate-400">无数据</div>}
    >
      <section
        className="flex flex-col flex-1 min-h-0 w-full bg-slate-100 border border-slate-200 rounded"
      >
        {/* ═════ 顶部工具栏 ═════ */}
        <div className="shrink-0 h-11 px-4 flex items-center justify-between bg-white border-b border-slate-200 text-[12.5px] text-slate-600">
          <div className="flex items-center gap-2">
            <button
              onClick={() => setShowThumbs(s => !s)}
              className={`w-5 h-7 grid place-items-center rounded hover:bg-slate-100 ${
                showThumbs ? 'text-primary' : 'text-slate-400'
              }`}
              title="显示/隐藏页面缩略图"
            >
              <i className="ri-layout-masonry-line"></i>
            </button>
            <span className="mx-0 text-slate-300">|</span>
            <button
              onClick={goPrev}
              disabled={currentPage <= 1}
              className="w-5 h-7 grid place-items-center rounded hover:bg-slate-100 disabled:opacity-40 disabled:cursor-not-allowed"
              title="上一页"
            >
              <i className="ri-arrow-left-s-line"></i>
            </button>
            <span className="font-mono tabular-nums">
              <input
                type="text"
                value={pageInputValue !== '' ? pageInputValue : currentPage}
                onChange={handlePageInputChange}
                onFocus={e => e.currentTarget.select()}
                onBlur={handlePageJump}
                onKeyDown={e => {
                  if (e.key === 'Enter') handlePageJump();
                }}
                className="w-6 h-6 text-center bg-slate-50 border border-slate-200 rounded text-[12px] font-mono outline-none focus:border-primary"
              />
              <span className="text-slate-400"> / {numPages ?? '-'}</span>
            </span>
            <button
              onClick={goNext}
              disabled={!numPages || currentPage >= numPages}
              className="w-5 h-7 grid place-items-center rounded hover:bg-slate-100 disabled:opacity-40 disabled:cursor-not-allowed"
              title="下一页"
            >
              <i className="ri-arrow-right-s-line"></i>
            </button>
            <span className="mx-0 text-slate-300">|</span>
            <button
              onClick={zoomOut}
              disabled={zoomLevel <= 50}
              className="w-5 h-7 grid place-items-center rounded hover:bg-slate-100 disabled:opacity-40"
              title="缩小"
            >
              <i className="ri-zoom-out-line"></i>
            </button>
            <span className="font-mono min-w-[42px] text-center">{zoomLevel}%</span>
            <button
              onClick={zoomIn}
              disabled={zoomLevel >= 200}
              className="w-5 h-7 grid place-items-center rounded hover:bg-slate-100 disabled:opacity-40"
              title="放大"
            >
              <i className="ri-zoom-in-line"></i>
            </button>
          </div>

          <div className="flex items-center gap-2 text-[11.5px] min-w-0">
            {highlightLabel && (
              <>
                <span className="text-slate-400 shrink-0">当前高亮:</span>
                <span
                  className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded bg-amber-50 text-amber-800 border border-amber-200 max-w-[220px]"
                  title={highlightLabel}
                >
                  <i className="ri-focus-3-line shrink-0"></i>
                  <span className="truncate">{highlightLabel}</span>
                </span>
                <button
                  onClick={jumpToHighlight}
                  className="w-7 h-7 grid place-items-center rounded hover:bg-slate-100 shrink-0"
                  title="跳转到高亮页"
                >
                  <i className="ri-crosshair-line"></i>
                </button>
              </>
            )}
          </div>
        </div>

        {/* ═════ 主体：略缩图 + 视口 ═════ */}
        <div className="flex-1 min-h-0 flex min-w-0">
          {/* ── 略缩图面板 ── */}
          {showThumbs && (
            <div className="w-[132px] shrink-0 bg-white border-r border-slate-200 flex flex-col min-h-0">
              {/* 模式切换条 */}
              <div className="shrink-0 px-2 py-2 border-b border-slate-100">
                <div className="inline-flex rounded border border-slate-200 bg-slate-50 p-0.5 text-[10.5px] w-full">
                  <button
                    onClick={() => setThumbMode('filtered')}
                    disabled={rulePages.length === 0}
                    className={`flex-1 h-5 rounded font-medium ${
                      effThumbMode === 'filtered'
                        ? 'bg-white shadow-sm text-primary'
                        : 'text-slate-500 hover:text-slate-700'
                    } ${rulePages.length === 0 ? 'opacity-40 cursor-not-allowed' : ''}`}
                  >
                    只看相关
                  </button>
                  <button
                    onClick={() => setThumbMode('all')}
                    className={`flex-1 h-5 rounded font-medium ${
                      effThumbMode === 'all'
                        ? 'bg-white shadow-sm text-primary'
                        : 'text-slate-500 hover:text-slate-700'
                    }`}
                  >
                    全部 {numPages ?? ''}
                  </button>
                </div>
              </div>

              {/* 略缩图列表 */}
              <div
                ref={thumbsPanelRef}
                className="flex-1 overflow-y-auto py-2 px-2"
              >
                {numPages === null ? (
                  <div className="text-center text-[11px] text-slate-400 py-4">加载中…</div>
                ) : thumbPages.length === 0 ? (
                  <div className="text-center text-[11px] text-slate-400 py-8">
                    <i className="ri-forbid-2-line text-2xl text-slate-300"></i>
                    <div className="mt-1">此规则无关联页面</div>
                  </div>
                ) : (
                  <div className="relative w-full" style={{ height: totalThumbHeight }}>
                    {visibleThumbItems.map((p, visibleIndex) => {
                      const itemIndex = visibleThumbRange.start + visibleIndex;
                      const info = pageStatusMap.get(p);
                      const isCur = p === currentPage;
                      const isRulePage = rulePageSet.has(p);

                      let badge: React.ReactNode = null;
                      if (info) {
                        const b = STATUS_BADGE[info.worst];
                        const num = info.issues > 0 ? info.issues : info.worst === 'pass' ? '' : info.count;
                        badge = (
                          <span
                            className={`absolute top-1 right-1 inline-flex items-center justify-center min-w-[14px] h-[14px] px-0.5 rounded-full text-[9px] font-semibold text-white ${b.cls} shadow ring-1 ring-white`}
                          >
                            {num ? num : <i className={`${b.ic} text-[9px]`}></i>}
                          </span>
                        );
                      }

                      const frameCls = isCur
                        ? 'ring-2 ring-[#00684a] shadow-md'
                        : effThumbMode === 'all' && isRulePage
                        ? 'ring-1 ring-[#00684a]/40 shadow-sm'
                        : 'border border-slate-200 group-hover:border-slate-400 shadow-sm';

                      let fieldsLabel: React.ReactNode = null;
                      if (effThumbMode === 'filtered' && activePoint) {
                        const fs = getFieldsOnPage(activePoint, p - pageOffset);
                        const txt = fs.length ? fs.join(' · ') : '规则锚定页';
                        fieldsLabel = (
                          <div
                            className="text-[10px] leading-tight text-slate-500 text-center mt-0.5 line-clamp-2"
                            title={txt}
                          >
                            {txt}
                          </div>
                        );
                      }

                      return (
                        <button
                          key={p}
                          data-thumb-page={p}
                          onClick={() => setCurrentPage(p)}
                          className="absolute left-0 block w-full group"
                          style={{ top: itemIndex * THUMB_ESTIMATED_HEIGHT }}
                          title={`第 ${p} 页`}
                        >
                          <div className={`relative rounded overflow-hidden bg-white transition ${frameCls}`}>
                            <div className="w-full bg-gradient-to-b from-white to-slate-50 overflow-hidden">
                              <Page
                                pageNumber={p}
                                width={THUMB_WIDTH}
                                devicePixelRatio={1}
                                renderTextLayer={false}
                                renderAnnotationLayer={false}
                                loading={<div className="w-full h-[150px] bg-slate-50" />}
                                error={<div className="w-full h-[150px] bg-slate-50" />}
                              />
                            </div>
                            {badge}
                          </div>
                          <div
                            className={`text-center text-[10.5px] mt-1 ${
                              isCur ? 'text-[#00684a] font-semibold' : 'text-slate-500 group-hover:text-slate-700'
                            }`}
                          >
                            {p}
                          </div>
                          {fieldsLabel}
                        </button>
                      );
                    })}
                  </div>
                )}
              </div>
            </div>
          )}

          {/* ── 视口（单页） ── */}
          <div
            ref={viewportRef}
            className="flex-1 min-h-0 min-w-0 overflow-auto p-4"
            style={{ display: 'flex', justifyContent: 'center', alignItems: 'flex-start' }}
          >
            <div
              className="pdf-main-canvas"
              style={{
                position: 'relative',
                display: 'inline-block',
                textAlign: 'left',
                flexShrink: 0,
              }}
            >
              {numPages !== null && (
                <>
                  <Page
                    pageNumber={Math.min(currentPage, numPages)}
                    scale={zoomLevel / 100}
                    devicePixelRatio={mainPageDevicePixelRatio}
                    renderTextLayer={false}
                    renderAnnotationLayer={false}
                    onLoadSuccess={onMainPageLoadSuccess}
                    className="border border-slate-200 shadow-lg bg-white"
                    loading={<div className="w-[600px] h-[800px] grid place-items-center text-slate-400">页面加载中…</div>}
                  />
                  {mainHighlight && (
                    <svg
                      style={{
                        position: 'absolute',
                        top: 0,
                        left: 0,
                        width: '100%',
                        height: '100%',
                        pointerEvents: 'none',
                        zIndex: 12,
                      }}
                    >
                      <rect
                        x={mainHighlight.x}
                        y={mainHighlight.y}
                        width={mainHighlight.width}
                        height={mainHighlight.height}
                        // fill="#00AA00"
                        fill="#abf694"
                        fillOpacity="0.1"
                        stroke="#00684a"
                        strokeWidth="0.5"
                        rx="2"
                      >
                        <title>{`高亮文本: ${mainHighlight.text}`}</title>
                      </rect>
                    </svg>
                  )}
                </>
              )}
            </div>
          </div>
        </div>
      </section>
    </Document>
  );
}
