<!-- 创作项目列表页，负责短剧与重绘项目的查询、新建和删除。 -->
<script setup lang="ts">
import { computed, onMounted, ref, watch } from "vue";
import { Plus } from "@element-plus/icons-vue";
import { ElMessageBox } from "element-plus";
import { CircleAlert, Film } from "@/icons";
import { useRoute, useRouter } from "vue-router";
import CardActionMenu from "@/components/CardActionMenu.vue";
import ProjectSearchField from "@/components/ProjectSearchField.vue";
import CreativeShell from "@/components/creative/CreativeShell.vue";
import LoadingMedia from "@/components/creative/LoadingMedia.vue";
import ProjectDialog from "@/components/creative/ProjectDialog.vue";
import RedrawProjectDialog from "@/components/creative/RedrawProjectDialog.vue";
import {
  creativeApi,
  type CreativeOptions,
  type ProjectDetail,
  type ProjectPayload,
  type ProjectSummary,
} from "@/api/creative";
import { apiError } from "@/api/http";
import { useToastStore } from "@/stores/toast";
import { formatDateTime } from "@/utils/datetime";
import { firstVideoFrameURL } from "@/utils/media";

const route = useRoute(),
  router = useRouter(),
  toast = useToastStore();
const isDrama = computed(() => route.meta.projectType === "premium_drama");
const pageLabel = computed(() => (isDrama.value ? "短剧创作" : "剧本反推"));
const itemLabel = computed(() => "项目");
const projects = ref<ProjectSummary[]>([]),
  options = ref<CreativeOptions>({ styles: [], models: [] });
const loading = ref(false),
  saving = ref(false),
  dialogOpen = ref(false);
const openingProjectId = ref("");
const editingProject = ref<ProjectDetail | null>(null);
const keyword = ref("");
const filteredProjects = computed(() => {
  const value = keyword.value.trim().toLocaleLowerCase();
  if (!value) return projects.value;
  return projects.value.filter((project) => project.name.toLocaleLowerCase().includes(value));
});
const eraNames: Record<string, string> = {
  modern_city: "现代都市",
  ancient_history: "古代历史",
  ancient_fantasy: "古代玄幻",
  ancient_xianxia: "古代仙侠",
  future_scifi: "未来科幻",
  other: "其他",
};
/** 获取项目年代展示文案，并兼容用户自定义年代。 */
function eraLabel(project: ProjectSummary) {
  return project.era_type === "other" && project.custom_era?.trim()
    ? project.custom_era.trim()
    : eraNames[project.era_type] || project.era_type;
}
/** 按当前项目类型加载项目列表。 */
async function load() {
  loading.value = true;
  try {
    projects.value = isDrama.value ? await creativeApi.dramaProjects() : await creativeApi.projects();
  } catch (e) {
    showError(e, "加载失败");
  } finally {
    loading.value = false;
  }
}
/** 打开新建项目对话框。 */
function openCreate() {
  editingProject.value = null;
  dialogOpen.value = true;
}
/** 加载项目详情并打开编辑对话框。 */
async function openEdit(project: ProjectSummary) {
  try {
    editingProject.value = await creativeApi.project(project.id);
    dialogOpen.value = true;
  } catch (e) {
    showError(e, `加载${itemLabel.value}失败`);
  }
}
/** 校验并保存项目，根据编辑状态调用新建或更新接口。 */
async function saveDialog(payload: ProjectPayload) {
  if (!payload.name || (isDrama.value && !payload.style_id)) {
    toast.warning("请填写项目名称并选择视觉风格");
    return;
  }
  saving.value = true;
  try {
    if (editingProject.value) {
      await creativeApi.updateProject(editingProject.value.id, {
        ...payload,
        project_type: isDrama.value ? "premium_drama" : "video_redraw",
        localization: isDrama.value ? null : payload.localization,
      });
      dialogOpen.value = false;
      editingProject.value = null;
      toast.success("项目已保存");
      await load();
      return;
    }
    const project = await creativeApi.createProject({
      ...payload,
      project_type: isDrama.value ? "premium_drama" : "video_redraw",
      localization: isDrama.value ? null : payload.localization,
    });
    dialogOpen.value = false;
    toast.success("项目已创建");
    await openProject(project.id);
  } catch (e) {
    showError(e, editingProject.value ? "保存失败" : "创建失败");
  } finally {
    saving.value = false;
  }
}
/** 将空的可选项目字段转换为统一占位文案。 */
function formatOptionalText(value?: string) {
  return value?.trim() || "未填写";
}
/** 根据项目类型进入对应详情页，并防止重复跳转。 */
async function openProject(projectId: string) {
  if (openingProjectId.value) return;
  if (isDrama.value) {
    await router.push({
      path: `/drama-projects/${projectId}`,
      query: { from: "drama-projects" },
    });
    return;
  }
  openingProjectId.value = projectId;
  try {
    await router.push({
      path: `/redraw-projects/${projectId}`,
      query: { from: "redraw-projects" },
    });
  } catch (e) {
    showError(e, "进入项目失败");
  } finally {
    openingProjectId.value = "";
  }
}
/** 二次确认后删除项目并刷新列表。 */
async function removeProject(project: ProjectSummary) {
  try {
    await ElMessageBox.confirm(`确定删除${itemLabel.value}“${project.name}”吗？`, `删除${itemLabel.value}`, {
      type: "warning",
      confirmButtonText: "删除",
      cancelButtonText: "取消",
    });
    await creativeApi.deleteProject(project.id);
    toast.success(`${itemLabel.value}已删除`);
    await load();
  } catch (e) {
    if (e !== "cancel" && e !== "close") showError(e, "删除失败");
  }
}
/** 将接口异常转换为统一 Toast 错误信息。 */
function showError(error: unknown, title: string) {
  const detail = apiError(error);
  toast.error(detail.message, detail.copyText, title);
}
onMounted(async () => {
  try {
    options.value = await creativeApi.options();
  } catch (e) {
    showError(e, "加载失败");
  }
  await load();
});
watch(isDrama, () => {
  projects.value = [];
  keyword.value = "";
  void load();
});
</script>

<template>
  <CreativeShell :page-title="pageLabel">
    <section class="project-list-toolbar">
      <ProjectSearchField
        v-model="keyword"
        :placeholder="`搜索${pageLabel}${itemLabel}`"
      />
      <div class="project-list-actions creative-page-actions">
        <span>{{ filteredProjects.length }} 个{{ itemLabel }}</span>
        <el-button
          type="primary"
          :icon="Plus"
          @click="openCreate"
          >创建{{ itemLabel }}</el-button
        >
      </div>
    </section>
    <section
      v-loading="loading"
      class="project-grid"
    >
      <article
        v-for="project in filteredProjects"
        :key="project.id"
        class="project-card"
        :class="{ 'redraw-project-card': !isDrama }"
        @click="openProject(project.id)"
      >
        <span
          class="project-cover"
          :class="project.aspect_ratio === '9:16' ? 'portrait' : 'landscape'"
        >
          <LoadingMedia
            v-if="
              (isDrama && project.cover_video_url) ||
              (!isDrama && project.cover_url && project.cover_mime_type?.startsWith('video/'))
            "
            type="video"
            :src="firstVideoFrameURL(isDrama ? project.cover_video_url! : project.cover_url!)"
            muted
            playsinline
            preload="metadata"
            disablepictureinpicture
            disableremoteplayback
            controlslist="nodownload noplaybackrate noremoteplayback"
          />
          <LoadingMedia
            v-else-if="project.cover_url"
            type="image"
            :src="project.cover_url"
            :alt="`${project.name}封面`"
          />
          <span
            v-else
            class="project-placeholder"
          >
            <Film
              :size="30"
              stroke-width="1.25"
            />
            <small>暂无封面</small>
          </span>
          <span
            v-if="!isDrama"
            class="redraw-type-badge"
          >
            {{ project.short_drama_type || "未填写" }}
          </span>
          <span
            v-if="openingProjectId === project.id"
            class="processing-badge"
            >正在进入</span
          >
          <span
            v-else-if="Number(project.processing_count)"
            class="processing-badge"
            >处理中 {{ project.processing_count }}</span
          >
        </span>
        <span
          v-if="isDrama"
          class="project-copy"
          ><strong>{{ project.name }}</strong
          ><small class="project-tags"
            ><span>{{ project.style_name }}</span
            ><span>{{ eraLabel(project) }}</span
            ><span>{{ project.aspect_ratio }}</span></small
          ></span
        >
        <span
          v-else
          class="project-copy redraw-project-copy"
        >
          <el-tooltip
            placement="right-start"
            effect="light"
            popper-class="redraw-project-tooltip"
          >
            <template #content>
              <div class="project-tooltip-content">
                <header class="project-tooltip-header">
                  <CircleAlert :size="16" />
                  <strong>项目信息</strong>
                </header>
                <dl>
                  <div>
                    <dt>短剧名</dt>
                    <dd>{{ project.name }}</dd>
                  </div>
                  <div>
                    <dt>短剧类型</dt>
                    <dd>{{ project.short_drama_type || "未填写" }}</dd>
                  </div>
                  <div>
                    <dt>播放量</dt>
                    <dd>{{ formatOptionalText(project.play_count) }}</dd>
                  </div>
                  <div>
                    <dt>受众群体画像</dt>
                    <dd>{{ project.audience_profile || "未填写" }}</dd>
                  </div>
                  <div>
                    <dt>出品方</dt>
                    <dd>{{ project.producer || "未填写" }}</dd>
                  </div>
                  <div>
                    <dt>主演</dt>
                    <dd
                      v-if="project.cast_members?.length"
                      class="cast-tooltip-list"
                    >
                      <span
                        v-for="(member, index) in project.cast_members"
                        :key="`${member.name}-${index}`"
                      >
                        {{ member.name }}（{{ formatOptionalText(member.follower_count) }} 粉丝）
                      </span>
                    </dd>
                    <dd v-else>未填写</dd>
                  </div>
                </dl>
              </div>
            </template>
            <span
              class="project-info-trigger"
              @click.stop
            >
              <CircleAlert :size="18" />
            </span>
          </el-tooltip>
          <strong>{{ project.name }}</strong>
        </span>
        <span class="project-meta">
          <span>{{ Number(project.episode_count) }} 集</span>
          <time>{{ formatDateTime(project.updated_at) }}</time>
        </span>
        <CardActionMenu
          :label="`${project.name} 更多操作`"
          @edit="openEdit(project)"
          @delete="removeProject(project)"
        />
      </article>
      <div
        v-if="!loading && !filteredProjects.length"
        class="project-state"
      >
        <Film
          :size="38"
          stroke-width="1.2"
        />
        <strong>{{ keyword.trim() ? `没有匹配的${itemLabel}` : `还没有${pageLabel}${itemLabel}` }}</strong>
        <p>{{ keyword.trim() ? "换一个关键词继续查找。" : `点击右上角按钮创建第一个${itemLabel}。` }}</p>
        <el-button
          v-if="!keyword.trim()"
          type="primary"
          :icon="Plus"
          @click="openCreate"
        >
          创建{{ itemLabel }}
        </el-button>
      </div>
    </section>
    <ProjectDialog
      v-if="isDrama"
      v-model="dialogOpen"
      :options="options"
      :project="editingProject"
      :saving="saving"
      :project-type="isDrama ? 'premium_drama' : 'video_redraw'"
      @save="saveDialog"
    />
    <RedrawProjectDialog
      v-else
      v-model="dialogOpen"
      :project="editingProject"
      :saving="saving"
      @save="saveDialog"
    />
  </CreativeShell>
</template>

<style scoped lang="scss">
.project-list-toolbar {
  min-height: var(--control-height-lg);
  margin-bottom: var(--space-xl);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-xl);
}
.project-list-actions {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
}
.project-list-actions > span {
  color: var(--web-project-count);
  font-size: 12px;
  white-space: nowrap;
}
.project-grid {
  min-height: 240px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  align-items: stretch;
  gap: var(--space-lg);
}

.project-card {
  min-width: 0;
  position: relative;
  padding: 0;
  text-align: left;
  color: var(--web-text);
  background: var(--web-surface);
  border: 1px solid var(--web-line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  cursor: pointer;
  transition:
    background-color 0.18s,
    border-color 0.18s,
    color 0.18s;
}

.redraw-project-card {
  align-self: start;
  width: 100%;
}

.project-card:hover,
.project-card:focus-within {
  color: var(--web-text);
  background: var(--web-surface);
  border-color: var(--web-border-strong);
}

.project-cover {
  position: relative;
  aspect-ratio: 16 / 9;
  display: grid;
  place-items: center;
  overflow: hidden;
  background: var(--web-surface-raised);
  border-bottom: 1px solid var(--web-line);
}

.project-cover.landscape {
  background: var(--web-surface);
}

.project-cover video,
.project-cover img,
.project-cover :deep(.loading-media-element) {
  height: 100%;
  width: auto;
  max-width: 100%;
  object-fit: contain;
  pointer-events: none;
}

.project-placeholder {
  display: flex;
  align-items: center;
  flex-direction: column;
  gap: var(--space-sm);
  color: var(--web-project-placeholder);
}

.project-cover small {
  font-family: var(--font-ui);
  font-size: 12px;
  letter-spacing: 0;
}

.processing-badge {
  position: absolute;
  top: 12px;
  right: 54px;
  padding: 5px 8px;
  color: var(--web-accent-dark);
  background: var(--web-surface);
  border: 1px solid var(--web-line-emphasis);
  border-radius: var(--radius-sm);
  font-size: 11px;
  font-style: normal;
}

.redraw-type-badge {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 2;
  max-width: calc(100% - 72px);
  padding: 5px 9px;
  overflow: hidden;
  color: var(--web-project-badge-text);
  background: var(--web-surface);
  border: 1px solid var(--web-line-emphasis);
  border-radius: var(--radius-sm);
  box-shadow: var(--web-shadow-project-badge);
  font-size: 11px;
  line-height: 1;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.project-copy {
  padding: var(--space-lg) var(--space-lg) var(--space-sm);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.project-copy strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 16px;
  font-weight: 600;
}

.redraw-project-copy {
  min-width: 0;
  padding: var(--space-md) var(--space-lg) var(--space-sm);
  align-items: center;
  flex-direction: row;
  gap: var(--space-sm);
}

.redraw-project-copy strong {
  min-width: 0;
  flex: 1;
}

.project-info-trigger {
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  flex: 0 0 28px;
  color: var(--web-project-tooltip-accent);
  background: transparent;
  border: 0;
  cursor: help;
}

.project-tooltip-content {
  width: min(320px, calc(100vw - 52px));
  height: min(320px, calc(100vh - 48px));
  padding: 14px 16px 10px;
  box-sizing: border-box;
  color: var(--web-text);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.project-tooltip-header {
  padding-bottom: 10px;
  display: flex;
  align-items: center;
  gap: 7px;
  color: var(--web-project-tooltip-accent);
  border-bottom: 1px solid var(--web-project-tooltip-line);
}

.project-tooltip-header strong {
  color: var(--web-text);
  font-size: 14px;
  font-weight: 650;
}

.project-tooltip-content dl {
  margin: 0;
  display: grid;
}

.project-tooltip-content dl > div {
  padding: 9px 0;
  display: grid;
  grid-template-columns: 88px minmax(0, 1fr);
  gap: 10px;
  border-bottom: 1px solid var(--web-project-tooltip-line-soft);
}

.project-tooltip-content dl > div:last-child {
  border-bottom: 0;
}

.project-tooltip-content dt {
  color: var(--web-project-tooltip-label);
  font-size: 12px;
}

.project-tooltip-content dd {
  margin: 0;
  color: var(--web-text);
  font-size: 12px;
  line-height: 1.55;
  overflow-wrap: anywhere;
}

.cast-tooltip-list {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

:global(.redraw-project-tooltip.el-popper) {
  padding: 0;
  background: var(--web-surface);
  border-color: var(--web-line-strong);
  border-radius: 10px;
  box-shadow: var(--web-shadow-project-tooltip);
  overflow: hidden;
}

:global(.redraw-project-tooltip.el-popper .el-popper__arrow::before) {
  background: var(--web-surface);
  border-color: var(--web-line-strong);
}

.project-copy small {
  color: var(--web-project-copy-muted);
  font-size: 12px;
}

.project-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 8px;
  line-height: 1.45;
}

.project-tags span:not(:last-child)::after {
  content: "·";
  margin-left: 8px;
  color: var(--web-project-divider-text);
}

.project-meta {
  padding: 0 var(--space-lg) var(--space-lg);
  display: flex;
  justify-content: space-between;
  color: var(--web-project-meta);
  font-size: 12px;
}

.redraw-project-copy + .project-meta {
  padding: 0 var(--space-lg) var(--space-md);
}

.project-state {
  min-height: 320px;
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: var(--space-sm);
  color: var(--web-text-tertiary);
  text-align: center;
}
.project-state strong {
  color: var(--web-text-secondary);
  font-size: 16px;
}
.project-state p {
  margin: 0;
  color: var(--web-project-state-text);
  font-size: 12px;
}
.project-state :deep(.el-button) {
  margin-top: var(--space-sm);
}

.field-error {
  margin-top: 5px;
  color: var(--web-accent-dark);
  font-size: 12px;
  line-height: 1.2;
}

.field-invalid:deep(.el-input__wrapper) {
  border-color: var(--web-invalid-line);
  box-shadow: 0 0 0 1px var(--web-invalid-outline);
}

@media (max-width: 820px) {
  .project-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 680px) {
  .project-list-toolbar {
    align-items: stretch;
    flex-direction: column;
    gap: var(--space-md);
  }
  .project-list-toolbar .project-search-field {
    width: 100%;
  }
  .project-list-actions {
    justify-content: space-between;
  }
}

@media (max-width: 520px) {
  .project-grid {
    grid-template-columns: 1fr;
  }
}
</style>
