<template>
  <el-drawer
    :model-value="modelValue"
    class="sample-preview-drawer"
    size="min(760px, 94vw)"
    destroy-on-close
    @update:model-value="$emit('update:modelValue', $event)"
  >
    <template #header>
      <div class="sample-preview-heading">
        <span>
          <strong>样本预览</strong>
          <small>{{ sample?.name || sample?.id || "样本详情" }}</small>
        </span>
        <el-tag v-if="sample?.modality" effect="plain">{{ modalityLabel(sample.modality) }}</el-tag>
      </div>
    </template>

    <el-skeleton v-if="loading" :rows="10" animated />
    <el-empty v-else-if="!sample" description="暂无可预览的样本" />
    <template v-else>
      <section class="sample-preview-stage" :class="`modality-${sample.modality || 'file'}`">
        <img
          v-if="showImage"
          :src="sample.preview_url"
          :alt="sample.name || sample.id"
          @error="mediaFailed = true"
        />
        <video
          v-else-if="showVideo"
          :src="sample.preview_url"
          controls
          preload="metadata"
          @error="mediaFailed = true"
        />
        <div v-else class="sample-preview-fallback">
          <el-icon><Picture v-if="sample.modality === 'image'" /><VideoCamera v-else-if="sample.modality === 'video'" /><Histogram v-else-if="sample.modality === 'thermal'" /><Grid v-else-if="sample.modality === 'pointcloud' || sample.modality === 'raster'" /><Document v-else /></el-icon>
          <strong>{{ sample.scene || modalityLabel(sample.modality) }}</strong>
          <span>{{ fallbackMessage }}</span>
        </div>

        <template v-if="showImage">
          <div
            v-for="(box, index) in boundingBoxes"
            :key="`box-${index}`"
            class="sample-ground-truth-box"
            :style="box.style"
          ><span>{{ box.label }}</span></div>
          <svg v-if="polygons.length" class="sample-ground-truth-polygons" viewBox="0 0 100 100" preserveAspectRatio="none">
            <polygon v-for="(polygon, index) in polygons" :key="index" :points="polygon.points" />
          </svg>
          <i
            v-for="(hotspot, index) in hotspots"
            :key="`hotspot-${index}`"
            class="sample-hotspot"
            :style="hotspot"
          ></i>
        </template>

        <div class="sample-preview-stage-meta">
          <el-tag size="small" :type="showImage || showVideo ? 'success' : 'info'" effect="plain">
            {{ showImage || showVideo ? "媒体预览" : "结构化预览" }}
          </el-tag>
          <span v-if="sample.line_id">{{ sample.line_id }} {{ sample.mileage || "" }}</span>
        </div>
      </section>

      <div class="sample-preview-tags">
        <el-tag v-if="sample.source" effect="plain">{{ sample.source }}</el-tag>
        <el-tag v-if="sample.label_status" :type="sample.label_status === 'reviewed' ? 'success' : 'warning'" effect="plain">{{ statusLabel(sample.label_status) }}</el-tag>
        <el-tag v-if="sample.quality_status" :type="sample.quality_status === 'qualified' ? 'success' : 'warning'" effect="plain">{{ statusLabel(sample.quality_status) }}</el-tag>
        <el-tag v-for="tag in sample.difficulty_tags || []" :key="tag" type="info" effect="plain">{{ tag }}</el-tag>
      </div>

      <el-descriptions :column="2" border class="sample-preview-descriptions">
        <el-descriptions-item label="样本编号"><span class="entity-id">{{ sample.id }}</span></el-descriptions-item>
        <el-descriptions-item label="样本名称">{{ sample.name || "-" }}</el-descriptions-item>
        <el-descriptions-item label="场景">{{ sample.scene || "-" }}</el-descriptions-item>
        <el-descriptions-item label="线路位置">{{ locationText }}</el-descriptions-item>
        <el-descriptions-item label="来源">{{ sample.source || "-" }}</el-descriptions-item>
        <el-descriptions-item label="文件大小">{{ sizeText }}</el-descriptions-item>
        <el-descriptions-item label="入池原因">{{ sample.feedback_reason || "-" }}</el-descriptions-item>
        <el-descriptions-item label="目标模型">{{ sample.target_model_group || sample.model_group_id || "-" }}</el-descriptions-item>
        <el-descriptions-item label="存储地址" :span="2"><span class="sample-storage-url">{{ sample.storage_url || "本地临时文件" }}</span></el-descriptions-item>
      </el-descriptions>

      <el-tabs v-model="detailTab" class="sample-preview-tabs">
        <el-tab-pane label="元数据" name="metadata"><pre>{{ prettyJson(sample.metadata) }}</pre></el-tab-pane>
        <el-tab-pane label="标注真值" name="ground-truth"><pre>{{ prettyJson(sample.ground_truth) }}</pre></el-tab-pane>
        <el-tab-pane v-if="hasEvidence" label="证据信息" name="evidence"><pre>{{ prettyJson(sample.evidence) }}</pre></el-tab-pane>
        <el-tab-pane v-if="sample.validation_messages?.length" label="校验信息" name="validation"><pre>{{ prettyJson(sample.validation_messages) }}</pre></el-tab-pane>
      </el-tabs>
    </template>
  </el-drawer>
</template>

<script setup lang="ts">
import { computed, ref, watch } from "vue";
import { Document, Grid, Histogram, Picture, VideoCamera } from "@element-plus/icons-vue";
import type { JsonMap, SamplePreviewData } from "../../types/model-management";

const props = withDefaults(defineProps<{ modelValue: boolean; sample?: SamplePreviewData; loading?: boolean }>(), { loading: false });
defineEmits<{ "update:modelValue": [value: boolean] }>();

const detailTab = ref("metadata");
const mediaFailed = ref(false);
const showImage = computed(() => Boolean(props.sample?.preview_url && !mediaFailed.value && ["image", "thermal", "raster"].includes(String(props.sample.modality))));
const showVideo = computed(() => Boolean(props.sample?.preview_url && !mediaFailed.value && props.sample.modality === "video"));
const hasEvidence = computed(() => Object.keys(asMap(props.sample?.evidence)).length > 0);
const locationText = computed(() => [props.sample?.line_id, props.sample?.mileage].filter(Boolean).join(" / ") || "-");
const sizeText = computed(() => formatBytes(Number(props.sample?.size_bytes || props.sample?.metadata?.size_bytes || 0)));
const fallbackMessage = computed(() => {
  if (mediaFailed.value) return "媒体格式暂不支持浏览器直接显示，已保留结构化信息";
  if (String(props.sample?.storage_url || "").startsWith("s3://")) return "对象存储制品未挂载到浏览器，显示标注与证据信息";
  return "当前样本提供结构化信息预览";
});

const boundingBoxes = computed(() => {
  const truth = asMap(props.sample?.ground_truth);
  const evidence = asMap(props.sample?.evidence);
  const objects = Array.isArray(truth.objects) ? truth.objects : [];
  const rows = objects.map((item) => asMap(item));
  if (!rows.length && evidence.geometry) rows.push({ category: evidence.category || props.sample?.scene || "证据区域", bbox: evidence.geometry });
  return rows.map((item) => {
    const bbox = normalizedBox(item.bbox);
    return {
      label: String(item.category || item.label || "标注区域"),
      style: { left: `${bbox[0] * 100}%`, top: `${bbox[1] * 100}%`, width: `${bbox[2] * 100}%`, height: `${bbox[3] * 100}%` }
    };
  }).filter((item) => Number.parseFloat(item.style.width) > 0 && Number.parseFloat(item.style.height) > 0);
});

const polygons = computed(() => {
  const truth = asMap(props.sample?.ground_truth);
  const entries = [...(Array.isArray(truth.regions) ? truth.regions : []), ...(Array.isArray(truth.masks) ? truth.masks : [])].map((item) => asMap(item));
  return entries.map((item) => ({ points: polygonText(item.polygon || item.points) })).filter((item) => item.points);
});

const hotspots = computed(() => {
  const truth = asMap(props.sample?.ground_truth);
  return (Array.isArray(truth.hotspots) ? truth.hotspots : []).map((item) => {
    const point = asMap(item);
    return { left: `${Number(point.x || 0.5) * 100}%`, top: `${Number(point.y || 0.5) * 100}%` };
  });
});

watch(() => [props.modelValue, props.sample?.id, props.sample?.preview_url], () => {
  mediaFailed.value = false;
  detailTab.value = "metadata";
});

function asMap(value: unknown): JsonMap {
  return value && typeof value === "object" && !Array.isArray(value) ? value as JsonMap : {};
}

function normalizedBox(value: unknown): [number, number, number, number] {
  if (Array.isArray(value) && value.length >= 4) return value.slice(0, 4).map((item) => Math.max(0, Math.min(1, Number(item) || 0))) as [number, number, number, number];
  const box = asMap(value);
  const x = Number(box.x ?? box.left ?? box.xmin ?? 0);
  const y = Number(box.y ?? box.top ?? box.ymin ?? 0);
  const width = Number(box.width ?? box.w ?? (Number(box.xmax || 0) - x));
  const height = Number(box.height ?? box.h ?? (Number(box.ymax || 0) - y));
  return [x, y, width, height].map((item) => Math.max(0, Math.min(1, item))) as [number, number, number, number];
}

function polygonText(value: unknown) {
  if (!Array.isArray(value)) return "";
  return value.map((point) => Array.isArray(point) ? `${Number(point[0]) * 100},${Number(point[1]) * 100}` : "").filter(Boolean).join(" ");
}

function modalityLabel(value: unknown) {
  return ({ image: "可见光图片", video: "视频", thermal: "红外热成像", pointcloud: "三维点云", raster: "栅格影像", tif: "栅格影像" } as Record<string, string>)[String(value)] || String(value || "文件");
}

function statusLabel(value: unknown) {
  return ({ reviewed: "已复核", unlabeled: "未标注", queued: "待标注", excluded: "已排除", qualified: "质量合格", passed: "已通过", valid: "校验通过", invalid: "校验失败", "待测试": "待测试", "待校验": "待校验" } as Record<string, string>)[String(value)] || String(value || "-");
}

function formatBytes(value: number) {
  if (!value) return "-";
  if (value < 1024) return `${value} B`;
  if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
  return `${(value / 1024 / 1024).toFixed(1)} MB`;
}

function prettyJson(value: unknown) {
  const normalized = value && (typeof value !== "object" || Object.keys(value as object).length) ? value : {};
  return JSON.stringify(normalized, null, 2);
}
</script>

<style scoped>
.sample-preview-heading { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.sample-preview-heading span strong, .sample-preview-heading span small { display: block; }
.sample-preview-heading span strong { color: #172a46; font-size: 18px; }
.sample-preview-heading span small { margin-top: 4px; color: #64748b; font-size: 12px; }
.sample-preview-stage { position: relative; min-height: 330px; max-height: 480px; display: grid; place-items: center; overflow: hidden; border: 1px solid #cbd8e5; border-radius: 5px; background: repeating-linear-gradient(135deg, #edf3f8 0 14px, #f7fafc 14px 28px); }
.sample-preview-stage img, .sample-preview-stage video { width: 100%; max-height: 480px; display: block; object-fit: contain; background: #0f172a; }
.sample-preview-fallback { padding: 48px 24px; display: grid; place-items: center; gap: 10px; color: #17365d; text-align: center; }
.sample-preview-fallback .el-icon { font-size: 56px; }
.sample-preview-fallback strong { font-size: 18px; }
.sample-preview-fallback span { max-width: 430px; color: #64748b; font-size: 12px; line-height: 1.7; }
.sample-preview-stage-meta { position: absolute; right: 12px; bottom: 12px; left: 12px; display: flex; align-items: center; justify-content: space-between; gap: 10px; color: #334155; font-size: 12px; }
.sample-preview-stage-meta > span { padding: 4px 8px; background: rgba(255, 255, 255, .9); }
.sample-ground-truth-box { position: absolute; z-index: 2; min-width: 10px; min-height: 10px; border: 2px solid #dc2626; pointer-events: none; }
.sample-ground-truth-box span { position: absolute; top: -22px; left: -2px; padding: 2px 5px; background: #dc2626; color: #fff; font-size: 10px; white-space: nowrap; }
.sample-ground-truth-polygons { position: absolute; inset: 0; z-index: 2; width: 100%; height: 100%; pointer-events: none; }
.sample-ground-truth-polygons polygon { fill: rgba(234, 88, 12, .28); stroke: #ea580c; stroke-width: .6; vector-effect: non-scaling-stroke; }
.sample-hotspot { position: absolute; z-index: 3; width: 18px; height: 18px; border: 3px solid #fff; border-radius: 50%; background: #dc2626; box-shadow: 0 0 0 3px rgba(220, 38, 38, .45); transform: translate(-50%, -50%); }
.sample-preview-tags { margin: 14px 0 10px; display: flex; flex-wrap: wrap; gap: 7px; }
.sample-preview-descriptions { margin-bottom: 12px; }
.sample-storage-url { overflow-wrap: anywhere; }
.sample-preview-tabs pre { max-height: 300px; margin: 0; padding: 14px; overflow: auto; border: 1px solid #dce4ed; background: #f8fafc; color: #334155; font: 12px/1.6 Consolas, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }
@media (max-width: 640px) {
  .sample-preview-stage { min-height: 240px; }
  .sample-preview-descriptions :deep(table) { width: 100%; table-layout: fixed; }
  .sample-preview-descriptions :deep(.el-descriptions__cell) { min-width: 0; overflow-wrap: anywhere; word-break: break-word; }
  .sample-preview-descriptions :deep(.el-descriptions__label) { width: 68px; }
}
</style>
