<script setup lang="ts">
import { computed, onMounted, reactive, ref } from "vue";
import { ElMessageBox } from "element-plus";
import http, { apiError } from "@/api/http";
import AppIcon from "@/components/AppIcon.vue";
import MasterDetailLayout from "@/components/MasterDetailLayout.vue";
import { useToastStore } from "@/stores/toast";

type Prompt = Record<string, any>;
interface PromptBackupItem {
  title: string;
  type: string;
  content: string;
}
const promptTypes = [
  "剧本反推",
  "剧本分析",
  "角色生成",
  "场景生成",
  "道具生成",
  "分镜图生成",
  "首尾帧生成",
  "视频生成",
];
const toast = useToastStore();
const loading = ref(false);
const saving = ref(false);
const exporting = ref(false);
const restoring = ref(false);
const restoreInput = ref<HTMLInputElement>();
const items = ref<Prompt[]>([]);
const selectedId = ref("");
const creating = ref(false);
const query = reactive({ keyword: "", page: 1, page_size: 100 });
const form = reactive({ name: "", type: "", content: "" });
const selected = computed(() => items.value.find((item) => item.id === selectedId.value));

function reset(row?: Prompt) {
  Object.assign(form, {
    name: row?.name || "",
    type: row?.type || row?.category || "",
    content: row?.content || row?.version_content || "",
  });
  selectedId.value = row?.id || "";
  creating.value = !row;
}
async function load() {
  loading.value = true;
  try {
    const { data } = await http.get("/prompts", { params: query });
    items.value = data.data?.items || [];
    if (selectedId.value) {
      const current = items.value.find((item) => item.id === selectedId.value);
      if (current) reset(current);
      else reset();
    } else if (items.value.length) reset(items.value[0]);
  } catch (error) {
    showError(error);
  } finally {
    loading.value = false;
  }
}
function create() {
  reset();
}
function validate() {
  if (!form.name.trim()) return "请输入提示词名称";
  if (!form.type) return "请选择适用类型";
  if (!form.content.trim()) return "请输入提示词内容";
  return "";
}
async function save() {
  const message = validate();
  if (message) {
    toast.warning(message);
    return;
  }
  saving.value = true;
  try {
    const payload = { name: form.name.trim(), type: form.type, content: form.content };
    if (selectedId.value) await http.put(`/prompts/${selectedId.value}`, payload);
    else {
      const { data } = await http.post("/prompts", payload);
      selectedId.value = data.data.id;
    }
    creating.value = false;
    toast.success("提示词已保存");
    await load();
  } catch (error) {
    showError(error);
  } finally {
    saving.value = false;
  }
}
async function remove() {
  if (!selected.value) return;
  try {
    await ElMessageBox.confirm(
      `确定要删除提示词“${selected.value.name || "未命名提示词"}”吗？此操作不可恢复。`,
      "确认删除",
      { type: "warning" },
    );
    await http.delete(`/prompts/${selectedId.value}`);
    toast.success("提示词已删除");
    selectedId.value = "";
    reset();
    await load();
  } catch (error) {
    if (error !== "cancel" && error !== "close") showError(error);
  }
}
async function fetchAllPrompts() {
  const result: Prompt[] = [];
  let page = 1;
  while (true) {
    const { data } = await http.get("/prompts", { params: { page, page_size: 100 } });
    const current = data.data?.items || [];
    result.push(...current);
    if (result.length >= Number(data.data?.total || 0) || !current.length) return result;
    page += 1;
  }
}
function backupFilename() {
  const value = new Date()
    .toLocaleString("sv-SE", { timeZone: "Asia/Shanghai", hour12: false })
    .replace(/[-:]/g, "")
    .replace(" ", "-");
  return `prompts-backup-${value}.json`;
}
async function exportPrompts() {
  exporting.value = true;
  try {
    const prompts = await fetchAllPrompts();
    const backup = {
      format: "juhe-factory-prompts",
      version: 1,
      exported_at: new Date().toISOString(),
      prompts: prompts.map((item) => ({
        title: String(item.name || ""),
        type: String(item.type || ""),
        content: String(item.content || ""),
      })),
    };
    const url = URL.createObjectURL(
      new Blob([JSON.stringify(backup, null, 2)], { type: "application/json;charset=utf-8" }),
    );
    const link = document.createElement("a");
    link.href = url;
    link.download = backupFilename();
    link.click();
    URL.revokeObjectURL(url);
    toast.success(`已导出 ${prompts.length} 条提示词`);
  } catch (error) {
    showError(error);
  } finally {
    exporting.value = false;
  }
}
function parseBackup(value: string): PromptBackupItem[] {
  const parsed = JSON.parse(value) as { prompts?: unknown } | unknown[];
  const source = Array.isArray(parsed) ? parsed : parsed?.prompts;
  if (!Array.isArray(source) || !source.length) throw new Error("JSON 中没有可恢复的提示词");
  const titles = new Set<string>();
  return source.map((raw, index) => {
    if (!raw || typeof raw !== "object") throw new Error(`第 ${index + 1} 条提示词格式无效`);
    const item = raw as Record<string, unknown>;
    const title = String(item.title || item.name || "").trim();
    const type = String(item.type || "").trim();
    const content = String(item.content || "").trim();
    if (!title || !type || !content) throw new Error(`第 ${index + 1} 条提示词缺少标题、类型或内容`);
    if (titles.has(title)) throw new Error(`JSON 中存在重复标题：${title}`);
    titles.add(title);
    return { title, type, content };
  });
}
function chooseRestore() {
  restoreInput.value?.click();
}
async function restorePrompts(event: Event) {
  const input = event.target as HTMLInputElement;
  const file = input.files?.[0];
  if (!file) return;
  restoring.value = true;
  try {
    const backup = parseBackup(await file.text());
    const existing = await fetchAllPrompts();
    const byTitle = new Map<string, Prompt>();
    for (const item of existing)
      if (!byTitle.has(String(item.name || "").trim())) byTitle.set(String(item.name || "").trim(), item);
    const overwriteCount = backup.filter((item) => byTitle.has(item.title)).length;
    await ElMessageBox.confirm(
      `文件包含 ${backup.length} 条提示词，将覆盖 ${overwriteCount} 条、新增 ${backup.length - overwriteCount} 条。是否继续恢复？`,
      "确认恢复提示词",
      { type: "warning", confirmButtonText: "确认恢复", cancelButtonText: "取消" },
    );
    for (const item of backup) {
      const payload = { name: item.title, type: item.type, content: item.content };
      const current = byTitle.get(item.title);
      if (current) await http.put(`/prompts/${current.id}`, payload);
      else await http.post("/prompts", payload);
    }
    toast.success(`恢复完成：覆盖 ${overwriteCount} 条，新增 ${backup.length - overwriteCount} 条`);
    await load();
  } catch (error) {
    if (error !== "cancel" && error !== "close") {
      if (error instanceof SyntaxError) toast.error("JSON 文件格式错误");
      else if (error instanceof Error && !("response" in error)) toast.error(error.message);
      else showError(error);
    }
  } finally {
    restoring.value = false;
    input.value = "";
  }
}
function showError(error: unknown) {
  const detail = apiError(error);
  toast.error(detail.message, detail.copyText);
}
onMounted(load);
</script>

<template>
  <MasterDetailLayout
    title="提示词管理"
    description="管理系统提示词，保存后立即生效"
    empty-text="请从左侧选择提示词，或点击新增提示词"
    :has-selection="Boolean(selectedId) || creating"
  >
    <template #header-actions>
      <div class="header-actions">
        <input
          ref="restoreInput"
          class="restore-input"
          type="file"
          accept="application/json,.json"
          @change="restorePrompts"
        />
        <el-button
          :loading="exporting"
          :disabled="restoring"
          @click="exportPrompts"
          ><AppIcon name="Upload" />导出</el-button
        >
        <el-button
          :loading="restoring"
          :disabled="exporting"
          @click="chooseRestore"
          ><AppIcon name="Download" />恢复</el-button
        >
      </div>
    </template>
    <template #list-toolbar
      ><el-input
        v-model="query.keyword"
        clearable
        placeholder="搜索提示词名称"
        ><template #prefix><AppIcon name="Search" /></template></el-input
    ></template>
    <template #list>
      <el-button
        class="create-prompt-button"
        type="primary"
        @click="create"
        ><AppIcon name="Plus" />新建提示词</el-button
      >
      <button
        v-for="item in items"
        :key="item.id"
        class="master-item"
        :class="{ active: item.id === selectedId }"
        type="button"
        @click="reset(item)"
      >
        <strong>{{ item.name || "未命名提示词" }}</strong
        ><span>{{ item.type || item.category || "未分类" }}</span>
      </button>
      <el-empty
        v-if="!loading && !items.length"
        description="暂无提示词"
        :image-size="70"
      />
    </template>
    <template #detail>
      <header class="editor-heading">
        <div>
          <span>{{ selectedId ? "编辑提示词" : "新增提示词" }}</span>
          <h3>{{ form.name || "未命名提示词" }}</h3>
        </div>
      </header>
      <el-form
        class="editor-form"
        :model="form"
        label-position="top"
      >
        <div class="form-grid">
          <el-form-item
            label="提示词名称"
            required
            ><el-input
              v-model.trim="form.name"
              placeholder="请输入提示词名称"
          /></el-form-item>
          <el-form-item
            label="适用类型"
            required
            ><el-select
              v-model="form.type"
              placeholder="请选择适用类型"
              class="type-select"
              filterable
              ><el-option
                v-for="type in promptTypes"
                :key="type"
                :label="type"
                :value="type" /></el-select
          ></el-form-item>
        </div>
        <el-form-item
          class="prompt-content-field"
          label="提示词内容"
          required
          ><el-input
            v-model="form.content"
            type="textarea"
            :rows="16"
            resize="none"
            placeholder="请输入提示词内容"
        /></el-form-item>
      </el-form>
      <footer class="editor-actions">
        <el-button
          v-if="selectedId"
          type="danger"
          plain
          @click="remove"
          ><AppIcon name="Delete" />删除</el-button
        ><el-button
          type="primary"
          :loading="saving"
          @click="save"
          >保存</el-button
        >
      </footer>
    </template>
  </MasterDetailLayout>
</template>

<style scoped>
.header-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 8px;
}
.header-actions :deep(.el-button) {
  margin-left: 0;
}
.restore-input {
  display: none;
}
.master-item {
  width: 100%;
  padding: 12px 13px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  border: 1px solid transparent;
  border-radius: 9px;
  background: transparent;
  color: var(--color-text);
  text-align: left;
  cursor: pointer;
  transition:
    background 0.16s,
    border-color 0.16s;
}
.master-item:hover {
  background: #eef4ff;
}
.master-item.active {
  border-color: #b8cff5;
  background: #eaf2ff;
}
.master-item strong {
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.master-item span {
  color: var(--color-text-secondary);
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.create-prompt-button {
  width: 100%;
  margin: 0 0 8px;
  justify-content: center;
}
.editor-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--color-border-light);
}
.editor-heading span {
  color: var(--color-text-secondary);
  font-size: 12px;
}
.editor-heading h3 {
  margin-top: 4px;
  font-size: 20px;
}
.editor-form {
  display: flex;
  flex: 1;
  min-height: 0;
  flex-direction: column;
  padding-top: 22px;
}
.form-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 16px;
}
.type-select {
  width: 100%;
}
.prompt-content-field {
  display: flex;
  flex: 1;
  min-height: 0;
  flex-direction: column;
}
.prompt-content-field :deep(.el-form-item__content) {
  flex: 1;
  min-height: 0;
}
.prompt-content-field :deep(.el-textarea) {
  height: 100%;
}
.prompt-content-field :deep(.el-textarea__inner) {
  height: 100%;
  min-height: 420px !important;
}
.editor-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding-top: 18px;
  border-top: 1px solid var(--color-border-light);
}
:deep(.master-editor) {
  display: flex;
  min-height: 0;
  flex-direction: column;
}
@media (max-width: 640px) {
  .header-actions {
    width: 100%;
    justify-content: flex-start;
  }
  .form-grid {
    grid-template-columns: 1fr;
  }
  .prompt-content-field :deep(.el-textarea__inner) {
    min-height: 360px !important;
  }
}
</style>
