<template>
  <a-modal
    v-model:open="innerOpen"
    title="模板预览"
    width="1240px"
    :footer="null"
    :body-style="{ padding: '16px 20px 22px' }"
    destroy-on-close
    wrap-class-name="native-template-list-preview-modal"
    @cancel="onClose"
  >
    <a-spin :spinning="loading">
      <div v-if="errorText" class="native-preview-error">{{ errorText }}</div>
      <a-row v-else-if="schema" :gutter="20" class="native-preview-row">
        <a-col :xs="24" :lg="11" class="native-preview-left">
          <a-tabs v-model:activeKey="jsonTabKey" size="small" class="json-tabs">
            <a-tab-pane key="template" tab="模板JSON">
              <div class="json-template-pane">
                <div class="json-box-title">画布实际 JSON（模板样式）</div>
                <a-textarea v-model:value="canvasJsonText" :rows="16" class="json-textarea" placeholder="模板 JSON" />
              </div>
            </a-tab-pane>
            <a-tab-pane key="params" tab="参数JSON">
              <div class="params-json-pane">
                <div class="params-json-head">
                  <div class="json-sub-tabs json-sub-tabs--segmented" role="tablist" aria-label="参数数据来源">
                    <button
                      type="button"
                      role="tab"
                      :class="['capsule-tab', { 'is-active': dataSourceType === 'manual' }]"
                      @click="dataSourceType = 'manual'"
                    >
                      手动JSON
                    </button>
                    <button
                      type="button"
                      role="tab"
                      :class="['capsule-tab', { 'is-active': dataSourceType === 'mock' }]"
                      @click="dataSourceType = 'mock'"
                    >
                      模拟JSON
                    </button>
                  </div>
                  <a-button
                    v-show="dataSourceType === 'mock'"
                    size="small"
                    type="primary"
                    ghost
                    class="json-capsule-btn"
                    @click="onGenerateMock"
                  >
                    根据画布生成
                  </a-button>
                </div>
                <a-textarea
                  v-show="dataSourceType === 'manual'"
                  v-model:value="previewDataText"
                  :rows="14"
                  class="json-textarea json-textarea--main"
                  placeholder="手动输入预览数据 JSON"
                />
                <template v-if="dataSourceType === 'mock'">
                  <a-textarea
                    v-model:value="mockDataText"
                    :rows="12"
                    class="json-textarea json-textarea--main"
                    placeholder="点击「根据画布生成」自动填充模拟数据"
                  />
                </template>
              </div>
            </a-tab-pane>
          </a-tabs>
        </a-col>
        <a-col :xs="24" :lg="13" class="native-preview-right">
          <div class="preview-header-row">
            <div class="preview-title">预览样式</div>
            <a-space v-if="previewHtml" size="small" align="center" wrap class="preview-header-actions" @click.stop>
              <a-tooltip title="缩小">
                <a-button type="default" size="small" class="preview-zoom-btn" @click="zoomPreviewOut">
                  <Icon icon="ant-design:zoom-out-outlined" />
                </a-button>
              </a-tooltip>
              <span class="preview-zoom-label">{{ zoomPercentLabel }}</span>
              <a-tooltip title="放大">
                <a-button type="default" size="small" class="preview-zoom-btn" @click="zoomPreviewIn">
                  <Icon icon="ant-design:zoom-in-outlined" />
                </a-button>
              </a-tooltip>
              <a-tooltip title="恢复按窗口适应">
                <a-button type="default" size="small" class="preview-zoom-fit-btn" @click="resetPreviewZoom">适应</a-button>
              </a-tooltip>
              <a-tooltip title="使用浏览器打印当前预览内容">
                <a-button type="primary" size="small" class="preview-print-btn" @click="handleBrowserPrint">
                  <Icon icon="ant-design:printer-outlined" />
                  打印
                </a-button>
              </a-tooltip>
            </a-space>
          </div>
          <div v-if="layoutPaperPx" ref="previewHostRef" class="preview-frame-wrap">
            <template v-if="previewHtml">
              <!-- 内层用 margin:auto，放大后父级可四向滚动，避免 flex 居中导致裁切 -->
              <div class="preview-scroll-flex">
                <div class="preview-zoom-slot">
                  <div
                    class="preview-scale-shim"
                    :style="{
                      width: `${layoutPaperPx.wPx * previewDisplayScale}px`,
                      height: `${layoutPaperPx.hPx * previewDisplayScale}px`,
                    }"
                  >
                    <div
                      class="preview-scale-inner"
                      :style="{
                        width: `${layoutPaperPx.wPx}px`,
                        height: `${layoutPaperPx.hPx}px`,
                        transform: `scale(${previewDisplayScale})`,
                      }"
                    >
                      <iframe
                        ref="previewIframeRef"
                        class="preview-iframe"
                        title="原生模板预览"
                        :srcdoc="previewHtml"
                        :style="{
                          width: `${layoutPaperPx.wPx}px`,
                          height: `${layoutPaperPx.hPx}px`,
                        }"
                        @load="onPreviewIframeLoad"
                      />
                    </div>
                  </div>
                </div>
              </div>
            </template>
            <a-empty v-else description="正在生成预览…" />
          </div>
        </a-col>
      </a-row>
    </a-spin>
  </a-modal>
</template>

<script lang="ts" setup>
  import { computed, nextTick, ref, watch } from 'vue';
  import { useDebounceFn, useResizeObserver } from '@vueuse/core';
  import { Icon } from '/@/components/Icon';
  import { useMessage } from '/@/hooks/web/useMessage';
  import { queryById } from '../printTemplate.api';
  import { renderNativePrintHtml, resolvePrintPageCount } from '../native/core/printRenderer';
  import { stringifyNativeTemplateStyle } from '../native/core/nativeTemplateStyleSerialize';
  import { generateNativeMockDataObject } from '../native/core/nativeMockData';
  import { normalizeImportedNativeSchema } from '../native/core/nativeSchemaNormalize';
  import type { NativeTemplateSchema } from '../native/core/types';

  const props = defineProps<{
    open: boolean;
    /** 打印模板主键 id */
    templateId: string | null;
  }>();

  const emit = defineEmits<{
    (e: 'update:open', v: boolean): void;
  }>();

  const { createMessage } = useMessage();

  const innerOpen = computed({
    get: () => props.open,
    set: (v: boolean) => emit('update:open', v),
  });

  const loading = ref(false);
  const errorText = ref('');
  const schema = ref<NativeTemplateSchema | null>(null);
  const canvasJsonText = ref('{}');
  const jsonTabKey = ref<'template' | 'params'>('template');
  const dataSourceType = ref<'manual' | 'mock'>('mock');
  const previewDataText = ref('{}');
  const mockDataText = ref('{}');
  const previewHtml = ref('');
  /** 预览区容器，用于根据可用空间计算缩放比 */
  const previewHostRef = ref<HTMLElement | null>(null);
  const previewIframeRef = ref<HTMLIFrameElement | null>(null);
  /** 自动适应窗口时的缩放系数（相对纸张像素，通常 ≤1） */
  const autoFitScale = ref(1);
  /** 用户在「适应」基础上的缩放倍数，1 表示与自动适应一致 */
  const zoomMultiplier = ref(1);
  const ZOOM_STEP = 1.15;
  const ZOOM_MULT_MIN = 0.25;
  const ZOOM_MULT_MAX = 4;

  /** 最终用于 transform 的缩放 = 自动适应 × 手动倍数，并做安全上下限 */
  const previewDisplayScale = computed(() => {
    const raw = autoFitScale.value * zoomMultiplier.value;
    if (!Number.isFinite(raw) || raw <= 0) {
      return 1;
    }
    return Math.min(4, Math.max(0.08, raw));
  });

  /** 相对「适应窗口」的百分比，便于用户理解当前放大程度 */
  const zoomPercentLabel = computed(() => `${Math.round(zoomMultiplier.value * 100)}%`);

  /** CSS mm 转 px（与浏览器常规 96dpi 一致） */
  const MM_TO_CSS_PX = 96 / 25.4;

  const activeDataText = computed(() => (dataSourceType.value === 'mock' ? mockDataText.value : previewDataText.value));

  const previewData = computed(() => {
    try {
      return JSON.parse(activeDataText.value || '{}');
    } catch (_e) {
      return {};
    }
  });

  /** 按纸张 mm × 页数换算为像素（理论下限；auto 表格等实际可能更高，见 contentMeasurePx） */
  const paperPixelSize = computed(() => {
    if (!schema.value) {
      return null;
    }
    const pageCount = Math.max(1, resolvePrintPageCount(schema.value, previewData.value));
    const wMm = Number(schema.value.page?.width || 210);
    const hMm = Number(schema.value.page?.height || 297);
    const wPx = wMm * MM_TO_CSS_PX;
    const hPx = hMm * pageCount * MM_TO_CSS_PX;
    return { wPx, hPx, pageCount };
  });

  /** iframe 内实际排版测量（解决 autoPage 表格超出单页高度后仍被裁切） */
  const contentMeasurePx = ref({ w: 0, h: 0 });

  /** 预览/打印用的版面像素：取理论纸张与实测内容的较大值 */
  const layoutPaperPx = computed(() => {
    const ps = paperPixelSize.value;
    if (!ps) {
      return null;
    }
    const m = contentMeasurePx.value;
    return {
      ...ps,
      wPx: Math.max(ps.wPx, m.w || 0),
      hPx: Math.max(ps.hPx, m.h || 0),
    };
  });

  /** 遍历 iframe 文档与 scrollHeight，估算真实内容宽高 */
  function measureIframeContentBox(doc: Document): { w: number; h: number } {
    const body = doc.body;
    let minTop = Infinity;
    let maxBottom = 0;
    let minLeft = Infinity;
    let maxRight = 0;
    const visit = (el: Element) => {
      if (el instanceof HTMLElement) {
        const r = el.getBoundingClientRect();
        if (r.height > 0.5 && r.width > 0.5) {
          minTop = Math.min(minTop, r.top);
          maxBottom = Math.max(maxBottom, r.bottom);
          minLeft = Math.min(minLeft, r.left);
          maxRight = Math.max(maxRight, r.right);
        }
      }
      for (let i = 0; i < el.children.length; i++) {
        visit(el.children[i]);
      }
    };
    visit(body);
    const byRect =
      Number.isFinite(minTop) && maxBottom > 0
        ? { w: Math.ceil(maxRight - minLeft + 6), h: Math.ceil(maxBottom - minTop + 12) }
        : { w: 0, h: 0 };
    const sh = Math.max(doc.documentElement.scrollHeight, body.scrollHeight, byRect.h);
    const sw = Math.max(doc.documentElement.scrollWidth, body.scrollWidth, byRect.w);
    return { w: Math.ceil(sw), h: Math.ceil(sh) };
  }

  function updateContentMeasure() {
    void nextTick(() => {
      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
          const doc = previewIframeRef.value?.contentDocument;
          if (!doc?.body) {
            contentMeasurePx.value = { w: 0, h: 0 };
            scheduleComputeScale();
            return;
          }
          contentMeasurePx.value = measureIframeContentBox(doc);
          scheduleComputeScale();
        });
      });
    });
  }

  async function refreshPreview() {
    if (!schema.value) {
      previewHtml.value = '';
      return;
    }
    try {
      previewHtml.value = await renderNativePrintHtml(schema.value, previewData.value);
    } catch (e: any) {
      previewHtml.value = '';
      createMessage.error(`预览渲染失败：${e?.message || '未知错误'}`);
    }
  }

  const debouncedRefreshPreview = useDebounceFn(() => void refreshPreview(), 320);

  function computePreviewScale() {
    const host = previewHostRef.value;
    const ps = layoutPaperPx.value;
    if (!host || !ps) {
      autoFitScale.value = 1;
      return;
    }
    const pad = 20;
    const availW = Math.max(0, host.clientWidth - pad);
    const availH = Math.max(0, host.clientHeight - pad);
    if (availW <= 0 || availH <= 0 || ps.wPx <= 0 || ps.hPx <= 0) {
      autoFitScale.value = 1;
      return;
    }
    const raw = Math.min(availW / ps.wPx, availH / ps.hPx, 1) * 0.96;
    autoFitScale.value = Number.isFinite(raw) && raw > 0 ? raw : 1;
  }

  function zoomPreviewIn() {
    zoomMultiplier.value = Math.min(ZOOM_MULT_MAX, zoomMultiplier.value * ZOOM_STEP);
    scheduleScrollPreviewCenter();
  }

  function zoomPreviewOut() {
    zoomMultiplier.value = Math.max(ZOOM_MULT_MIN, zoomMultiplier.value / ZOOM_STEP);
    scheduleScrollPreviewCenter();
  }

  /** 缩放后把可视区域滚到预览块大致居中，便于看到全貌 */
  function scheduleScrollPreviewCenter() {
    void nextTick(() => {
      const host = previewHostRef.value;
      if (!host) {
        return;
      }
      const slot = host.querySelector('.preview-zoom-slot') as HTMLElement | null;
      if (!slot) {
        return;
      }
      const sr = slot.getBoundingClientRect();
      const hr = host.getBoundingClientRect();
      const nextLeft = host.scrollLeft + (sr.left - hr.left) + sr.width / 2 - host.clientWidth / 2;
      const nextTop = host.scrollTop + (sr.top - hr.top) + sr.height / 2 - host.clientHeight / 2;
      host.scrollTo({
        left: Math.max(0, nextLeft),
        top: Math.max(0, nextTop),
        behavior: 'smooth',
      });
    });
  }

  /** 恢复为仅按窗口自动适应（清除手动缩放） */
  function resetPreviewZoom() {
    zoomMultiplier.value = 1;
    scheduleComputeScale();
    scheduleScrollPreviewCenter();
  }

  function onPreviewIframeLoad() {
    updateContentMeasure();
    // 二维码等异步绘制后再测一次，避免首次高度偏小
    window.setTimeout(() => updateContentMeasure(), 400);
  }

  /** 调用浏览器打印预览 iframe 内文档（与当前预览 HTML 一致） */
  function handleBrowserPrint() {
    const win = previewIframeRef.value?.contentWindow;
    if (!win) {
      createMessage.warning('预览未就绪，请稍后再试');
      return;
    }
    try {
      win.focus();
      win.print();
    } catch (_e) {
      createMessage.error('无法唤起打印，请检查浏览器是否拦截弹窗');
    }
  }

  function scheduleComputeScale() {
    void nextTick(() => computePreviewScale());
  }

  useResizeObserver(previewHostRef, () => {
    scheduleComputeScale();
  });

  watch([paperPixelSize, previewHtml, () => props.open], () => {
    contentMeasurePx.value = { w: 0, h: 0 };
    scheduleComputeScale();
  });

  watch([layoutPaperPx, () => props.open], () => {
    if (props.open) {
      scheduleComputeScale();
    }
  });

  function onClose() {
    errorText.value = '';
    schema.value = null;
    previewHtml.value = '';
    zoomMultiplier.value = 1;
    autoFitScale.value = 1;
    contentMeasurePx.value = { w: 0, h: 0 };
  }

  function onGenerateMock() {
    if (!schema.value) {
      return;
    }
    try {
      const mockObj = generateNativeMockDataObject(schema.value.elements, canvasJsonText.value);
      mockDataText.value = JSON.stringify(mockObj, null, 2);
      createMessage.success('已根据画布组件生成模拟数据 JSON');
    } catch (e: any) {
      createMessage.error(`生成失败：${e?.message || '未知错误'}`);
    }
  }

  watch(
    () => [props.open, props.templateId] as const,
    async ([isOpen, id]) => {
      if (!isOpen || !id) {
        return;
      }
      loading.value = true;
      errorText.value = '';
      schema.value = null;
      previewHtml.value = '';
      zoomMultiplier.value = 1;
      autoFitScale.value = 1;
      contentMeasurePx.value = { w: 0, h: 0 };
      try {
        const record = (await queryById(id)) as Record<string, any>;
        const rawText = String(record?.templateJson || '').trim();
        if (!rawText) {
          errorText.value = '该记录没有模板 JSON';
          return;
        }
        const parsed = JSON.parse(rawText);
        if (parsed?.engine !== 'native') {
          errorText.value = '仅原生模板（engine=native）支持此预览';
          return;
        }
        const normalized = normalizeImportedNativeSchema(parsed);
        const pw = Number(record?.paperWidthMm);
        const ph = Number(record?.paperHeightMm);
        if (pw > 0 && ph > 0) {
          normalized.page.width = pw;
          normalized.page.height = ph;
        }
        schema.value = normalized;
        canvasJsonText.value = stringifyNativeTemplateStyle(normalized);
        const mockObj = generateNativeMockDataObject(normalized.elements, canvasJsonText.value);
        mockDataText.value = JSON.stringify(mockObj, null, 2);
        previewDataText.value = mockDataText.value;
        dataSourceType.value = 'mock';
        jsonTabKey.value = 'params';
        await refreshPreview();
        scheduleComputeScale();
      } catch (e: any) {
        errorText.value = e?.message || '加载模板失败';
      } finally {
        loading.value = false;
      }
    },
    { immediate: false },
  );

  watch([activeDataText, schema, () => props.open], () => {
    if (!props.open || !schema.value) {
      return;
    }
    void debouncedRefreshPreview();
  });

  watch(
    () => props.open,
    (v) => {
      if (!v) {
        onClose();
      }
    },
  );
</script>

<style scoped lang="less">
  .native-preview-error {
    padding: 12px 4px;
    color: #cf1322;
  }

  .native-preview-row {
    min-height: 440px;
    align-items: stretch;
  }

  .native-preview-left {
    display: flex;
    flex-direction: column;
    min-height: 0;
  }

  .native-preview-right {
    display: flex;
    flex-direction: column;
    min-height: 440px;
    max-height: 72vh;
  }

  .json-tabs {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
    border: 1px solid #f0f0f0;
    border-radius: 10px;
    background: #fff;
    padding: 10px 12px 12px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
  }

  .json-tabs :deep(.ant-tabs-content-holder) {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }

  .json-tabs :deep(.ant-tabs-content),
  .json-tabs :deep(.ant-tabs-tabpane) {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }

  .json-template-pane,
  .params-json-pane {
    display: flex;
    flex-direction: column;
    gap: 12px;
    flex: 1;
    min-height: 0;
  }

  .json-box-title {
    margin-bottom: 0;
    font-size: 12px;
    color: rgba(0, 0, 0, 0.55);
  }

  .json-textarea {
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
    font-size: 12px;
    line-height: 1.45;
  }

  .json-textarea--main {
    flex: 1;
    min-height: 200px;
    resize: none;
  }

  .params-json-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
  }

  /* 手动 / 模拟：胶囊分段（与设计器视觉一致） */
  .json-sub-tabs.json-sub-tabs--segmented {
    display: inline-flex;
    align-items: center;
    padding: 3px 5px;
    background: #f0f2f5;
    border-radius: 999px;
    border: 1px solid #e8e8e8;
    gap: 3px;
    flex-shrink: 0;
  }

  .capsule-tab {
    margin: 0;
    padding: 4px 14px;
    border: none;
    border-radius: 999px;
    background: transparent;
    font-size: 12px;
    line-height: 1.45;
    color: rgba(0, 0, 0, 0.65);
    cursor: pointer;
    transition:
      background 0.2s,
      color 0.2s,
      box-shadow 0.2s;
  }

  .capsule-tab:hover:not(.is-active) {
    color: rgba(0, 0, 0, 0.88);
  }

  .capsule-tab.is-active {
    background: #fff;
    color: #1677ff;
    font-weight: 500;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
  }

  .params-json-head :deep(.json-capsule-btn.ant-btn) {
    height: 28px;
    padding: 0 16px;
    line-height: 26px;
    border-radius: 999px;
  }

  .preview-header-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 10px;
    flex-shrink: 0;
  }

  .preview-title {
    margin-bottom: 0;
    font-size: 13px;
    font-weight: 600;
    color: rgba(0, 0, 0, 0.85);
  }

  .preview-header-actions {
    flex-shrink: 0;
    justify-content: flex-end;
  }

  .preview-print-btn {
    display: inline-flex;
    align-items: center;
    gap: 4px;
  }

  .preview-zoom-label {
    min-width: 40px;
    text-align: center;
    font-size: 12px;
    color: rgba(0, 0, 0, 0.55);
    user-select: none;
  }

  .preview-zoom-btn,
  .preview-zoom-fit-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  .preview-zoom-fit-btn {
    padding: 0 10px;
    font-size: 12px;
  }

  .preview-frame-wrap {
    flex: 1;
    min-height: 0;
    overflow: auto;
    border: 1px solid #f0f0f0;
    border-radius: 10px;
    background: #f5f5f5;
    padding: 12px;
    box-sizing: border-box;
  }

  /* 至少占满可视区；内容超出时由外层 preview-frame-wrap 滚动，避免 flex 居中裁切放大内容 */
  .preview-scroll-flex {
    min-height: 100%;
    display: flex;
    box-sizing: border-box;
  }

  .preview-zoom-slot {
    margin: auto;
    flex-shrink: 0;
  }

  .preview-scale-shim {
    overflow: visible;
    flex-shrink: 0;
    border-radius: 4px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  }

  .preview-scale-inner {
    transform-origin: top left;
    will-change: transform;
  }

  .preview-iframe {
    display: block;
    border: none;
    background: #fff;
  }
</style>

<!-- 弹窗在窄屏下不超出视口 -->
<style lang="less">
  .native-template-list-preview-modal .ant-modal {
    max-width: calc(100vw - 24px) !important;
  }
</style>
