<!-- 视觉风格管理页，负责风格图片、名称和排序维护。 -->
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from "vue";
import { ElMessageBox, type FormInstance, type UploadRequestOptions } from "element-plus";
import AppIcon from "@/components/AppIcon.vue";
import PageHeader from "@/components/PageHeader.vue";
import http, { apiError } from "@/api/http";
import { useToastStore } from "@/stores/toast";

type UploadError = Error & { status: number; method: string; url: string };
const toast = useToastStore();
const loading = ref(false);
const saving = ref(false);
const dialogOpen = ref(false);
const editingId = ref("");
const items = ref<Record<string, any>[]>([]);
const formRef = ref<FormInstance>();
const form = reactive<Record<string, any>>({ name: "", image: "", image_key: "" });
const draggedId = ref("");
const originalOrder = ref<string[]>([]);
const reordering = ref(false);
const canReorder = computed(() => items.value.length > 1 && !reordering.value);
const rules = { name: [{ required: true, message: "请填写风格名", trigger: "blur" }] };

// 加载风格列表，并保留接口返回的排序结果。
async function load() {
  loading.value = true;
  try {
    const { data } = await http.get("/resources/styles", { params: { page: 1, page_size: 100 } });
    items.value = data.data.items || [];
  } catch (error) {
    showError(error);
  } finally {
    loading.value = false;
  }
}
// 重置弹窗表单；编辑时回填当前风格及其图片信息。
function reset(row?: Record<string, any>) {
  editingId.value = row?.id || "";
  Object.assign(form, {
    name: row?.name || "",
    image: row?.image_url || "",
    image_key: row?.image_key || "",
    image_url: row?.image_url || "",
    image_mime: row?.image_mime || "",
    image_size: row?.image_size || "",
    image_width: row?.image_width || "",
    image_height: row?.image_height || "",
  });
}
// 打开新建风格弹窗。
function openCreate() {
  reset();
  dialogOpen.value = true;
}
// 打开编辑风格弹窗并回填目标风格。
function openEdit(row: Record<string, any>) {
  reset(row);
  dialogOpen.value = true;
}
// 上传风格图片，并将服务端返回的图片元数据写入表单。
async function upload(options: UploadRequestOptions) {
  const data = new FormData();
  data.append("file", options.file);
  try {
    const response = await http.post("/uploads/style-images", data, {
      headers: { "Content-Type": "multipart/form-data" },
    });
    Object.assign(form, response.data.data);
    form.image = response.data.data.image_url || response.data.data.image_key;
    options.onSuccess(response.data);
  } catch (error) {
    const uploadError = Object.assign(error instanceof Error ? error : new Error(String(error)), {
      status: 0,
      method: "POST",
      url: "/uploads/style-images",
    }) as UploadError;
    options.onError(uploadError);
    showError(error);
  }
}
// 校验并保存风格；图片上传成功后才允许提交。
async function save() {
  if (!(await formRef.value?.validate().catch(() => false))) return;
  if (!form.image_key) {
    toast.warning("请先上传风格图片");
    return;
  }
  saving.value = true;
  try {
    const payload = { ...form };
    delete payload.image;
    if (editingId.value) await http.put(`/resources/styles/${editingId.value}`, payload);
    else await http.post("/resources/styles", payload);
    dialogOpen.value = false;
    toast.success(editingId.value ? "风格已更新" : "风格已创建");
    await load();
  } catch (error) {
    showError(error);
  } finally {
    saving.value = false;
  }
}
// 删除指定风格，并在成功后刷新列表。
async function remove(row: Record<string, any>) {
  try {
    await ElMessageBox.confirm(`确定删除风格“${row.name}”吗？`, "删除风格", {
      type: "warning",
      confirmButtonText: "删除",
      cancelButtonText: "取消",
    });
    await http.delete(`/resources/styles/${row.id}`);
    toast.success("风格已删除");
    await load();
  } catch (error) {
    if (error !== "cancel" && error !== "close") showError(error);
  }
}
// 记录拖拽起点和原始顺序，用于提交排序或失败回滚。
function startDrag(row: Record<string, any>, event: DragEvent) {
  if (!canReorder.value) return;
  draggedId.value = row.id;
  originalOrder.value = items.value.map((item) => item.id);
  event.dataTransfer?.setData("text/plain", row.id);
}
// 根据当前悬停位置实时调整风格卡片顺序。
function moveOver(targetId: string) {
  const sourceIndex = items.value.findIndex((item) => item.id === draggedId.value);
  const targetIndex = items.value.findIndex((item) => item.id === targetId);
  if (sourceIndex < 0 || targetIndex < 0 || sourceIndex === targetIndex) return;
  const [moved] = items.value.splice(sourceIndex, 1);
  items.value.splice(targetIndex, 0, moved);
}
// 提交拖拽后的顺序；失败时恢复原始顺序。
async function finishDrag() {
  if (!draggedId.value) return;
  const nextOrder = items.value.map((item) => item.id);
  draggedId.value = "";
  if (nextOrder.every((id, index) => id === originalOrder.value[index])) return;
  reordering.value = true;
  try {
    await http.put("/resources/styles/reorder", { ids: nextOrder });
    toast.success("风格排序已保存");
  } catch (error) {
    const positions = new Map(originalOrder.value.map((id, index) => [id, index]));
    items.value.sort((a, b) => (positions.get(a.id) ?? 0) - (positions.get(b.id) ?? 0));
    showError(error);
  } finally {
    reordering.value = false;
  }
}
// 将接口异常转换为管理端统一提示。
function showError(error: unknown) {
  const detail = apiError(error);
  toast.error(detail.message, detail.copyText);
}

onMounted(load);
</script>

<template>
  <div class="page-stack management-page">
    <PageHeader
      title="风格管理"
      description="管理项目风格、展示图片与排序"
    >
      <el-button
        type="primary"
        @click="openCreate"
        ><AppIcon name="Plus" />新建风格</el-button
      >
    </PageHeader>
    <section
      v-loading="loading"
      class="panel style-grid-panel"
    >
      <div
        v-if="items.length"
        class="style-card-grid"
      >
        <article
          v-for="row in items"
          :key="row.id"
          class="style-card"
          :class="{ 'is-dragging': draggedId === row.id }"
          @dragover.prevent="moveOver(row.id)"
          @drop.prevent="finishDrag"
        >
          <div class="style-card__visual">
            <img
              :src="row.image_url"
              alt=""
              draggable="false"
            />
            <span
              class="style-card__drag"
              :draggable="canReorder"
              title="拖动排序"
              @dragstart="startDrag(row, $event)"
              @dragend="finishDrag"
            >
              <AppIcon name="Rank" />
            </span>
            <div class="style-card__actions">
              <el-button
                circle
                title="编辑风格"
                @click="openEdit(row)"
                ><AppIcon name="EditPen"
              /></el-button>
              <el-button
                circle
                type="danger"
                plain
                title="删除风格"
                @click="remove(row)"
                ><AppIcon name="Delete"
              /></el-button>
            </div>
          </div>
          <strong>{{ row.name }}</strong>
        </article>
      </div>
      <el-empty
        v-else
        description="暂无风格"
      />
    </section>

    <el-dialog
      v-model="dialogOpen"
      :title="editingId ? '编辑风格' : '新建风格'"
      class="style-dialog"
      width="min(720px, calc(100vw - 32px))"
      align-center
    >
      <el-form
        ref="formRef"
        :model="form"
        :rules="rules"
        label-position="top"
        class="style-form"
      >
        <div class="style-form__preview-column">
          <span class="style-form__label">图片预览</span>
          <div class="upload-preview-frame">
            <img
              v-if="form.image"
              class="upload-preview"
              :src="form.image"
              alt=""
            />
            <div
              v-else
              class="upload-preview-empty"
            >
              <AppIcon name="Picture" />
              <span>9:16 图片预览</span>
            </div>
          </div>
        </div>
        <div class="style-form__fields">
          <el-form-item
            label="风格名"
            prop="name"
            ><el-input v-model.trim="form.name"
          /></el-form-item>
          <el-form-item
            label="风格图片"
            required
          >
            <el-upload
              :show-file-list="false"
              :http-request="upload"
              accept="image/jpeg,image/png,image/gif"
            >
              <el-button><AppIcon name="Upload" />{{ form.image ? "重新上传" : "上传图片" }}</el-button>
            </el-upload>
            <p class="field-helper">推荐 9:16，支持 JPEG、PNG、GIF，最大 10MB</p>
          </el-form-item>
        </div>
      </el-form>
      <template #footer
        ><el-button @click="dialogOpen = false">取消</el-button
        ><el-button
          type="primary"
          :loading="saving"
          @click="save"
          >保存</el-button
        ></template
      >
    </el-dialog>
  </div>
</template>

<style scoped>
.style-grid-panel {
  flex: 1;
  min-height: 0;
  overflow: auto;
}
.style-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  gap: 16px;
}
.style-card {
  min-width: 0;
  overflow: hidden;
  background: #fff;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  transition:
    border-color 0.18s,
    transform 0.18s;
}
.style-card:hover {
  border-color: var(--color-primary);
  transform: translateY(-2px);
}
.style-card.is-dragging {
  opacity: 0.55;
}
.style-card__visual {
  height: 230px;
  position: relative;
  overflow: hidden;
  background: var(--color-bg);
}
.style-card__visual img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.style-card__drag {
  position: absolute;
  top: 10px;
  left: 10px;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  color: #fff;
  background: #111827;
  border-radius: 8px;
  cursor: grab;
}
.style-card__actions {
  position: absolute;
  top: 10px;
  right: 10px;
  display: flex;
  gap: 6px;
}
.style-card > strong {
  padding: 13px 14px;
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.style-form {
  display: grid;
  grid-template-columns: 180px minmax(0, 1fr);
  gap: var(--space-lg);
  align-items: start;
}
.style-form__preview-column,
.style-form__fields {
  min-width: 0;
}
.style-form__label {
  display: block;
  margin-bottom: var(--space-sm);
  color: var(--color-text);
  font-size: var(--font-size-sm);
  line-height: 22px;
}
.style-form__label::before {
  margin-right: var(--space-xs);
  color: var(--color-danger);
  content: "*";
}
.upload-preview-frame {
  width: 100%;
  aspect-ratio: 9 / 16;
  overflow: hidden;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-bg);
}
.upload-preview {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.upload-preview-empty {
  display: flex;
  height: 100%;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  color: var(--color-text-secondary);
  font-size: var(--font-size-sm);
}
.upload-preview-empty :deep(.el-icon) {
  font-size: 28px;
}
.style-form__fields :deep(.el-form-item:last-child) {
  margin-bottom: 0;
}
.field-helper {
  width: 100%;
  margin: var(--space-sm) 0 0;
  color: var(--color-text-secondary);
  font-size: var(--font-size-sm);
  line-height: 1.6;
}
@media (max-width: 600px) {
  .style-card-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .style-card__visual {
    height: 190px;
  }
  .style-form {
    grid-template-columns: 1fr;
    gap: var(--space-md);
  }
  .upload-preview-frame {
    width: min(180px, 100%);
    margin: 0 auto;
  }
}
</style>
