<!-- 剧本分析工作台，负责原文编辑、模型配置、分析任务和结果展示。 -->
<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
import { CircleClose, Setting } from "@element-plus/icons-vue";
import { ElMessageBox } from "element-plus";
import { ArrowLeft, Pencil, Sparkles } from "@/icons";
import { onBeforeRouteLeave, useRoute } from "vue-router";
import CreativeShell from "@/components/creative/CreativeShell.vue";
import ModelConfigDialog from "@/components/creative/ModelConfigDialog.vue";
import ScriptImportDialog from "@/components/creative/ScriptImportDialog.vue";
import ScriptAnalysisResult from "@/components/creative/ScriptAnalysisResult.vue";
import { creativeApi, type CreativeOptions, type ModelConfig, type ScriptAnalysis } from "@/api/creative";
import { apiError } from "@/api/http";
import { useToastStore } from "@/stores/toast";
import { useModelPrompts } from "@/composables/useModelPrompts";
import { ensureScriptAnalysisTextModel } from "@/utils/scriptAnalysisModel";

const route = useRoute();
const toast = useToastStore();
const script = ref<ScriptAnalysis | null>(null);
const options = ref<CreativeOptions>({ styles: [], models: [] });
const modelConfigs = ref<ModelConfig[]>([]);
const name = ref("");
const sourceContent = ref("");
const resultContent = ref("");
const loading = ref(false);
const saving = ref(false);
const modelsOpen = ref(false);
const savingModels = ref(false);
const editingName = ref(false);
const nameInputRef = ref<{ focus: () => void } | null>(null);
let activeSave: Promise<boolean> | null = null;
let pollingTimer: ReturnType<typeof setInterval> | undefined;
const { prompts, promptSelections, loadPrompts, selectPrompt, createPrompt, updatePrompt, deletePrompt } =
  useModelPrompts();
const analysisActive = computed(
  () => script.value?.analysis_status === "queued" || script.value?.analysis_status === "running",
);
const analysisCancelling = computed(() => analysisActive.value && script.value?.analysis_message === "正在取消分析");
const modelConfigured = computed(() =>
  modelConfigs.value.some((config) => config.model_type === "text" && Boolean(config.model_id)),
);
const savedContent = ref({ name: "", sourceContent: "", resultContent: "" });
const hasUnsavedChanges = computed(() => {
  return (
    name.value !== savedContent.value.name ||
    sourceContent.value !== savedContent.value.sourceContent ||
    resultContent.value !== savedContent.value.resultContent
  );
});
const nameInputWidth = computed(() => {
  const text = name.value || "未命名剧本";
  const textWidth = Array.from(text).reduce((width, character) => {
    return width + (/^[\x00-\xff]$/.test(character) ? 0.6 : 1);
  }, 0);
  return `${Math.min(420, Math.max(160, Math.ceil(textWidth * 16 + 28)))}px`;
});

async function editName() {
  editingName.value = true;
  await nextTick();
  nameInputRef.value?.focus();
}
function rememberSavedContent() {
  savedContent.value = {
    name: name.value,
    sourceContent: sourceContent.value,
    resultContent: resultContent.value,
  };
}
async function autoSaveOnBlur() {
  if (loading.value || !hasUnsavedChanges.value) return;
  const saved = await save();
  if (saved && hasUnsavedChanges.value) await save();
}
function finishNameEditing() {
  editingName.value = false;
  void autoSaveOnBlur();
}

function showError(error: unknown, title = "加载失败") {
  const detail = apiError(error);
  toast.error(detail.message, detail.copyText, title);
}
async function load() {
  loading.value = true;
  try {
    const [loadedScript, loadedOptions, loadedConfigs] = await Promise.all([
      creativeApi.scriptAnalysis(String(route.params.scriptId)),
      creativeApi.options(),
      creativeApi.userModelConfigs("script_analysis"),
    ]);
    script.value = loadedScript;
    options.value = loadedOptions;
    modelConfigs.value = await ensureScriptAnalysisTextModel(loadedOptions, loadedConfigs);
    await loadPrompts(["剧本分析"]);
    name.value = script.value.name;
    sourceContent.value = script.value.source_content;
    resultContent.value = script.value.result_content;
    rememberSavedContent();
    syncPolling();
  } catch (error) {
    showError(error);
  } finally {
    loading.value = false;
  }
}
async function refreshAnalysis() {
  if (!script.value) return;
  try {
    const updated = await creativeApi.scriptAnalysis(script.value.id);
    script.value = updated;
    resultContent.value = updated.result_content;
    savedContent.value.resultContent = updated.result_content;
    syncPolling();
  } catch (error) {
    stopPolling();
    showError(error, "刷新分析状态失败");
  }
}
function syncPolling() {
  if (analysisActive.value && !pollingTimer) pollingTimer = setInterval(refreshAnalysis, 2500);
  else if (!analysisActive.value) stopPolling();
}
function stopPolling() {
  if (pollingTimer) clearInterval(pollingTimer);
  pollingTimer = undefined;
}
async function saveModels(payload: Record<string, any>) {
  const modelId = payload.prompt_reverse?.model_id;
  if (!modelId) return toast.warning("请选择文本模型");
  savingModels.value = true;
  try {
    await creativeApi.saveUserModelConfig("script_analysis", "text", modelId);
    modelConfigs.value = await creativeApi.userModelConfigs("script_analysis");
    modelsOpen.value = false;
    toast.success("模型配置已保存");
  } catch (error) {
    showError(error, "保存模型配置失败");
  } finally {
    savingModels.value = false;
  }
}
async function persistCurrentContent() {
  if (!script.value) return false;
  const submitted = {
    name: name.value,
    sourceContent: sourceContent.value,
    resultContent: resultContent.value,
  };
  saving.value = true;
  try {
    const updated = await creativeApi.updateScriptAnalysis(script.value.id, {
      name: submitted.name.trim() || "未命名剧本",
      source_content: submitted.sourceContent,
      result_content: submitted.resultContent,
    });
    script.value = updated;
    if (name.value === submitted.name) name.value = updated.name;
    if (sourceContent.value === submitted.sourceContent) sourceContent.value = updated.source_content;
    if (resultContent.value === submitted.resultContent) resultContent.value = updated.result_content;
    savedContent.value = {
      name: updated.name,
      sourceContent: updated.source_content,
      resultContent: updated.result_content,
    };
    editingName.value = false;
    return true;
  } catch (error) {
    showError(error, "保存失败");
    return false;
  } finally {
    saving.value = false;
  }
}
function save() {
  if (activeSave) return activeSave;
  activeSave = persistCurrentContent().finally(() => {
    activeSave = null;
  });
  return activeSave;
}
async function analyze() {
  if (!script.value || analysisActive.value) return;
  if (!modelConfigured.value) {
    modelsOpen.value = true;
    toast.warning("请先选择并保存剧本分析文本模型");
    return;
  }
  if (!sourceContent.value.trim()) {
    toast.warning("请先填写剧本原文");
    return;
  }
  if (!(await save())) return;
  try {
    await creativeApi.analyzeScriptAnalysis(script.value.id);
    script.value.analysis_status = "queued";
    script.value.analysis_message = "剧本分析排队中";
    syncPolling();
    toast.success("剧本分析任务已提交");
  } catch (error) {
    showError(error, "开始分析失败");
  }
}
async function cancelAnalysis() {
  if (!script.value || !analysisActive.value) return;
  try {
    await ElMessageBox.confirm("确定取消当前剧本分析任务吗？取消后已扣积分不退。", "取消分析", {
      type: "warning",
      confirmButtonText: "确认取消",
      cancelButtonText: "继续分析",
    });
  } catch (error) {
    if (error === "cancel" || error === "close") return;
    throw error;
  }
  try {
    await creativeApi.cancelScriptAnalysis(script.value.id);
    script.value.analysis_message = "正在取消分析";
    await refreshAnalysis();
  } catch (error) {
    showError(error, "取消分析失败");
  }
}
function handleAnalysisAction() {
  if (analysisActive.value) void cancelAnalysis();
  else void analyze();
}
function applyImportedScript(imported: ScriptAnalysis) {
  script.value = imported;
  name.value = imported.name;
  sourceContent.value = imported.source_content;
  resultContent.value = imported.result_content;
  rememberSavedContent();
}
async function prepareScriptImport() {
  if (activeSave && !(await activeSave)) return false;
  if (hasUnsavedChanges.value && !(await save())) return false;
  return true;
}
onBeforeRouteLeave(async () => {
  if (activeSave && !(await activeSave)) return false;
  if (!hasUnsavedChanges.value) return true;
  return save();
});
onMounted(() => {
  void load();
});
onBeforeUnmount(stopPolling);
</script>

<template>
  <CreativeShell class="script-workbench-shell">
    <section class="script-context-bar">
      <div class="workbench-heading">
        <RouterLink
          class="back-button"
          :to="{ name: 'script-analysis' }"
          title="返回"
        >
          <ArrowLeft :size="18" />
        </RouterLink>
        <el-input
          v-if="editingName"
          ref="nameInputRef"
          v-model="name"
          class="name-input"
          :style="{ '--name-input-width': nameInputWidth }"
          maxlength="120"
          placeholder="未命名剧本"
          @blur="finishNameEditing"
        />
        <div
          v-else
          class="name-display"
        >
          <strong>{{ name || "未命名剧本" }}</strong>
          <button
            type="button"
            class="name-edit-button"
            title="编辑剧本名"
            @click="editName"
          >
            <Pencil :size="15" />
          </button>
        </div>
      </div>
      <div class="script-context-actions creative-page-actions">
        <el-button @click="modelsOpen = true"
          ><el-icon><Setting /></el-icon>模型配置</el-button
        >
        <ScriptImportDialog
          v-if="script"
          :script-id="script.id"
          :disabled="loading || analysisActive"
          :before-import="prepareScriptImport"
          @imported="applyImportedScript"
        />
        <el-button
          class="analyze-button"
          :type="analysisActive ? 'danger' : 'primary'"
          :loading="saving"
          :disabled="loading || analysisCancelling || (!analysisActive && !sourceContent.trim())"
          @click="handleAnalysisAction"
          ><el-icon v-if="analysisActive"><CircleClose /></el-icon
          ><Sparkles
            v-else
            :size="16"
          />{{
            analysisActive
              ? analysisCancelling
                ? "取消中"
                : "取消任务"
              : script?.analysis_status === "succeeded" || script?.analysis_status === "failed"
                ? "重新分析"
                : "开始分析"
          }}</el-button
        >
      </div>
    </section>

    <section
      v-loading="loading"
      class="workbench-panels"
    >
      <article class="workbench-panel">
        <header>
          <strong>剧本原文</strong><span>{{ sourceContent.length }} 字</span>
        </header>
        <el-input
          v-model="sourceContent"
          type="textarea"
          resize="none"
          placeholder="在这里粘贴或编辑剧本原文"
          @blur="autoSaveOnBlur"
        />
      </article>
      <ScriptAnalysisResult
        :result="script?.analysis_result"
        :characters="script?.characters"
        :status="script?.analysis_status"
        :message="script?.analysis_message"
      />
    </section>
    <ModelConfigDialog
      v-model="modelsOpen"
      analysis-only
      project-type="script_analysis"
      :options="options"
      :configs="modelConfigs"
      :saving="savingModels"
      :prompts="prompts"
      :prompt-selections="promptSelections"
      @save="saveModels"
      @prompt-select="selectPrompt"
      @prompt-create="createPrompt"
      @prompt-update="updatePrompt"
      @prompt-delete="deletePrompt"
    />
  </CreativeShell>
</template>

<style scoped lang="scss">
.script-workbench-shell {
  height: 100dvh;
  min-height: 0;
  overflow: hidden;
}
.script-workbench-shell :deep(.creative-shell__scroll) {
  min-height: 0;
  padding-bottom: 20px;
  display: flex;
  flex: 1;
  flex-direction: column;
  overflow: hidden;
}
.script-context-bar {
  min-height: var(--control-height-lg);
  margin-bottom: var(--space-lg);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-xl);
}
.workbench-heading {
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 14px;
}
.back-button {
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  flex: 0 0 38px;
  color: var(--web-accent-muted);
  background: var(--web-surface);
  border: 1px solid var(--web-line-strong);
  border-radius: 9px;
}
.back-button:hover {
  color: var(--web-text);
  border-color: var(--web-line-emphasis);
}
.name-display {
  min-width: 0;
  max-width: min(420px, 50vw);
  display: flex;
  align-items: center;
  gap: 8px;
}
.name-display strong {
  min-width: 0;
  overflow: hidden;
  color: var(--creative-text);
  font-size: 16px;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.name-edit-button {
  width: 30px;
  height: 30px;
  padding: 0;
  display: grid;
  place-items: center;
  flex: 0 0 30px;
  color: var(--creative-muted);
  background: transparent;
  border: 0;
  border-radius: 7px;
  cursor: pointer;
}
.name-edit-button:hover {
  color: var(--creative-accent);
  background: var(--web-hover);
}
.name-input {
  width: min(var(--name-input-width), 50vw);
}
.name-input :deep(.el-input__wrapper) {
  min-height: 42px;
  padding: 0 12px;
  background: var(--creative-surface);
  border: 1px solid var(--creative-line);
  border-radius: 9px;
  box-shadow: none;
}
.name-input :deep(.el-input__wrapper:hover) {
  border-color: var(--web-line-emphasis);
}
.name-input :deep(.el-input__wrapper.is-focus) {
  border-color: var(--creative-accent);
  box-shadow: 0 0 0 1px var(--creative-accent);
}
.name-input :deep(.el-input__inner) {
  color: var(--creative-text);
  font-size: 16px;
  font-weight: 600;
}
.workbench-panels {
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  flex: 1;
  gap: 16px;
}
.analyze-button {
  font-weight: 400;
}
.workbench-panel {
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--creative-surface);
  border: 1px solid var(--creative-line);
  border-radius: 12px;
  overflow: hidden;
}
.workbench-panel header {
  min-height: 48px;
  padding: 0 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--creative-line);
}
.workbench-panel header strong {
  font-size: 14px;
}
.workbench-panel header span {
  color: var(--creative-muted);
  font-size: 12px;
}
.workbench-panel :deep(.el-textarea),
.workbench-panel :deep(.el-textarea__inner) {
  height: 100%;
  flex: 1;
}
.workbench-panel :deep(.el-textarea__inner) {
  min-height: 0 !important;
  padding: 16px;
  color: var(--creative-text);
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  font-size: 14px;
  line-height: 1.75;
}
@media (max-width: 800px) {
  .script-workbench-shell,
  .script-workbench-shell :deep(.creative-shell__scroll) {
    height: auto;
    overflow: visible;
  }
  .workbench-heading {
    flex: 1;
  }
  .name-display {
    max-width: 100%;
  }
  .name-input {
    width: 100%;
  }
  .script-context-bar {
    align-items: stretch;
    flex-direction: column;
  }
  .script-context-actions {
    width: 100%;
    flex-wrap: wrap;
  }
  .workbench-panels {
    min-height: auto;
    grid-template-columns: 1fr;
  }
  .workbench-panel {
    min-height: 420px;
  }
}
</style>
