<template>
  <div class="store-cover-page" v-loading="loading">
    <div class="page-inner">
      <section class="preview-section">
        <h2 class="block-heading">效果预览</h2>
        <div class="preview-area">
          <div class="preview-card preview-card--blue">
            <div class="preview-media">
              <img :src="previewDemoImg" alt="效果预览示例" class="preview-img preview-img--single" />
              <div class="preview-album-pill" aria-hidden="true">
                <span>相册</span>
                <el-icon>
                  <ArrowRight />
                </el-icon>
              </div>
            </div>
            <div class="preview-meta">
              <div>
                <div class="store-title">
                  {{ storeNameDisplay }}
                </div>
                <el-rate class="preview-rate" :model-value="5" disabled show-score score-template="5.0" text-color="#151515" />
              </div>
              <div class="reviews-right">
                {{ previewReviews }} 条评价
                <el-icon class="reviews-arrow">
                  <ArrowRight />
                </el-icon>
              </div>
            </div>
          </div>
        </div>
      </section>

      <section class="upload-section">
        <h2 class="block-heading">上传封面图</h2>
        <div class="upload-layout">
          <div
            class="upload-slot"
            :class="{ filled: !!draftCover, uploading: uploading }"
            :role="draftCover ? undefined : 'button'"
            :tabindex="draftCover ? -1 : 0"
            @click="onUploadSlotClick"
            @keydown.enter.prevent="onUploadSlotKeydown"
          >
            <input
              ref="fileInputRef"
              type="file"
              class="upload-input-hidden"
              :accept="fileAcceptAttr"
              @change="onFileInputChange"
            />
            <template v-if="!draftCover">
              <el-icon class="upload-slot__plus">
                <Plus />
              </el-icon>
              <div class="upload-slot__primary">上传图片/视频 {{ slotCountTip }}</div>
              <div class="upload-slot__hint">建议尺寸 1242*640px，大小不超过 {{ maxMbDisplay }}MB</div>
            </template>
            <template v-else-if="draftCover.isVideo">
              <div class="upload-slot__preview upload-slot__preview--video">
                <video
                  :src="draftCover.url"
                  class="upload-slot__thumb-v"
                  muted
                  playsinline
                  preload="metadata"
                  :poster="draftCover.posterUrl || undefined"
                />
                <div class="upload-slot__hover-overlay">
                  <button type="button" class="upload-slot__action" @click.stop="openCoverPreview">
                    <el-icon><View /></el-icon>
                    <span>查看</span>
                  </button>
                  <button type="button" class="upload-slot__action" @click.stop="clearDraft">
                    <el-icon><Delete /></el-icon>
                    <span>删除</span>
                  </button>
                </div>
              </div>
            </template>
            <template v-else>
              <div class="upload-slot__preview">
                <img :src="draftCover.url" alt="已选封面图" class="upload-slot__thumb" />
                <div class="upload-slot__hover-overlay">
                  <button type="button" class="upload-slot__action" @click.stop="openCoverPreview">
                    <el-icon><View /></el-icon>
                    <span>查看</span>
                  </button>
                  <button type="button" class="upload-slot__action" @click.stop="clearDraft">
                    <el-icon><Delete /></el-icon>
                    <span>删除</span>
                  </button>
                </div>
              </div>
            </template>
          </div>

          <ul class="instruction-list">
            <li v-for="(line, idx) in instructionLines" :key="idx" class="instruction-list__item">
              <span class="instruction-list__icon" aria-hidden="true">
                <el-icon><InfoFilled /></el-icon>
              </span>
              <span class="instruction-list__text">{{ line }}</span>
            </li>
          </ul>
        </div>
      </section>

      <div class="save-footer">
        <el-button type="primary" class="btn-save" :loading="saving" :disabled="!canSubmit" @click="onSave"> 保存 </el-button>
      </div>
    </div>

    <PcImagePreviewViewer
      v-model:visible="coverImagePreviewVisible"
      :url-list="coverImagePreviewUrls"
      :initial-index="coverImagePreviewInitialIndex"
    />

    <PcVideoPreviewDialog
      v-model="coverVideoPreviewVisible"
      :src="previewTarget?.url || ''"
      @closed="onCoverVideoPreviewClosed"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted } from "vue";
import { ArrowRight, Plus, InfoFilled, View, Delete } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import { getStoreHeadImg, saveStoreHeadImg } from "@/api/modules/storeDecoration";
import { getDetail } from "@/api/modules/homeEntry";
import { localGet } from "@/utils";
import { uploadFilesToOss, isUploadUserCancelledError, isUploadApiErrorAlreadyMessaged } from "@/api/upload.js";
import previewDemoImg from "@/assets/images/uDianShiImg4.svg";
import PcImagePreviewViewer from "@/components/pcMediaPreview/PcImagePreviewViewer.vue";
import PcVideoPreviewDialog from "@/components/pcMediaPreview/PcVideoPreviewDialog.vue";

/** 与商户端封面 imgType 一致 */
const IMG_TYPE_COVER = 38;

const IMAGE_MAX_MB = 10;
const VIDEO_MAX_MB = 80;
/** 与设计稿文案「不超 10M」一致展示 */
const maxMbDisplay = IMAGE_MAX_MB;

/** 对齐官方相册 OSS 快照规则（节选） */
function tryOssVideoSnapshotCoverUrl(videoUrl: string): string {
  if (!videoUrl || typeof videoUrl !== "string") return "";
  if (/upload\.ailien\.shop/i.test(videoUrl)) return "";
  const isOss =
    videoUrl.includes("aliyuncs.com") ||
    videoUrl.includes("oss-cn-") ||
    videoUrl.includes("oss.") ||
    /ailien\.shop/i.test(videoUrl) ||
    videoUrl.includes("alien-volume");
  if (!isOss) return "";
  const sep = videoUrl.includes("?") ? "&" : "?";
  return `${videoUrl}${sep}x-oss-process=video/snapshot,t_0,f_jpg,w_800,h_600,m_fast`;
}

function normalizeCoverMediaUrl(raw: unknown): string {
  const s = typeof raw === "string" ? raw.trim() : "";
  if (!s) return "";
  if (s.startsWith("{")) {
    try {
      const o = JSON.parse(s) as Record<string, unknown>;
      const v = typeof o.video === "string" ? o.video : typeof o.videoUrl === "string" ? o.videoUrl : "";
      const im = typeof o.cover === "string" ? o.cover : "";
      if (/\.(mp4|mov|m4v|webm|3gp)(\?|#|$)/i.test(v)) return v;
      return im || s;
    } catch {
      return s;
    }
  }
  return s;
}

interface DraftCover {
  id?: string | number;
  url: string;
  isVideo: boolean;
  posterUrl?: string;
}

type StoreImgRow = Record<string, unknown>;

const loading = ref(false);
const uploading = ref(false);
const saving = ref(false);

const draftCover = ref<DraftCover | null>(null);

const fileInputRef = ref<HTMLInputElement | null>(null);
/** 图片预览（与 storeHeadMap 一致）；视频用弹窗 + 快照避免删除后空引用 */
const coverImagePreviewVisible = ref(false);
const coverImagePreviewUrls = ref<string[]>([]);
const coverImagePreviewInitialIndex = ref(0);
const coverVideoPreviewVisible = ref(false);
const previewTarget = ref<DraftCover | null>(null);
const slotCountTip = computed(() => (draftCover.value ? `(1/1)` : `(0/1)`));

const previewReviews = ref(1853);
const storeNameDisplay = ref("示例门店");

const fileAcceptAttr = "image/jpeg,image/png,image/webp,video/mp4,video/quicktime,video/webm,.mp4,.MOV,.mov,.webm,.m4v";

const instructionLines = [
  "封面图将展示在门店列表、搜索结果等位置，是用户对门店的第一印象",
  "支持JPG、PNG、WebP格式图片，或MP4、MOV格式视频",
  "图片建议包含门店招牌、环境或特色产品，清晰美观无水印"
];

const canSubmit = computed(() => {
  const u = draftCover.value?.url?.trim();
  return Boolean(u && !uploading.value);
});

const fetchStoreDetail = async () => {
  try {
    const param = { id: localGet("geeker-user")?.userInfo?.storeId };
    if (!param.id) return;
    const res: any = await getDetail(param as any);
    if (res?.code === 200 && res.data?.storeName) {
      storeNameDisplay.value = String(res.data.storeName);
    }
  } catch {
    /* ignore */
  }
};

function pickRowId(raw: unknown): string | number | undefined {
  if (raw === null || raw === undefined || raw === "") return undefined;
  if (typeof raw === "number") return Number.isFinite(raw) ? raw : undefined;
  if (typeof raw === "string") return raw;
  return undefined;
}

function rowToDraft(it: StoreImgRow): DraftCover | null {
  let u = normalizeCoverMediaUrl(it.imgUrl);
  if (!u) return null;
  const isVideo = !!(it.isVideo === true || it.isVideo === 1) || /\.(mp4|mov|m4v|webm|3gp)(\?|#|$)/i.test(u);

  let poster = "";
  const thumbRaw = it.thumbnail_url ?? it.thumbnailUrl ?? it.cover ?? it.coverUrl;
  poster = typeof thumbRaw === "string" ? thumbRaw.trim() : "";
  if (isVideo && !poster && u) {
    poster = tryOssVideoSnapshotCoverUrl(u);
  }

  const id = pickRowId(it.id);

  return {
    id,
    url: u,
    isVideo,
    ...(poster ? { posterUrl: poster } : {})
  };
}

const fetchCoverDraft = async () => {
  const userInfo: any = localGet("geeker-user")?.userInfo || {};
  const storeId = userInfo.storeId;
  if (!storeId) {
    draftCover.value = null;
    return;
  }
  loading.value = true;
  try {
    const res: any = await getStoreHeadImg(storeId, IMG_TYPE_COVER);
    if (res && (res.code === 200 || res.code === "200") && res.data) {
      const dataAny = res.data as Record<string, unknown>;
      const imgList = Array.isArray(dataAny) ? dataAny : (dataAny.storeImgList as StoreImgRow[]) || [];
      const sorted = [...imgList].sort(
        (a: unknown, b: unknown) => (Number((a as StoreImgRow)?.imgSort) || 0) - (Number((b as StoreImgRow)?.imgSort) || 0)
      );
      const firstRow = sorted[0];
      draftCover.value = firstRow ? rowToDraft(firstRow as StoreImgRow) : null;
    } else {
      draftCover.value = null;
    }
  } catch {
    draftCover.value = null;
  } finally {
    loading.value = false;
  }
};

function openFilePicker() {
  if (uploading.value || saving.value || draftCover.value) return;
  fileInputRef.value?.click();
}

function onUploadSlotClick() {
  openFilePicker();
}

function onUploadSlotKeydown() {
  openFilePicker();
}

function openCoverPreview() {
  const d = draftCover.value;
  if (!d) return;
  if (d.isVideo) {
    previewTarget.value = { ...d };
    coverVideoPreviewVisible.value = true;
  } else {
    coverImagePreviewUrls.value = [d.url];
    coverImagePreviewInitialIndex.value = 0;
    coverImagePreviewVisible.value = true;
  }
}

function onCoverVideoPreviewClosed() {
  coverVideoPreviewVisible.value = false;
  previewTarget.value = null;
}

async function ingestPickedFile(file: File): Promise<boolean> {
  const name = String(file.name || "").toLowerCase();
  const type = String(file.type || "");

  let isVideo = /^video\//i.test(type) || /\.(mp4|mov|m4v|webm|3gp)$/i.test(name);

  const imgOk = type === "image/jpeg" || type === "image/png" || type === "image/webp" || /\.(jpe?g|png|webp)$/i.test(name);

  if (!imgOk && !isVideo) {
    ElMessage.warning("仅支持 JPG、PNG、WebP、MP4、MOV");
    return false;
  }

  const maxMb = isVideo ? VIDEO_MAX_MB : IMAGE_MAX_MB;
  const sizeMb = file.size / (1024 * 1024);
  if (sizeMb > maxMb) {
    ElMessage.warning(`文件不能超过 ${maxMb}MB`);
    return false;
  }

  uploading.value = true;
  try {
    const urls = await uploadFilesToOss([file], isVideo ? "video" : "image", {
      /** 保留全局 PopupLoading 进度；上传完成不 toast，由本页统一提示（上传服务已含合规校验） */
      uploadSuccessMessage: null
    });
    const url = urls[0];
    if (!url) throw new Error("上传成功但未返回地址");

    let posterUrl = "";
    if (isVideo) {
      posterUrl = tryOssVideoSnapshotCoverUrl(url);
    }

    draftCover.value = {
      id: undefined,
      url,
      isVideo,
      ...(posterUrl ? { posterUrl } : {})
    };
    ElMessage.success("上传成功");
    return true;
  } catch (e: unknown) {
    if (isUploadUserCancelledError(e)) {
      return false;
    }
    if (isUploadApiErrorAlreadyMessaged(e)) {
      return false;
    }
    const msg =
      typeof e === "object" && e && "message" in e && typeof (e as { message?: unknown }).message === "string"
        ? String((e as Error).message)
        : "上传失败";
    ElMessage.error(msg);
    return false;
  } finally {
    uploading.value = false;
  }
}

async function onFileInputChange(ev: Event): Promise<void> {
  const input = ev.target as HTMLInputElement;
  const file = input.files?.[0];
  input.value = "";
  if (!file) return;
  await ingestPickedFile(file);
}

function clearDraft() {
  draftCover.value = null;
}

async function onSave() {
  if (saving.value) return;
  if (!canSubmit.value) {
    ElMessage.warning("请先上传一张图片或一段视频（仅 1 条）");
    return;
  }
  const sidRaw = localGet("geeker-user")?.userInfo?.storeId;
  const numericStoreId = sidRaw != null ? Number(sidRaw) : NaN;
  if (!Number.isFinite(numericStoreId) || numericStoreId <= 0) {
    ElMessage.error("门店信息缺失，无法保存");
    return;
  }

  saving.value = true;
  try {
    const dc = draftCover.value!;
    const storeImgList = [
      {
        id: dc.id,
        imgUrl: dc.url,
        imgType: IMG_TYPE_COVER,
        imgSort: 1,
        storeId: numericStoreId,
        isExtract: 0
      }
    ];

    const res: any = await saveStoreHeadImg({
      storeImgList,
      storeId: numericStoreId,
      imgType: IMG_TYPE_COVER,
      imgMode: 0
    });

    if (res?.code !== 200 && res?.code !== "200") {
      ElMessage.error(String(res?.msg || res?.message || "保存失败"));
      return;
    }

    ElMessage.success(String(res?.msg || "保存成功"));
    await fetchCoverDraft();
  } catch {
    ElMessage.error("保存失败");
  } finally {
    saving.value = false;
  }
}

onMounted(async () => {
  await fetchStoreDetail();
  await fetchCoverDraft();
});
</script>

<style scoped lang="scss">
.store-cover-page {
  min-height: 100%;
  padding: 24px;
  background: #ffffff;
}
.page-inner {
  max-width: 960px;
  margin: 0;
}
.preview-section {
  margin-bottom: 28px;
}
.block-heading {
  margin: 0 0 12px;
  font-size: 18px;
  font-weight: 700;
  color: #151515;
  text-align: left;
}
.preview-area {
  max-width: 360px;
}
.preview-card {
  padding: 12px;
  background: #eff6ff;
  border-radius: 12px;
  box-shadow: 0 2px 12px rgb(0 0 0 / 8%);
}
.preview-card--blue {
  border: 2px solid #60a5fa;
}
.preview-media {
  position: relative;
  overflow: hidden;
  background: #f0f2f5;
  border-radius: 8px 8px 0 0;
}
.preview-album-pill {
  position: absolute;
  right: 10px;
  bottom: 10px;
  z-index: 2;
  display: inline-flex;
  gap: 4px;
  align-items: center;
  padding: 6px 12px;
  font-size: 12px;
  line-height: 1;
  color: #ffffff;
  pointer-events: none;
  background-color: rgb(0 0 0 / 60%);
  border-radius: 20px;
  .el-icon {
    font-size: 12px;
  }
}
.preview-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.preview-img--single {
  display: block;
  width: 100%;
  max-height: 198px;
  object-fit: cover;
}
.preview-meta {
  display: flex;
  gap: 8px;
  align-items: center;
  justify-content: space-between;
  padding: 6px 10px 10px;
  background: #ffffff;
  border-radius: 0 0 8px 8px;
  .store-title {
    margin-bottom: 6px;
    font-size: 15px;
    font-weight: 600;
    color: #151515;
  }
  .preview-rate {
    display: inline-flex;
    align-items: center;
    height: auto;
    line-height: 1;
    :deep(.el-rate__icon) {
      margin-right: 0;
      font-size: 18px;
    }
    :deep(.el-rate__item:not(:last-of-type) .el-rate__icon) {
      margin-right: 3px;
    }
    :deep(.el-rate__text) {
      padding: 0;
      margin-left: 4px;
      font-size: 14px;
    }
  }
  .reviews-right {
    display: inline-flex;
    flex-shrink: 0;
    gap: 2px;
    align-items: center;
    font-size: 14px;
    color: #666666;
    white-space: nowrap;
    .reviews-arrow {
      font-size: 14px;
      color: #909399;
    }
  }
}
.upload-section {
  margin-bottom: 32px;
}
.upload-layout {
  display: flex;
  flex-wrap: wrap;
  gap: 32px 40px;
  align-items: flex-start;
}
.upload-slot {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  justify-content: center;
  width: 310px;
  min-height: 200px;
  padding: 20px;
  cursor: pointer;
  border: 2px dashed #cfd4dc;
  border-radius: 12px;
  transition:
    border-color 0.2s,
    background 0.2s;
  &:hover:not(.uploading, .filled) {
    background: rgb(246 248 250);
    border-color: #909399;
  }
  &.uploading {
    pointer-events: none;
    cursor: wait;
    opacity: 0.75;
  }
  &.filled {
    padding: 0;
    cursor: default;
    background: #000000;
    border-color: #e4e7ed;
    border-style: solid;
    border-radius: 10px;
  }
  &__plus {
    font-size: 30px;
    font-weight: 600;
    color: #909399;
  }
  &__primary {
    font-size: 15px;
    color: #909399;
  }
  &__hint {
    font-size: 12px;
    line-height: 1.55;
    color: #909399;
    text-align: center;
  }
  &__preview {
    position: relative;
    width: 100%;
    min-height: 180px;
    max-height: 280px;
    overflow: hidden;
    border-radius: 10px;
  }
  &__preview--video {
    background: #000000;
  }
  &__thumb {
    display: block;
    width: 100%;
    max-height: 280px;
    object-fit: contain;
  }
  &__thumb-v {
    display: block;
    width: 100%;
    max-height: 280px;
    object-fit: contain;
  }
  &__hover-overlay {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    gap: 18px;
    align-items: center;
    justify-content: center;
    pointer-events: none;
    background: rgb(80 80 80 / 92%);
    border-radius: 10px;
    opacity: 0;
    transition: opacity 0.2s ease;
  }
  &__preview:hover &__hover-overlay {
    pointer-events: auto;
    opacity: 1;
  }
  &__action {
    display: inline-flex;
    gap: 8px;
    align-items: center;
    padding: 0;
    font-size: 15px;
    font-weight: 500;
    line-height: 1;
    color: #ffffff;
    cursor: pointer;
    background: none;
    border: none;
    border-radius: 0;
    .el-icon {
      font-size: 18px;
    }
    &:hover {
      opacity: 0.9;
    }
  }
}
.upload-input-hidden {
  position: absolute;
  width: 0;
  height: 0;
  pointer-events: none;
  opacity: 0;
}
.instruction-list {
  flex: 1;
  min-width: 260px;
  padding: 0;
  margin: 0;
  list-style: none;
  &__item {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    margin-bottom: 14px;
    font-size: 13px;
    line-height: 1.65;
    color: #575c66;
    &:last-child {
      margin-bottom: 0;
    }
  }
  &__icon {
    flex-shrink: 0;
    margin-top: 2px;
    font-size: 16px;
    line-height: 1;
    color: #409eff;
    .el-icon {
      display: block;
    }
  }
}
.save-footer {
  display: flex;
  justify-content: center;
  margin-top: 8px;
}
.btn-save {
  width: 100%;
  max-width: 180px;
  height: 44px;
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  border-radius: 10px;
}
:deep(.btn-save.el-button--primary:not(.is-disabled)) {
  --el-button-bg-color: #3b82f6;
  --el-button-border-color: #3b82f6;
  --el-button-hover-bg-color: #2563eb;
  --el-button-hover-border-color: #2563eb;
  --el-button-active-bg-color: #1d4ed8;
  --el-button-active-border-color: #1d4ed8;
}

/* 禁用：无素材 / 上传中 → 置灰不可点；保存 loading 时仍为蓝色 */
:deep(.btn-save.el-button--primary.is-disabled:not(.is-loading)),
:deep(.btn-save.el-button--primary.is-disabled:not(.is-loading):hover) {
  --el-button-bg-color: #ebedf0 !important;
  --el-button-border-color: #ebedf0 !important;
  --el-button-hover-bg-color: #ebedf0 !important;
  --el-button-hover-border-color: #ebedf0 !important;
  --el-button-disabled-bg-color: #ebedf0 !important;
  --el-button-disabled-border-color: #ebedf0 !important;

  color: #b1b8c4 !important;
  cursor: not-allowed !important;
  opacity: 1 !important;
}

@media screen and (width <= 640px) {
  .upload-layout {
    flex-direction: column;
  }
  .upload-slot {
    width: 100%;
    max-width: 360px;
  }
}
</style>
