<!-- 分镜时间线组件，负责分镜选择、任务状态展示及前后插入和删除操作。 -->
<script setup lang="ts">
import { ref } from "vue";
import { CirclePlus, Delete, Loading } from "@element-plus/icons-vue";
import type { GenerationTask, Storyboard } from "@/api/creative";

const props = defineProps<{ storyboards: Storyboard[]; selectedId?: string; tasks: GenerationTask[] }>();
const emit = defineEmits<{
  select: [storyboard: Storyboard];
  insert: [storyboard: Storyboard, position: "before" | "after"];
  delete: [storyboard: Storyboard];
}>();
const track = ref<HTMLElement>();
const activeStatuses = new Set([
  "pending_submission",
  "submitting",
  "submitted",
  "processing",
  "result_ready",
  "downloading",
  "cancel_requested",
]);
/** 获取指定分镜和任务类型的最近一次生成任务。 */
function latestTask(item: Storyboard, taskType: string) {
  return props.tasks
    .filter(
      (task) =>
        task.task_type === taskType &&
        (task.storyboard_id === item.id ||
          (taskType === "prompt_reverse" && !task.storyboard_id && task.sequence_no === item.sequence_no)),
    )
    .sort((left, right) => Date.parse(right.created_at) - Date.parse(left.created_at))[0];
}
/** 按任务优先级计算分镜当前的展示状态。 */
function statusFor(item: Storyboard) {
  const reinfer = latestTask(item, "prompt_reverse");
  const latest = [reinfer]
    .filter((task): task is GenerationTask => Boolean(task))
    .sort((left, right) => Date.parse(right.created_at) - Date.parse(left.created_at))[0];
  if (latest && activeStatuses.has(latest.status)) {
    if (latest.status === "cancel_requested") return "停止中";
    return latest.storyboard_id ? "重新反推中" : "反推中";
  }
  if (latest?.status === "failed") return "反推失败";
  if (latest?.status === "cancelled") return "反推已取消";
  if (item.prompt_content || item.script_content) return "反推完成";
  if (item.status === "failed") return "反推失败";
  if (item.status === "generating") return "反推中";
  if (item.status === "queued") return "等待反推";
  return "";
}
/** 将分镜任务状态转换为样式类名。 */
function statusClass(item: Storyboard) {
  const status = statusFor(item);
  if (status.includes("失败")) return "failed";
  if (status.includes("中")) return "running";
  if (status.includes("完成")) return "completed";
  return "cancelled";
}
/** 判断分镜是否存在进行中的生成任务。 */
function isLoading(item: Storyboard) {
  const reinfer = latestTask(item, "prompt_reverse");
  return Boolean(reinfer && activeStatuses.has(reinfer.status));
}
/** 将垂直滚轮位移转换为时间线横向滚动。 */
function wheel(event: WheelEvent) {
  if (!track.value || Math.abs(event.deltaY) <= Math.abs(event.deltaX)) return;
  event.preventDefault();
  track.value.scrollLeft += event.deltaY;
}
</script>

<template>
  <section class="timeline-panel">
    <header>
      <div>
        <strong>分镜时间线</strong><span>{{ storyboards.length }} 个镜头</span>
      </div>
    </header>
    <div
      v-if="storyboards.length"
      ref="track"
      class="timeline-track"
      @wheel="wheel"
    >
      <div
        v-for="item in storyboards"
        :key="item.id"
        class="timeline-card-shell"
      >
        <button
          class="timeline-insert timeline-insert-before"
          type="button"
          title="在前面插入分镜"
          @click.stop="emit('insert', item, 'before')"
        >
          <el-icon>
            <CirclePlus />
          </el-icon>
        </button>
        <button
          class="timeline-insert timeline-insert-after"
          type="button"
          title="在后面插入分镜"
          @click.stop="emit('insert', item, 'after')"
        >
          <el-icon>
            <CirclePlus />
          </el-icon>
        </button>
        <article
          class="timeline-card"
          :class="{ active: item.id === selectedId }"
          @click="emit('select', item)"
        >
          <div class="timeline-header">
            <strong :class="{ completed: item.prompt_content || item.script_content }"
              >分镜 {{ item.sequence_no }}</strong
            >
          </div>
          <div
            class="timeline-status-center"
            :class="statusClass(item)"
          >
            <el-icon
              v-if="isLoading(item)"
              class="is-loading"
              ><Loading /></el-icon
            ><span>{{ statusFor(item) || "未反推" }}</span>
          </div>
          <button
            class="timeline-delete"
            type="button"
            title="删除分镜"
            @click.stop="emit('delete', item)"
          >
            <el-icon>
              <Delete />
            </el-icon>
          </button>
        </article>
      </div>
    </div>
    <div
      v-else
      class="timeline-empty"
    >
      <strong>尚未生成分镜</strong><span>开始反推后，分镜会按原片顺序出现在这里。</span>
    </div>
  </section>
</template>

<style scoped lang="scss">
.timeline-panel {
  background: var(--web-surface);
  border: 1px solid var(--web-line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.timeline-panel > header {
  padding: 9px 14px;
  display: flex;
  align-items: center;
  border-bottom: 1px solid var(--web-line-strong);
}

.timeline-panel header div {
  display: flex;
  align-items: center;
  gap: 10px;
}

.timeline-panel header span {
  color: var(--web-text-note);
  font-size: 11px;
}

.timeline-track {
  padding: 10px;
  display: flex;
  gap: 0;
  overflow-x: auto;
  scrollbar-color: var(--web-scrollbar-thumb) var(--web-scrollbar-track-panel);
  scrollbar-width: thin;
}

.timeline-card-shell {
  position: relative;
  width: 192px;
  min-width: 192px;
  height: 86px;
  padding: 0 6px;
  box-sizing: border-box;
}

.timeline-track::-webkit-scrollbar {
  height: 9px;
}

.timeline-track::-webkit-scrollbar-track {
  background: var(--web-surface);
  border-radius: 8px;
}

.timeline-track::-webkit-scrollbar-thumb {
  background: var(--web-danger-soft);
  border: 2px solid var(--web-line-strong);
  border-radius: 8px;
}

.timeline-card {
  position: relative;
  width: 180px;
  min-width: 180px;
  height: 86px;
  padding: 0;
  text-align: left;
  color: var(--web-text);
  background: var(--web-surface);
  border: 1px solid var(--web-line-strong);
  border-radius: 8px;
  cursor: pointer;
  outline: none;
  transition:
    background-color 0.18s,
    border-color 0.18s,
    outline-color 0.18s;
}

.timeline-card:hover,
.timeline-card.active {
  background: var(--web-surface);
  border-color: var(--web-line-active);
}

.timeline-card:focus-visible {
  background: var(--web-surface);
  border-color: var(--web-project-card-hover-line);
  outline: 2px solid var(--web-card-focus-ring);
  outline-offset: 2px;
}

.timeline-card.active {
  box-shadow: 0 0 0 1px var(--web-line-active) inset;
}

.timeline-header {
  position: relative;
  min-height: 28px;
  padding: 8px 8px 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  color: var(--web-text);
  font:
    13px Arial,
    sans-serif;
}

.timeline-header strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.timeline-header strong.completed {
  color: var(--web-complete);
}

.timeline-status-center {
  position: absolute;
  inset: 34px 30px 7px 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  color: var(--web-text-secondary);
  font-size: 11px;
  text-align: center;
}

.timeline-status-center.running {
  color: var(--web-accent-dark);
}

.timeline-status-center.failed {
  color: var(--web-accent-dark);
}

.timeline-status-center.completed {
  color: var(--web-complete);
}

.timeline-status-center.cancelled {
  color: var(--web-text-secondary);
}

.timeline-insert,
.timeline-delete {
  position: absolute;
  z-index: 2;
  display: grid;
  place-items: center;
  padding: 0;
  line-height: 1;
  box-sizing: border-box;
  opacity: 0;
  pointer-events: none;
  color: var(--web-accent-dark);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  cursor: pointer;
  transition:
    opacity 0.15s,
    background-color 0.15s,
    border-color 0.15s,
    color 0.15s;
}

.timeline-insert :deep(.el-icon),
.timeline-delete :deep(.el-icon) {
  width: 1em;
  height: 1em;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

.timeline-insert {
  top: 50%;
  width: 20px;
  height: 20px;
  transform: translateY(-50%);
}

.timeline-insert-before {
  left: -4px;
}

.timeline-insert-after {
  right: -4px;
}

.timeline-delete {
  top: auto;
  bottom: 8px;
  right: 5px;
  width: 22px;
  height: 22px;
  color: var(--web-accent-dark);
  border-radius: 5px;
}

.timeline-card-shell:hover .timeline-insert,
.timeline-insert:focus-visible {
  opacity: 1;
  pointer-events: auto;
}

.timeline-delete {
  opacity: 1;
  pointer-events: auto;
}

.timeline-insert:hover,
.timeline-delete:hover {
  background: var(--web-hover);
  border-color: var(--web-timeline-action-line);
}

.timeline-insert:focus-visible,
.timeline-delete:focus-visible {
  opacity: 1;
  pointer-events: auto;
  background: var(--web-hover);
  border-color: var(--web-timeline-focus-line);
  outline: 2px solid var(--web-timeline-focus-ring);
  outline-offset: 2px;
}

@media (hover: none) {
  .timeline-insert,
  .timeline-delete {
    opacity: 1;
    pointer-events: auto;
  }
}

.timeline-empty {
  height: 110px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
}

.timeline-empty span {
  margin-top: 5px;
  color: var(--web-text-tertiary);
  font-size: 12px;
}

@media (max-width: 680px) {
  .timeline-card-shell {
    width: 172px;
    min-width: 172px;
    padding: 0 6px;
  }

  .timeline-card {
    width: 160px;
    min-width: 160px;
  }
}
</style>
