<template>
  <el-card class="box" shadow="never">
    <el-tabs v-model="tab" @tab-change="onTab">
      <el-tab-pane label="待审核" name="pending">
        <el-table v-loading="loadP" :data="pending" stripe>
          <el-table-column label="封面" width="100">
            <template #default="{ row }">
              <el-image
                :src="fullUrl(row.imageUrl)"
                fit="cover"
                class="thumb"
                :preview-src-list="[fullUrl(row.imageUrl)]"
              />
            </template>
          </el-table-column>
          <el-table-column prop="title" label="标题" min-width="140" />
          <el-table-column prop="userId" label="用户ID" width="88" />
          <el-table-column prop="author" label="作者" width="100" />
          <el-table-column prop="authorOpenid" label="OpenID" width="120" show-overflow-tooltip />
          <el-table-column prop="uploadDate" label="上传日期" width="120" />
          <el-table-column label="操作" width="300" fixed="right">
            <template #default="{ row }">
              <el-button type="primary" link @click="openDetail(row.id)">详情</el-button>
              <el-button type="success" link @click="doApprove(row.id)">通过</el-button>
              <el-button type="danger" link @click="openRejectDialog(row.id)">拒绝</el-button>
              <el-button type="danger" link @click="doDelete(row.id)">删除</el-button>
            </template>
          </el-table-column>
        </el-table>
      </el-tab-pane>
      <el-tab-pane label="已拒绝" name="rejected">
        <el-table v-loading="loadR" :data="rejected" stripe>
          <el-table-column label="封面" width="100">
            <template #default="{ row }">
              <el-image
                :src="fullUrl(row.imageUrl)"
                fit="cover"
                class="thumb"
                :preview-src-list="[fullUrl(row.imageUrl)]"
              />
            </template>
          </el-table-column>
          <el-table-column prop="title" label="标题" min-width="120" show-overflow-tooltip />
          <el-table-column prop="userId" label="用户ID" width="88" />
          <el-table-column prop="author" label="作者" width="100" />
          <el-table-column prop="rejectDate" label="拒绝日期" width="108" />
          <el-table-column prop="rejectReason" label="拒绝原因" min-width="200" show-overflow-tooltip />
          <el-table-column label="操作" width="160" fixed="right">
            <template #default="{ row }">
              <el-button type="primary" link @click="openDetail(row.id)">详情</el-button>
              <el-button type="danger" link @click="doDelete(row.id)">删除</el-button>
            </template>
          </el-table-column>
        </el-table>
      </el-tab-pane>
      <el-tab-pane label="已通过" name="approved">
        <div class="approved-toolbar">
          <el-input
            v-model="approvedSearch"
            clearable
            placeholder="搜索：标题、作者、作品 ID、日期（如 2026-04-03 或 2026-04）"
            class="approved-search"
            @clear="onApprovedSearch"
            @keyup.enter="onApprovedSearch"
          >
            <template #append>
              <el-button @click="onApprovedSearch">搜索</el-button>
            </template>
          </el-input>
          <div class="approved-actions">
            <el-button :disabled="!selectedApproved.length" @click="batchDownload(false)">
              批量下载原图
            </el-button>
            <el-button type="primary" :disabled="!selectedApproved.length" @click="batchDownload(true)">
              批量下载（水印）
            </el-button>
          </div>
        </div>
        <el-table
          v-loading="loadA"
          :data="approved"
          stripe
          row-key="id"
          @selection-change="onApprovedSelection"
        >
          <el-table-column type="selection" width="44" />
          <el-table-column label="封面" width="88">
            <template #default="{ row }">
              <el-image :src="fullUrl(row.imageUrl)" fit="cover" class="thumb" />
            </template>
          </el-table-column>
          <el-table-column prop="id" label="作品ID" width="82" />
          <el-table-column prop="title" label="标题" min-width="120" show-overflow-tooltip />
          <el-table-column prop="author" label="作者" width="96" show-overflow-tooltip />
          <el-table-column prop="uploadDate" label="上传日" width="102" />
          <el-table-column prop="approvedDate" label="通过/上架" width="102" />
          <el-table-column prop="createTime" label="上传时间" width="158" show-overflow-tooltip />
          <el-table-column prop="authorOpenid" label="OpenID" width="108" show-overflow-tooltip />
          <el-table-column label="下载" width="200" fixed="right">
            <template #default="{ row }">
              <el-button type="primary" link @click="downloadWorkZip(row, false)">原图</el-button>
              <el-button type="primary" link @click="downloadWorkZip(row, true)">水印</el-button>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="120" fixed="right">
            <template #default="{ row }">
              <el-button type="primary" link @click="openDetail(row.id)">详情</el-button>
              <el-button type="danger" link @click="doDelete(row.id)">删除</el-button>
            </template>
          </el-table-column>
          <el-table-column label="排序" width="132" fixed="right">
            <template #default="{ $index }">
              <el-button
                :disabled="$index === 0 || !!approvedSearch.trim()"
                size="small"
                @click="move($index, -1)"
              >
                上移
              </el-button>
              <el-button
                :disabled="$index === approved.length - 1 || !!approvedSearch.trim()"
                size="small"
                @click="move($index, 1)"
              >
                下移
              </el-button>
            </template>
          </el-table-column>
        </el-table>
      </el-tab-pane>
    </el-tabs>

    <el-dialog
      v-model="detailVisible"
      title="作品详情"
      width="720px"
      align-center
      destroy-on-close
      class="work-detail-dlg"
      @closed="detailWork = null"
    >
      <template #footer>
        <el-button @click="detailVisible = false">关闭</el-button>
        <el-button
          v-if="detailWork"
          type="danger"
          :loading="deleteLoading"
          @click="doDeleteFromDetail"
        >
          删除作品
        </el-button>
      </template>
      <div v-loading="detailLoading" class="detail-body">
        <template v-if="detailWork">
          <h4 class="detail-sec">用户上传的图片（{{ detailWork.images.length }} 张）</h4>
          <div class="detail-imgs">
            <el-image
              v-for="(im, idx) in detailWork.images"
              :key="idx"
              :src="mediaUrl(im.url)"
              fit="cover"
              class="detail-thumb"
              preview-teleported
              :preview-src-list="detailPreviewList"
              :initial-index="idx"
            />
          </div>

          <h4 class="detail-sec">作品信息</h4>
          <el-descriptions
            :column="2"
            border
            size="small"
            class="work-detail-desc"
            label-width="132px"
          >
            <el-descriptions-item label="作品 ID">
              {{ detailWork.id }}
            </el-descriptions-item>
            <el-descriptions-item label="状态">{{ workStatusLabel(detailWork.status) }}</el-descriptions-item>
            <el-descriptions-item
              v-if="detailWork.status === 'rejected' && detailWork.rejectReason"
              label="拒绝原因"
              :span="2"
            >
              <span class="detail-desc">{{ detailWork.rejectReason }}</span>
            </el-descriptions-item>
            <el-descriptions-item label="标题" :span="2">{{ detailWork.title }}</el-descriptions-item>
            <el-descriptions-item label="描述" :span="2">
              <span class="detail-desc">{{ detailWork.description || "—" }}</span>
            </el-descriptions-item>
            <el-descriptions-item label="拍摄日期">
              {{ detailWork.shootDate || "—" }}
            </el-descriptions-item>
            <el-descriptions-item label="类别" :span="2">
              {{ formatCategories(detailWork.categories) }}
            </el-descriptions-item>
            <el-descriptions-item label="点赞">{{ detailWork.likesCount }}</el-descriptions-item>
            <el-descriptions-item label="创建时间">{{ detailWork.createTime || "—" }}</el-descriptions-item>
          </el-descriptions>

          <h4 class="detail-sec">提交用户</h4>
          <el-descriptions
            :column="2"
            border
            size="small"
            class="work-detail-desc"
            label-width="132px"
          >
            <el-descriptions-item label="用户 ID">{{ detailWork.userId }}</el-descriptions-item>
            <el-descriptions-item label="昵称">{{ detailWork.author }}</el-descriptions-item>
            <el-descriptions-item label="手机号">{{ detailWork.authorPhone || "—" }}</el-descriptions-item>
            <el-descriptions-item label="OpenID">{{ detailWork.authorOpenid }}</el-descriptions-item>
          </el-descriptions>
        </template>
      </div>
    </el-dialog>

    <el-dialog
      v-model="rejectDialogVisible"
      title="拒绝作品"
      width="520px"
      align-center
      destroy-on-close
      @closed="onRejectDialogClosed"
    >
      <p class="reject-hint">请选择常见原因，并可在下方补充说明（将一并保存）。用户修改后可重新提交审核。</p>
      <el-radio-group v-model="rejectPreset" class="reject-presets">
        <el-radio v-for="p in REJECT_PRESETS" :key="p" :value="p" class="reject-radio">
          {{ p }}
        </el-radio>
      </el-radio-group>
      <el-input
        v-model="rejectDetail"
        type="textarea"
        :rows="4"
        maxlength="500"
        show-word-limit
        placeholder="补充说明（选填）；若选择「自定义」，请在此完整填写拒绝原因"
        class="reject-textarea"
      />
      <template #footer>
        <el-button @click="rejectDialogVisible = false">取消</el-button>
        <el-button type="danger" :loading="rejectSubmitting" @click="confirmReject">
          确认拒绝
        </el-button>
      </template>
    </el-dialog>
  </el-card>
</template>

<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import JSZip from "jszip";
import { ElMessage, ElMessageBox } from "element-plus";
import {
  approveWork,
  deleteWork,
  fetchApprovedWorks,
  fetchPendingWorks,
  fetchRejectedWorks,
  fetchWorkAuditDetail,
  rejectWork,
  sortApprovedWorks,
  type ApprovedWork,
  type PendingWork,
  type RejectedWork,
  type WorkAuditDetailWork,
} from "@/api/admin";
import { resolveAdminMediaUrl, resolveAdminUploadBlobFetchUrl } from "@/utils/mediaUrl";
import { getToken } from "@/utils/request";
import {
  blobWithWatermark,
  fetchImageBlob,
  triggerSave,
} from "@/utils/watermarkDownload";

const REJECT_PRESETS = [
  "画质或构图未达到展示要求",
  "内容与稻城亚丁主题关联度不足",
  "图片或文字存在不当内容",
  "分类、标题或描述需修改",
  "自定义",
] as const;

const tab = ref<"pending" | "rejected" | "approved">("pending");
const pending = ref<PendingWork[]>([]);
const rejected = ref<RejectedWork[]>([]);
const approved = ref<ApprovedWork[]>([]);
const loadP = ref(false);
const loadR = ref(false);
const loadA = ref(false);
const approvedSearch = ref("");
const selectedApproved = ref<ApprovedWork[]>([]);

const detailVisible = ref(false);
const detailLoading = ref(false);
const detailWork = ref<WorkAuditDetailWork | null>(null);
const deleteLoading = ref(false);

const rejectDialogVisible = ref(false);
const rejectTargetId = ref(0);
const rejectPreset = ref<string>(REJECT_PRESETS[0]);
const rejectDetail = ref("");
const rejectSubmitting = ref(false);

const categoryLabels: Record<string, string> = {
  landscape: "风景",
  humanity: "人文",
  ecology: "生态",
  other: "其他",
  mountain: "风景",
  lake: "风景",
  meadow: "生态",
  temple: "人文",
  culture: "人文",
};

const detailPreviewList = computed(() =>
  (detailWork.value?.images ?? []).map((im) => mediaUrl(im.url))
);

function mediaUrl(path: string): string {
  return resolveAdminMediaUrl(path);
}

function blobFetchUrl(displayUrl: string): string {
  const u = resolveAdminUploadBlobFetchUrl(displayUrl);
  return u || displayUrl;
}

function blobFetchInit(): RequestInit {
  const t = getToken();
  return t ? { headers: { Authorization: `Bearer ${t}` } } : {};
}

/** 保存文件名与磁盘格式一致，避免 PNG/WebP 被存成 .jpg 导致系统预览报损 */
function extFromStoragePath(u: string): string {
  const base = (u.split("?")[0] ?? "").split("#")[0].toLowerCase();
  if (base.endsWith(".png")) return "png";
  if (base.endsWith(".webp")) return "webp";
  if (base.endsWith(".gif")) return "gif";
  if (base.endsWith(".jpeg") || base.endsWith(".jpg")) return "jpg";
  return "jpg";
}

function formatCategories(cats: string[]): string {
  if (!cats.length) return "—";
  return cats.map((c) => categoryLabels[c] ?? c).join("、");
}

function workStatusLabel(s: string): string {
  if (s === "pending") return "待审核";
  if (s === "approved") return "已通过";
  if (s === "rejected") return "已拒绝";
  return s || "—";
}

async function openDetail(id: number) {
  detailVisible.value = true;
  detailWork.value = null;
  detailLoading.value = true;
  try {
    const res = await fetchWorkAuditDetail(id);
    detailWork.value = res.data.work;
  } catch {
    ElMessage.error("加载详情失败");
    detailVisible.value = false;
  } finally {
    detailLoading.value = false;
  }
}

function apiOrigin(): string {
  const v = import.meta.env.VITE_API_BASE;
  if (typeof v === "string" && v.trim()) {
    return v.trim().replace(/\/+$/, "");
  }
  return "";
}

function fullUrl(path: string): string {
  if (!path) return "";
  if (path.startsWith("http")) return path;
  if (path.startsWith("/uploads/")) {
    const o = apiOrigin();
    return o ? `${o}${path}` : path;
  }
  return path;
}

async function loadPending() {
  loadP.value = true;
  try {
    const res = await fetchPendingWorks();
    pending.value = res.data.list;
  } finally {
    loadP.value = false;
  }
}

async function loadRejected() {
  loadR.value = true;
  try {
    const res = await fetchRejectedWorks();
    rejected.value = res.data.list;
  } finally {
    loadR.value = false;
  }
}

function onApprovedSelection(rows: ApprovedWork[]) {
  selectedApproved.value = rows;
}

function onApprovedSearch() {
  void loadApproved();
}

async function loadApproved() {
  loadA.value = true;
  try {
    const res = await fetchApprovedWorks({ keyword: approvedSearch.value });
    approved.value = res.data.list;
    selectedApproved.value = [];
  } finally {
    loadA.value = false;
  }
}

function uploadDateLine(row: ApprovedWork, w?: WorkAuditDetailWork): string {
  return (
    row.uploadDate ||
    (w?.createTime ? w.createTime.slice(0, 10) : "") ||
    (row.createTime ? row.createTime.slice(0, 10) : "")
  );
}

async function downloadWorkZip(row: ApprovedWork, watermarked: boolean) {
  try {
    const res = await fetchWorkAuditDetail(row.id);
    const w = res.data.work;
    const raw = w.images?.length ? w.images.map((i) => i.url) : [w.coverImage];
    const urls = raw.filter((u) => !!u);
    if (!urls.length) {
      ElMessage.warning("没有可下载的图片");
      return;
    }
    const author = row.author || w.author || "用户";
    const dateLine = uploadDateLine(row, w);

    if (urls.length === 1) {
      let blob = await fetchImageBlob(blobFetchUrl(mediaUrl(urls[0])), blobFetchInit());
      if (watermarked) {
        blob = await blobWithWatermark(
          blob,
          `作者：${author}`,
          `上传：${dateLine || "—"}`
        );
      }
      triggerSave(
        blob,
        watermarked ? `work-${row.id}-水印.jpg` : `work-${row.id}.${extFromStoragePath(urls[0])}`
      );
      ElMessage.success("已下载");
      return;
    }

    const zip = new JSZip();
    const folder = zip.folder(`work-${row.id}`);
    for (let i = 0; i < urls.length; i++) {
      let blob = await fetchImageBlob(blobFetchUrl(mediaUrl(urls[i])), blobFetchInit());
      if (watermarked) {
        blob = await blobWithWatermark(
          blob,
          `作者：${author}`,
          `上传：${dateLine || "—"}`
        );
      }
      const ext = watermarked ? "jpg" : extFromStoragePath(urls[i]);
      folder?.file(`${i + 1}.${ext}`, blob);
    }
    const out = await zip.generateAsync({ type: "blob" });
    triggerSave(out, watermarked ? `work-${row.id}-水印.zip` : `work-${row.id}.zip`);
    ElMessage.success("已打包下载");
  } catch (e) {
    ElMessage.error(e instanceof Error ? e.message : "下载失败");
  }
}

async function batchDownload(watermarked: boolean) {
  const rows = selectedApproved.value;
  if (!rows.length) {
    ElMessage.warning("请先勾选作品");
    return;
  }
  try {
    ElMessage.info("正在打包，请稍候…");
    const zip = new JSZip();
    for (const row of rows) {
      const res = await fetchWorkAuditDetail(row.id);
      const w = res.data.work;
      const raw = w.images?.length ? w.images.map((i) => i.url) : [w.coverImage];
      const urls = raw.filter((u) => !!u);
      if (!urls.length) continue;
      const author = row.author || w.author || "用户";
      const dateLine = uploadDateLine(row, w);
      const folder = zip.folder(`work-${row.id}`);
      for (let i = 0; i < urls.length; i++) {
        let blob = await fetchImageBlob(blobFetchUrl(mediaUrl(urls[i])), blobFetchInit());
        if (watermarked) {
          blob = await blobWithWatermark(
            blob,
            `作者：${author}`,
            `上传：${dateLine || "—"}`
          );
        }
        const ext = watermarked ? "jpg" : extFromStoragePath(urls[i]);
        folder?.file(`${i + 1}.${ext}`, blob);
      }
    }
    const out = await zip.generateAsync({ type: "blob" });
    triggerSave(out, `works-batch-${watermarked ? "水印-" : ""}${Date.now()}.zip`);
    ElMessage.success("已下载压缩包");
  } catch (e) {
    ElMessage.error(e instanceof Error ? e.message : "批量下载失败");
  }
}

function onTab(name: string | number) {
  if (name === "pending") void loadPending();
  else if (name === "rejected") void loadRejected();
  else void loadApproved();
}

async function doApprove(id: number) {
  await ElMessageBox.confirm("确认通过该作品？", "提示", { type: "warning" });
  await approveWork(id);
  await loadPending();
  await loadRejected();
  await loadApproved();
}

function openRejectDialog(id: number) {
  rejectTargetId.value = id;
  rejectPreset.value = REJECT_PRESETS[0];
  rejectDetail.value = "";
  rejectDialogVisible.value = true;
}

function onRejectDialogClosed() {
  rejectTargetId.value = 0;
  rejectDetail.value = "";
}

function buildRejectReason(): string {
  if (rejectPreset.value === "自定义") {
    return rejectDetail.value.trim();
  }
  const d = rejectDetail.value.trim();
  return d ? `${rejectPreset.value}：${d}` : rejectPreset.value;
}

async function confirmReject() {
  const reason = buildRejectReason();
  if (!reason) {
    ElMessage.warning(
      rejectPreset.value === "自定义" ? "请填写拒绝原因" : "请完善拒绝说明或选择具体原因"
    );
    return;
  }
  if (reason.length > 500) {
    ElMessage.warning("拒绝原因不超过 500 字");
    return;
  }
  const id = rejectTargetId.value;
  if (!id) return;
  rejectSubmitting.value = true;
  try {
    await rejectWork(id, { reason });
    ElMessage.success("已拒绝");
    rejectDialogVisible.value = false;
    await loadPending();
    await loadRejected();
  } finally {
    rejectSubmitting.value = false;
  }
}

async function doDelete(id: number) {
  await ElMessageBox.confirm(
    "确认删除该作品？将同时移除图片、类别与点赞等关联数据，且不可恢复。",
    "删除确认",
    { type: "warning", confirmButtonText: "删除", confirmButtonClass: "el-button--danger" }
  );
  await deleteWork(id);
  ElMessage.success("已删除");
  if (detailWork.value?.id === id) {
    detailVisible.value = false;
    detailWork.value = null;
  }
  await loadPending();
  await loadRejected();
  await loadApproved();
}

async function doDeleteFromDetail() {
  const id = detailWork.value?.id;
  if (id == null) return;
  deleteLoading.value = true;
  try {
    await doDelete(id);
  } finally {
    deleteLoading.value = false;
  }
}

async function move(index: number, delta: number) {
  const arr = [...approved.value];
  const j = index + delta;
  if (j < 0 || j >= arr.length) return;
  [arr[index], arr[j]] = [arr[j], arr[index]];
  approved.value = arr;
  await sortApprovedWorks(arr.map((x) => x.id));
}

onMounted(() => {
  void loadPending();
});
</script>

<style scoped>
.box {
  border-radius: 12px;
}

.approved-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-bottom: 14px;
}

.approved-search {
  flex: 1;
  min-width: 280px;
  max-width: 560px;
}

.approved-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.thumb {
  width: 64px;
  height: 64px;
  border-radius: 8px;
}

.detail-body {
  min-height: 120px;
}

.detail-sec {
  margin: 20px 0 12px;
  font-size: 15px;
  font-weight: 600;
  color: #0f172a;
}

.detail-sec:first-of-type {
  margin-top: 0;
}

.detail-imgs {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.detail-thumb {
  width: 120px;
  height: 120px;
  border-radius: 8px;
}

.detail-desc {
  white-space: pre-wrap;
  word-break: break-word;
}

/* 描述列表标签列：避免「作品 ID」「拍摄日期」等被压成两行 */
.work-detail-dlg :deep(.work-detail-desc .el-descriptions__label) {
  white-space: nowrap !important;
  word-break: keep-all !important;
}

.reject-hint {
  margin: 0 0 14px;
  font-size: 13px;
  line-height: 1.55;
  color: #64748b;
}

.reject-presets {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: 16px;
}

.reject-radio {
  margin-right: 0;
  height: auto;
  white-space: normal;
  line-height: 1.45;
}

.reject-textarea {
  width: 100%;
}
</style>
