import { serverRequest } from "./serverConnection";

export interface ScriptEvalResult {
  totalScore: number;
  grade: string;
  dimensionScores: Record<string, number>;
  subScores?: Record<string, Record<string, number>>;
  evidence?: Record<string, string[]>;
  summary: string;
  issues: string[];
  highlights: string[];
  suggestions: string[];
}

const MODEL = "qwen3.7-max";

const EVAL_OUTPUT_CONTRACT = `
强制输出 JSON，主维度键名必须严格为：
hook(20), plot(20), character(15), logic(15), visual(15), content(15)。
不要把 dialogue 作为主维度返回；台词对白作为 character/plot/content 的证据和子项分析。

同时返回 subScores 和 evidence：
- subScores：每个主维度 3-5 个细分参数，分值按该维度满分拆分。
- evidence：每个主维度 1-3 条具体证据，必须指向场景、台词、设定、冲突或段落。

返回结构：
{
  "dimensionScores": { "hook": 数字, "plot": 数字, "character": 数字, "logic": 数字, "visual": 数字, "content": 数字 },
  "subScores": {
    "hook": { "openingImpact": 数字, "suspenseChain": 数字, "sceneHook": 数字 },
    "plot": { "structure": 数字, "rhythm": 数字, "conflict": 数字, "reversal": 数字 },
    "character": { "motivation": 数字, "arc": 数字, "voice": 数字, "relationship": 数字 },
    "logic": { "causality": 数字, "worldRules": 数字, "foreshadowing": 数字, "continuity": 数字 },
    "visual": { "sceneDetail": 数字, "shotPotential": 数字, "aigcFeasibility": 数字 },
    "content": { "theme": 数字, "emotion": 数字, "marketFit": 数字, "originality": 数字 }
  },
  "evidence": { "hook": ["..."], "plot": ["..."], "character": ["..."], "logic": ["..."], "visual": ["..."], "content": ["..."] },
  "summary": "200-300字综合评价",
  "issues": ["具体扣分点，带维度和证据", ...],
  "highlights": ["具体亮点，带维度和证据", ...],
  "suggestions": ["按优先级排列的改稿建议", ...]
}`;

const EVAL_SYSTEM_PROMPT = `你是一位资深影视剧本评审专家，拥有二十年以上的编剧、制片和剧本医生经验。你精通各类影视叙事理论（三幕式、英雄之旅、起承转合、序列法），同时深度跟踪AIGC短剧/漫剧行业最新趋势。你的任务是对用户提供的剧本进行严谨、系统、多维度的量化评分。

【剧本类型识别】
收到剧本后，首先判断类型：AIGC短剧/漫剧（单集5-30分钟，竖屏平台，高密度反转、强节奏）或传统影视剧本（单集40分钟以上，长视频平台，完整起承转合）。类型判定将影响各维度的评价侧重点。

【评分体系（100分制，六个维度）】
1. hook 钩子设计（20分）：开篇钩子、集末钩子、场景内钩子、悬念链完整性。短剧前3秒须有即时爆点；长剧第一幕结束前须建立核心悬念。
2. plot 剧情结构（20分）：结构框架、节奏控制、冲突设计、逻辑自洽。短剧"每分钟有事件"，反转密度加分；长剧需处理好B线C线与主线交织。
3. character 角色塑造（18分）：主角弧光、角色辨识度、角色动机、配角质量。短剧角色须在前2分钟建立；长剧需要内在矛盾和多阶段成长。
4. dialogue 台词对白（15分）：角色语言区分度、信息传递效率、潜台词与留白、金句与记忆点。
5. visual 画面表现（15分）：场景描写质量、视觉叙事技巧、镜头感与节奏、制作可行性。AIGC需考虑AI生成技术边界与一致性。
6. content 内容深度（12分）：主题表达、情感共鸣、社会/人性洞察。

【评分铁律】
- 扣分必须明确指出剧本中的具体段落/场景/台词。
- 严禁给出任何维度的满分，必须有扣分理由。
- 优缺点都要充分展开，不可只批不夸或只夸不批。
- 不因题材类型偏见降低评分，不因某一方面出色而抬高其他维度（避免光环效应）。
- 敢于拉开各维度分数差距，避免全部给中等分数。

【等级标准】按总分百分比：S≥90 | A 80-89 | B 70-79 | C 60-69 | D<60。

请严格按以下 JSON 格式返回（不要包含任何其他文字，不要用代码块包裹以外的说明）：
{
  "dimensionScores": { "hook": 数字, "plot": 数字, "character": 数字, "dialogue": 数字, "visual": 数字, "content": 数字 },
  "summary": "200-300字综合评价，概括整体质量、市场潜力与目标受众匹配度",
  "issues": ["每条指出具体维度的扣分点并引用剧本原文位置", ...],
  "highlights": ["核心亮点，引用剧本具体场景", ...],
  "suggestions": ["按优先级排列的改进建议（最优先/次优先/可优化）", ...]
}`;

const DIMENSION_WEIGHTS: Record<string, { maxScore: number }> = {
  hook: { maxScore: 20 },
  plot: { maxScore: 20 },
  character: { maxScore: 15 },
  logic: { maxScore: 15 },
  visual: { maxScore: 15 },
  content: { maxScore: 15 },
};

function computeTotalAndGrade(scores: Record<string, number>): { totalScore: number; grade: string } {
  const totalScore = Math.round(
    Object.entries(DIMENSION_WEIGHTS).reduce((sum, [key, dim]) => {
      return sum + Math.max(0, Math.min(dim.maxScore, scores[key] ?? 0));
    }, 0),
  );
  const grade = totalScore >= 90 ? "S" : totalScore >= 80 ? "A" : totalScore >= 70 ? "B" : totalScore >= 60 ? "C" : "D";
  return { totalScore, grade };
}

function extractJson(text: string): unknown {
  const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
  const raw = fenced ? fenced[1].trim() : text.trim();
  return JSON.parse(raw);
}

function normalizeScoreValue(value: unknown, maxScore: number): number {
  const score = Number(value);
  if (!Number.isFinite(score)) return 0;
  return Math.max(0, Math.min(maxScore, Math.round(score * 10) / 10));
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
}

function normalizeEvidenceItems(source: unknown[], limit: number): string[] {
  const items: string[] = [];
  for (const item of source) {
    const value = String(item).trim();
    if (!value) continue;
    items.push(value);
    if (items.length >= limit) break;
  }
  return items;
}

function normalizeNestedScores(value: unknown): Record<string, Record<string, number>> {
  if (!isRecord(value)) return {};

  const normalized: Record<string, Record<string, number>> = {};
  for (const [dimensionKey, dimension] of Object.entries(DIMENSION_WEIGHTS)) {
    const source = value[dimensionKey] ?? (dimensionKey === "logic" ? value.dialogue : undefined);
    if (!isRecord(source)) continue;

    const entries = Object.entries(source)
      .map(([key, score]) => [key, normalizeScoreValue(score, dimension.maxScore)] as const)
      .filter(([, score]) => score > 0);
    if (entries.length > 0) normalized[dimensionKey] = Object.fromEntries(entries);
  }

  return normalized;
}

function normalizeEvidence(value: unknown): Record<string, string[]> {
  if (!isRecord(value)) return {};

  const normalized: Record<string, string[]> = {};
  for (const dimensionKey of Object.keys(DIMENSION_WEIGHTS)) {
    const source = value[dimensionKey] ?? (dimensionKey === "logic" ? value.dialogue : undefined);
    if (!Array.isArray(source)) continue;

    const items = normalizeEvidenceItems(source, 3);
    if (items.length > 0) normalized[dimensionKey] = items;
  }

  return normalized;
}

export async function evaluateScript(script: string, signal?: AbortSignal): Promise<ScriptEvalResult> {
  const payload = await serverRequest<{
    content?: string;
    choices?: Array<{ message?: { content?: string } }>;
    text?: string;
  }>("ai/chat", {
    method: "POST",
    body: {
      model: MODEL,
      messages: [
        { role: "system", content: EVAL_SYSTEM_PROMPT },
        { role: "system", content: EVAL_OUTPUT_CONTRACT },
        { role: "user", content: `请评测以下剧本：\n\n${script.slice(0, 8000)}` },
      ],
      stream: false,
      temperature: 0.3,
      max_tokens: 4096,
    },
    signal,
    timeoutMs: 180_000,
    maxRetries: 0,
    fallbackMessage: "评测请求失败",
  });

  const content: string = payload?.content ?? payload?.choices?.[0]?.message?.content ?? payload?.text ?? "";

  if (!content) throw new Error("模型未返回有效内容");

  const parsed = extractJson(content) as Record<string, unknown>;
  const dimensionScores: Record<string, number> = {};
  const rawScores = parsed.dimensionScores as Record<string, number> | undefined;
  if (!rawScores || typeof rawScores !== "object") throw new Error("评分格式异常");

  for (const key of Object.keys(DIMENSION_WEIGHTS)) {
    const rawValue = key === "logic" ? rawScores.logic ?? rawScores.dialogue : rawScores[key];
    dimensionScores[key] = normalizeScoreValue(rawValue, DIMENSION_WEIGHTS[key].maxScore);
  }

  const { totalScore, grade } = computeTotalAndGrade(dimensionScores);

  return {
    totalScore,
    grade,
    dimensionScores,
    subScores: normalizeNestedScores(parsed.subScores),
    evidence: normalizeEvidence(parsed.evidence),
    summary: String(parsed.summary || ""),
    issues: Array.isArray(parsed.issues) ? parsed.issues.map(String) : [],
    highlights: Array.isArray(parsed.highlights) ? parsed.highlights.map(String) : [],
    suggestions: Array.isArray(parsed.suggestions) ? parsed.suggestions.map(String) : [],
  };
}
