<!-- components/pdf-viewer/PdfViewer.vue -->
<template>
  <view class="pdf-viewer" :style="{ height: containerHeight + 'px' }">
    <!-- 标题 -->
    <view v-if="pdfInfo.title" class="pdf-header">
      <text class="pdf-title">{{ pdfInfo.title }}</text>
    </view>

    <!-- 滚动容器 -->
    <scroll-view
      class="pdf-scroll"
      scroll-y
      :show-scrollbar="false"
      @scroll="onScroll"
      :scroll-top="scrollTop"
      :style="{ height: scrollContainerHeight + 'px' }"
    >
      <!-- 页面列表 -->
     <view
       v-for="pageNum in totalPages"
       :key="pageNum"
       class="page-item"
       :style="{ minHeight: getPageMinHeight(pageNum) + 'px' }"
     >
       <!-- 页码标签 -->
       <view v-if="props.showPageNumber" class="page-number-tag">
         第 {{ pageNum }} 页
       </view>
     
       <!-- 缩小模式：widthFix + 固定最大高度防过长 -->
       <view v-if="!isZoomed[pageNum] && hasImage(pageNum)" class="fit-mode">
         <image
           :src="getImage(pageNum)"
           mode="widthFix"
           class="pdf-image-fit"
           :show-menu-by-longpress="false"
         />
       </view>
     
       <!-- 高清模式：原始尺寸 + 可拖动 -->
       <view
         v-else-if="isZoomed[pageNum] && hasImage(pageNum)"
         class="zoom-container"
         @touchstart="onTouchStart($event, pageNum)"
         @touchmove="onTouchMove($event, pageNum)"
         @touchend="onTouchEnd"
         @touchcancel="onTouchEnd"
       >
         <view
           class="image-original"
           :style="{
             transform: `translate(${translateX[pageNum] || 0}px, ${translateY[pageNum] || 0}px)`,
             width: (pageRenderWidth[pageNum] || 0) + 'px',
             height: (pageRenderHeight[pageNum] || 0) + 'px'
           }"
         >
           <image
             :src="getImage(pageNum)"
             mode="scaleToFill"
             style="display: block; width: 100%; height: 100%;"
           />
         </view>
       </view>
     
       <!-- 加载中 / 错误 -->
       <view v-else-if="isLoading(pageNum)" class="placeholder loading">
         <view class="spinner"></view>
       </view>
       <view
         v-else-if="isFailed(pageNum)"
         class="placeholder error"
         @click="retryPage(pageNum)"
       >
         ❌ 加载失败，点击重试
       </view>
     
       <!-- 操作按钮 -->
       <view class="action-btns" v-if="hasImage(pageNum)">
         <button
           v-if="!isZoomed[pageNum]"
           class="zoom-btn"
           @click="toggleZoom(pageNum)"
         >
           放大查看
         </button>
         <button
           v-else
           class="reset-btn-inline"
           @click="resetZoom(pageNum)"
         >
           重置
         </button>
       </view>
     </view>
    

      <!-- 全局状态 -->
      <view v-if="globalLoading" class="global-status">
        <view class="spinner"></view>
        <text>正在加载文档...</text>
      </view>
      <view v-else-if="globalError" class="global-status error">
        <text>❌ {{ errorMessage }}</text>
        <button size="mini" @click="reload">重试</button>
      </view>
      <view v-else-if="totalPages > 0" class="progress-bar">
        <text>已加载 {{ loadedSet.size }} / {{ totalPages }} 页</text>
        <view class="bar-bg">
          <view class="bar-fill" :style="{ width: progress + '%' }"></view>
        </view>
      </view>
    </scroll-view>
  </view>
</template>

<script setup lang="ts">
// ================== IMPORTS ==================
import { ref, computed, onMounted, onUnmounted } from 'vue';
import * as pdfjsLib from 'pdfjs-dist/build/pdf';

// 👇 关键：静态导入 worker（Vite 语法）
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker?url';

// ================== PROPS ==================
interface PdfInfo {
  title?: string;
  url: string;
}

const props = withDefaults(defineProps<{
  pdfInfo: PdfInfo;
  autoLoad?: boolean;
  lazyLoad?: boolean;
  maxRetryCount?: number;
  showPageName?: boolean;
  showPageNumber?: boolean;
}>(), {
  autoLoad: true,
  lazyLoad: true,
  maxRetryCount: 3,
  showPageNumber: false,
});

// ================== STATE ==================
const isMounted = ref(true);
const containerHeight = ref(0);
const scrollContainerHeight = ref(0);

// PDF 文档
const pdfDoc = ref<pdfjsLib.PDFDocumentProxy | null>(null);
const totalPages = ref(0);

// 页面数据（按页存储）
const images = ref<string[]>([]);
const loading = ref<boolean[]>([]);
const failed = ref<Record<number, number>>({});
const loadedSet = ref<Set<number>>(new Set());

// 👇 每页独立尺寸（支持横版/竖版）
const pageOriginalWidth = ref<Record<number, number>>({});
const pageOriginalHeight = ref<Record<number, number>>({});
const pageRenderWidth = ref<Record<number, number>>({});
const pageRenderHeight = ref<Record<number, number>>({});

// 双模式状态
const isZoomed = ref<Record<number, boolean>>({});
const translateX = ref<Record<number, number>>({});
const translateY = ref<Record<number, number>>({});

// 滚动 & 加载队列
const scrollTop = ref(0);
const lastScroll = ref(0);
const queue = ref<number[]>([]);
const isProcessingQueue = ref(false);

// 全局状态
const globalLoading = ref(false);
const globalError = ref(false);
const errorMessage = ref('');

// ================== COMPUTED ==================
const progress = computed(() => 
  totalPages.value > 0 ? (loadedSet.value.size / totalPages.value) * 100 : 0
);

const hasImage = (pageNum: number) => !!images.value[pageNum - 1];
const getImage = (pageNum: number) => images.value[pageNum - 1] || '';
const isLoading = (pageNum: number) => !!loading.value[pageNum - 1];
const isFailed = (pageNum: number) => (failed.value[pageNum] || 0) > 0;

// ================== LIFECYCLE ==================
onMounted(() => {
  const sys = uni.getSystemInfoSync();
  containerHeight.value = sys.windowHeight;
  scrollContainerHeight.value = sys.windowHeight - (props.pdfInfo.title ? 80 : 40);
  
  if (props.autoLoad) init();
});

onUnmounted(() => {
  isMounted.value = false;
  cleanup();
});

// ================== INIT ==================
const init = async () => {
  if (!props.pdfInfo.url) return setError('PDF URL 为空');
  
  try {
    reset();
    globalLoading.value = true;
    await loadDocument();
    preloadInitialPages();
  } catch (err: any) {
    setError(err.message || '加载失败');
  } finally {
    globalLoading.value = false;
  }
};

// ================== LOAD DOCUMENT ==================
const loadDocument = async () => {
  // ====== 平台差异化设置 worker ======
  let useWorkerFlag = false;

  // #ifdef H5
  pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker; // ← 静态导入
  useWorkerFlag = true;
  // #endif

  // #ifndef H5
  pdfjsLib.GlobalWorkerOptions.workerSrc = undefined;
  useWorkerFlag = false;
  // #endif

  const doc = await pdfjsLib.getDocument({
    url: props.pdfInfo.url,
    cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.11.338/cmaps/',
    cMapPacked: true,
    useWorker: useWorkerFlag,
  }).promise;

  pdfDoc.value = doc;
  totalPages.value = doc.numPages;

  // 初始化数组
  images.value = new Array(doc.numPages).fill('');
  loading.value = new Array(doc.numPages).fill(false);
};

// ================== PAGE LOADING ==================
const preloadInitialPages = () => {
  const count = Math.min(3, totalPages.value);
  for (let i = 1; i <= count; i++) addToQueue(i);
  processQueue();
};

const addToQueue = (pageNum: number) => {
  if (!queue.value.includes(pageNum)) queue.value.push(pageNum);
};

const processQueue = async () => {
  if (isProcessingQueue.value || queue.value.length === 0) return;
  isProcessingQueue.value = true;

  while (queue.value.length > 0) {
    const pageNum = queue.value.shift()!;
    await loadPage(pageNum);
    await new Promise(r => setTimeout(r, 10));
  }

  isProcessingQueue.value = false;
};

const loadPage = async (pageNum: number) => {
  if (!isMounted.value || !pdfDoc.value || loadedSet.value.has(pageNum)) return;
  if (getRetryCount(pageNum) >= props.maxRetryCount) return;

  try {
    loading.value[pageNum - 1] = true;
    const page = await pdfDoc.value.getPage(pageNum);

    // 获取原始尺寸
    const originalViewport = page.getViewport({ scale: 1 });
    pageOriginalWidth.value[pageNum] = originalViewport.width;
    pageOriginalHeight.value[pageNum] = originalViewport.height;

    // 计算高清渲染尺寸（目标宽度 ～1600px）
    const targetPhysicalWidth = 1600;
    const scale = Math.min(5.0, Math.max(1.0, targetPhysicalWidth / originalViewport.width));
    const renderViewport = page.getViewport({ scale });

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d')!;
    canvas.width = renderViewport.width;
    canvas.height = renderViewport.height;
    await page.render({ canvasContext: ctx, viewport: renderViewport }).promise;

    // 存储渲染结果
    pageRenderWidth.value[pageNum] = canvas.width;
    pageRenderHeight.value[pageNum] = canvas.height;
    images.value[pageNum - 1] = canvas.toDataURL('image/jpeg', 0.95);
    loadedSet.value.add(pageNum);
    delete failed.value[pageNum];

    canvas.width = canvas.height = 0;
  } catch (err) {
    console.error(`Page ${pageNum} error:`, err);
    failed.value[pageNum] = (failed.value[pageNum] || 0) + 1;
  } finally {
    if (isMounted.value) loading.value[pageNum - 1] = false;
  }
};

// ================== ZOOM MODE ==================
const toggleZoom = (pageNum: number) => {
  if (!hasImage(pageNum)) return;
  isZoomed.value[pageNum] = true;
};

const resetZoom = (pageNum: number) => {
  isZoomed.value[pageNum] = false;
  translateX.value[pageNum] = 0;
  translateY.value[pageNum] = 0;
};

// ================== DRAGGING ==================
const onTouchStart = (e: any, pageNum: number) => {
  const touches = e.touches || [];
  if (touches.length !== 1) return;
  const x = touches[0].clientX - (translateX.value[pageNum] || 0);
  const y = touches[0].clientY - (translateY.value[pageNum] || 0);
  (window as any).pdfTouchStart = { x, y, pageNum };
};

const onTouchMove = (e: any, pageNum: number) => {
  const touches = e.touches || [];
  if (touches.length !== 1 || !isZoomed.value[pageNum]) return;

  const start = (window as any).pdfTouchStart;
  if (!start || start.pageNum !== pageNum) return;

  const currentX = touches[0].clientX - start.x;
  const currentY = touches[0].clientY - start.y;

  const sys = uni.getSystemInfoSync();
  const viewW = sys.windowWidth;
  const viewH = sys.windowHeight;
  const imgW = pageRenderWidth.value[pageNum] || 0;
  const imgH = pageRenderHeight.value[pageNum] || 0;

  if (imgW === 0 || imgH === 0) return;

  const minX = viewW - imgW > 0 ? 0 : viewW - imgW;
  const minY = viewH - imgH > 0 ? 0 : viewH - imgH;
  const maxX = 0;
  const maxY = 0;

  translateX.value[pageNum] = Math.min(maxX, Math.max(minX, currentX));
  translateY.value[pageNum] = Math.min(maxY, Math.max(minY, currentY));
};

const onTouchEnd = () => {
  delete (window as any).pdfTouchStart;
};

// ================== LAZY LOAD ==================
const onScroll = (e: any) => {
  if (!props.lazyLoad || globalLoading.value) return;
  scrollTop.value = e.detail.scrollTop;
  const now = Date.now();
  if (now - lastScroll.value < 200) return;
  lastScroll.value = now;
  checkVisible();
};







const checkVisible = () => {
  if (totalPages.value === 0) return;

  const sys = uni.getSystemInfoSync();
  const winHeight = sys.windowHeight;
  const top = scrollTop.value;

  // 👇 关键：不依赖页面高度，直接按页码区间预加载
  // 假设每页至少占 200px（保守值）
  const MIN_PAGE_HEIGHT = 200; // px

  const visibleStartPage = Math.max(1, Math.floor(top / MIN_PAGE_HEIGHT));
  const visibleEndPage = Math.min(
    totalPages.value,
    Math.ceil((top + winHeight * 2) / MIN_PAGE_HEIGHT)
  );

  // 预加载前后各 2 页（共约 5～7 页）
  const startPage = Math.max(1, visibleStartPage - 2);
  const endPage = Math.min(totalPages.value, visibleEndPage + 2);

  for (let i = startPage; i <= endPage; i++) {
    if (!loadedSet.value.has(i) && !isLoading(i) && !isFailed(i)) {
      addToQueue(i);
    }
  }

  if (queue.value.length > 0 && !isProcessingQueue.value) {
    processQueue();
  }
};

// ================== UTILS ==================
const retryPage = (pageNum: number) => {
  if (getRetryCount(pageNum) < props.maxRetryCount) {
    addToQueue(pageNum);
    processQueue();
  }
};

const getRetryCount = (pageNum: number) => failed.value[pageNum] || 0;

const reload = () => init();

const reset = () => {
  images.value = [];
  loading.value = [];
  failed.value = {};
  loadedSet.value.clear();
  queue.value = [];
  isZoomed.value = {};
  translateX.value = {};
  translateY.value = {};
  pageOriginalWidth.value = {};
  pageOriginalHeight.value = {};
  pageRenderWidth.value = {};
  pageRenderHeight.value = {};
  globalError.value = false;
  errorMessage.value = '';
};

const cleanup = () => {
  if (pdfDoc.value) {
    pdfDoc.value.destroy();
    pdfDoc.value = null;
  }
};

const setError = (msg: string) => {
  globalError.value = true;
  errorMessage.value = msg;
};

const getPageMinHeight = (pageNum: number) => {
  if (loadedSet.value.has(pageNum)) {
    const w = pageRenderWidth.value[pageNum] || 1;
    const h = pageRenderHeight.value[pageNum] || 1;
    return uni.getSystemInfoSync().windowWidth * (h / w);
  }
  return 400; // px
};

// ================== EXPOSE ==================
defineExpose({
  reload,
  init,
});
</script>

<style scoped lang="scss">
.pdf-viewer {
  width: 100%;
  background: #fff;
}

.pdf-header {
  padding: 24rpx;
  background: #f8f9fa;
  font-size: 32rpx;
  font-weight: bold;
}

.pdf-scroll {
  width: 100%;
}

.page-item {
  position: relative;
  margin-bottom: 10rpx;
  padding: 0 24rpx;
}

.page-number-tag {
  position: absolute;
  top: -40rpx;
  left: 24rpx;
  background: #20269B;
  color: white;
  padding: 4rpx 12rpx;
  border-radius: 20rpx;
  font-size: 24rpx;
  z-index: 10;
}

.pdf-image-fit {
  width: 100%;
  display: block;
  border-radius: 12rpx;
  box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05);
}

.placeholder {
  width: 100%;
  height: 400rpx;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background: #f9f9f9;
  border-radius: 12rpx;
  font-size: 28rpx;
  color: #999;
}
.placeholder.error {
  color: #ff4d4f;
}

.zoom-container {
  overflow: hidden;
  background: #f9f9f9;
  border-radius: 12rpx;
  min-height: 400rpx;
}

.image-original {
  position: relative;
  transition: transform 0.1s ease-out;
}

.reset-btn {
  position: absolute;
  bottom: 20rpx;
  right: 20rpx;
  background: rgba(0,0,0,0.7);
  color: white;
  padding: 12rpx 20rpx;
  border-radius: 6rpx;
  font-size: 24rpx;
  z-index: 999;
}

.global-status {
  text-align: center;
  padding: 80rpx 0;
  color: #666;
  .spinner {
    margin: 0 auto 16rpx;
  }
}
.global-status.error button {
  margin-top: 20rpx;
  background: #20269B;
  color: white;
  border: none;
}

.progress-bar {
  padding: 20rpx;
  text-align: center;
  font-size: 26rpx;
  color: #666;
  .bar-bg {
    width: 100%;
    height: 8rpx;
    background: #eee;
    border-radius: 4rpx;
    margin-top: 8rpx;
    .bar-fill {
      height: 100%;
      background: #20269B;
      transition: width 0.3s;
    }
  }
}

.spinner {
  width: 40rpx;
  height: 40rpx;
  border: 4rpx solid #eee;
  border-top: 4rpx solid #20269B;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}
.action-btns {
  display: flex;
  justify-content: center;
  margin-top: 20rpx;
}

.zoom-btn, .reset-btn-inline {
  padding: 8rpx 24rpx;
  font-size: 24rpx;
  border-radius: 8rpx;
  background: #20269B;
  color: white;
  border: none;
  line-height: 1;
}

.reset-btn-inline {
  background: #666;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}
</style>