"use server"

/**
 * AI 自动标记试卷文本 —— Server Action + 纯转换辅助函数。
 *
 * 从 exams/actions.ts 拆分而来，职责：
 * - AutoMarkSchema / AutoMarkResult 类型定义
 * - buildTiptapDocFromAiResponse / splitByDottedTexts / extractTitleFromAiResponse 等纯转换函数
 * - autoMarkExamAction Server Action
 */

import type { ActionState } from "@/shared/types/action-state"
import { getTranslations } from "next-intl/server"
import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard"
import { Permissions } from "@/shared/types/permissions"
import { z } from "zod"
import { handleActionError } from "@/shared/lib/action-utils"
import { isRecord } from "@/shared/lib/type-guards"
import {
  failState,
  successState,
  invalidFormState,
  getStringValue,
} from "../actions-helpers"

// ---------------------------------------------------------------------------
// Schema & 类型
// ---------------------------------------------------------------------------

const AutoMarkSchema = z.object({
  sourceText: z.string().min(1, "试卷文本不能为空"),
  aiProviderId: z.string().optional(),
})

export interface AutoMarkResult {
  /** Tiptap JSONContent 文档,可直接载入编辑器 */
  doc: unknown
  /** 解析出的标题(如有) */
  title: string
}

// ---------------------------------------------------------------------------
// 纯转换辅助函数
// ---------------------------------------------------------------------------

const extractTitleFromAiResponse = (data: unknown): string => {
  if (!isRecord(data)) return ""
  return typeof data.title === "string" ? data.title : ""
}

/**
 * 将文本按加点字片段切分,返回带 dotted 标记的片段数组。
 * 用于在 Tiptap 文档中标记加点字(下加点)。
 */
const splitByDottedTexts = (
  text: string,
  dottedTexts: string[]
): Array<{ text: string; dotted: boolean }> => {
  if (dottedTexts.length === 0) return [{ text, dotted: false }]

  const result: Array<{ text: string; dotted: boolean }> = []
  let remaining = text

  while (remaining.length > 0) {
    // 找到最早出现的加点字
    let earliestIdx = -1
    let earliestText = ""
    for (const dt of dottedTexts) {
      if (!dt) continue
      const idx = remaining.indexOf(dt)
      if (idx >= 0 && (earliestIdx === -1 || idx < earliestIdx)) {
        earliestIdx = idx
        earliestText = dt
      }
    }

    if (earliestIdx === -1) {
      // 没有更多加点字,剩余文本作为普通片段
      if (remaining.length > 0) {
        result.push({ text: remaining, dotted: false })
      }
      break
    }

    // 加点字之前的普通文本
    if (earliestIdx > 0) {
      result.push({ text: remaining.slice(0, earliestIdx), dotted: false })
    }
    // 加点字片段
    result.push({ text: earliestText, dotted: true })
    remaining = remaining.slice(earliestIdx + earliestText.length)
  }

  return result
}

/**
 * 将 AI 返回的结构化 JSON 转换为 Tiptap JSONContent 文档。
 * 支持 sections(分组)和顶层 questions 两种形式。
 */
const buildTiptapDocFromAiResponse = (data: unknown): unknown => {
  if (!isRecord(data)) return { type: "doc", content: [] }

  const content: unknown[] = []
  const topQuestions = Array.isArray(data.questions) ? data.questions : []

  const buildQuestionBlock = (q: unknown): unknown | null => {
    if (!isRecord(q)) return null
    const type = typeof q.type === "string" ? q.type : "text"
    const score = typeof q.score === "number" ? q.score : 0
    const contentNode = isRecord(q.content) ? q.content : {}
    const text = typeof contentNode.text === "string" ? contentNode.text : ""

    const inner: unknown[] = []

    // 阅读材料(阅读理解题型的选段)
    const readingMaterial =
      typeof contentNode.readingMaterial === "string"
        ? contentNode.readingMaterial
        : ""
    if (readingMaterial) {
      const materialLines = readingMaterial
        .split("\n")
        .filter((l) => l.trim().length > 0)
      for (const line of materialLines) {
        inner.push({
          type: "paragraph",
          content: [{ type: "text", text: line, marks: [{ type: "italic" }] }],
        })
      }
    }

    // 题干段落(按行拆分),处理加点字标记
    const dottedTexts = Array.isArray(contentNode.dottedTexts)
      ? (contentNode.dottedTexts as unknown[])
          .filter((s): s is string => typeof s === "string")
      : []

    const lines = text.split("\n").filter((l) => l.trim().length > 0)
    for (const line of lines) {
      // 如果有加点字,在文本中标记对应片段
      if (dottedTexts.length > 0) {
        const segments = splitByDottedTexts(line, dottedTexts)
        const textNodes = segments.map((seg) =>
          seg.dotted
            ? {
                type: "text",
                text: seg.text,
                marks: [{ type: "dotted" }],
              }
            : { type: "text", text: seg.text }
        )
        inner.push({ type: "paragraph", content: textNodes })
      } else {
        inner.push({ type: "paragraph", content: [{ type: "text", text: line }] })
      }
    }

    // 选项列表
    const options = Array.isArray(contentNode.options) ? contentNode.options : []
    if (options.length > 0) {
      inner.push({
        type: "orderedList",
        content: options.map((opt) => {
          const o = isRecord(opt) ? opt : {}
          const id = typeof o.id === "string" ? o.id : ""
          const optText = typeof o.text === "string" ? o.text : ""
          return {
            type: "listItem",
            content: [
              {
                type: "paragraph",
                content: [{ type: "text", text: `${id}. ${optText}` }],
              },
            ],
          }
        }),
      })
    }

    // 子题(阅读理解的小题)—— 生成为嵌套的 questionBlock,便于编辑器层级展示与解析
    const subQuestions = Array.isArray(contentNode.subQuestions)
      ? contentNode.subQuestions
      : []
    for (const sub of subQuestions) {
      if (!isRecord(sub)) continue
      const subText = typeof sub.text === "string" ? sub.text : ""
      const subScore = typeof sub.score === "number" ? sub.score : 0
      if (subText) {
        // 子题文本按行拆分为段落
        const subLines = subText.split("\n").filter((l) => l.trim().length > 0)
        const subInner =
          subLines.length > 0
            ? subLines.map((line) => ({
                type: "paragraph",
                content: [{ type: "text", text: line }],
              }))
            : [{ type: "paragraph", content: [{ type: "text", text: " " }] }]
        inner.push({
          type: "questionBlock",
          attrs: { questionId: "", type: "text", score: subScore },
          content: subInner,
        })
      }
    }

    // questionBlock 要求 content: "block+",无内容时给空段落
    const innerContent =
      inner.length > 0
        ? inner
        : [{ type: "paragraph", content: [{ type: "text", text: " " }] }]

    return {
      type: "questionBlock",
      attrs: { questionId: "", type, score },
      content: innerContent,
    }
  }

  // 构建 groupBlock(大题分组),含 instruction
  const buildGroupBlock = (g: unknown): unknown | null => {
    if (!isRecord(g)) return null
    const title = typeof g.title === "string" ? g.title : ""
    const instruction = typeof g.instruction === "string" ? g.instruction : ""
    const questions = Array.isArray(g.questions) ? g.questions : []
    const children = questions
      .map(buildQuestionBlock)
      .filter((b): b is Record<string, unknown> => b !== null)
    const groupContent =
      children.length > 0
        ? children
        : [{ type: "paragraph", content: [{ type: "text", text: " " }] }]
    return {
      type: "groupBlock",
      attrs: { title, instruction },
      content: groupContent,
    }
  }

  // 构建 sectionBlock(分卷),内含多个 groupBlock
  const volumes = Array.isArray(data.volumes) ? data.volumes : []
  const topGroups = Array.isArray(data.groups) ? data.groups : []

  for (const volume of volumes) {
    if (!isRecord(volume)) continue
    const title = typeof volume.title === "string" ? volume.title : ""
    const innerGroups = Array.isArray(volume.groups) ? volume.groups : []
    const groupBlocks = innerGroups
      .map(buildGroupBlock)
      .filter((b): b is Record<string, unknown> => b !== null)
    const sectionContent =
      groupBlocks.length > 0
        ? groupBlocks
        : [{ type: "paragraph", content: [{ type: "text", text: " " }] }]
    content.push({
      type: "sectionBlock",
      attrs: { title, level: 1 },
      content: sectionContent,
    })
  }

  // 顶层 groups(无分卷时)
  for (const g of topGroups) {
    const block = buildGroupBlock(g)
    if (block) content.push(block)
  }

  // 顶层 questions(无分卷无大题时)
  if (volumes.length === 0 && topGroups.length === 0) {
    for (const q of topQuestions) {
      const block = buildQuestionBlock(q)
      if (block) content.push(block)
    }
  }

  return { type: "doc", content }
}

// ---------------------------------------------------------------------------
// Server Action
// ---------------------------------------------------------------------------

/**
 * AI 自动标记 —— 将粘贴的试卷文本交给 AI,返回带题目块/分组/填空/加点字标记的 Tiptap JSONContent 文档。
 * 教师可在编辑器中基于此结果继续微调。
 */
export async function autoMarkExamAction(
  prevState: ActionState<AutoMarkResult> | null,
  formData: FormData
): Promise<ActionState<AutoMarkResult>> {
  const t = await getTranslations("examHomework")
  try {
    await requirePermission(Permissions.EXAM_AI_GENERATE)

    const parsed = AutoMarkSchema.safeParse({
      sourceText: getStringValue(formData, "sourceText"),
      aiProviderId: getStringValue(formData, "aiProviderId") || undefined,
    })
    if (!parsed.success) {
      return invalidFormState<AutoMarkResult>(parsed.error, { useFirstMessage: true })
    }

    const { sourceText, aiProviderId } = parsed.data

    const systemPrompt = [
      "你是一个试卷结构解析引擎,专门解析中国中小学试卷。",
      "将给定的试卷文本解析为结构化 JSON,用于在富文本编辑器中渲染为可编辑的题目块。",
      "",
      "## 识别规则",
      "",
      "1. **分卷**:识别\"第Ⅰ卷\"\"第Ⅱ卷\"\"第一部分\"等顶层分卷标记,输出到 volumes。每个 volume 含 title、instruction(可选)和 groups。",
      "2. **大题分组**:识别\"一、选择题\"\"二、填空题\"\"三、阅读理解\"等大题标题,输出到 groups(若在 volume 内则归入对应 volume,否则归入顶层 groups)。每个 group 含 title、instruction(如\"每小题3分,共24分\")和 questions。",
      "3. **题型识别**:",
      "   - 选择题(单选/多选):题干 + A/B/C/D 选项 → type: \"single_choice\" 或 \"multiple_choice\"",
      "   - 判断题:题干 + 对/错 → type: \"judgment\"",
      "   - 填空题:题干含横线空(_______或(  )) → type: \"text\",在 blanks 中标记空数",
      "   - 简答/作文题:题干 + \"不少于X字\" → type: \"text\"",
      "   - 阅读理解(含选段+多小题)→ type: \"composite\",subQuestions 存小题",
      "4. **分值**:从\"每小题3分\"\"共24分\"\"(12分)\"等提取,分摊到每题。",
      "5. **选项**:A. B. C. D. 格式,输出到 content.options,每项含 id 和 text。",
      "6. **填空**:横线\"_______\"或括号\"(  )\"位置,在 content.blanks 中标记(数组长度=空数)。",
      "7. **加点字**:拼音注音题中加点的字(如\"解\"剖),在 content.dottedTexts 中列出原文片段。",
      "8. **子题**:阅读理解下的\"1. 2. 3.\"小题,输出到 content.subQuestions,每项含 text 和 score。",
      "9. **阅读材料**:阅读理解的文章选段,放到 content.readingMaterial 字段。",
      "10. **说明文字**:\"每小题3分,共24分\"\"选择正确答案的番号填涂在答题卡上\"等放到 group.instruction,不要混入题干。",
      "",
      "## 输出 JSON schema",
      "",
      "```json",
      "{",
      '  "title": "试卷标题",',
      '  "volumes": [',
      "    {",
      '      "title": "第Ⅰ卷 选择题",',
      '      "groups": [',
      "        {",
      '          "title": "一、选择正确答案的番号填涂在答题卡上",',
      '          "instruction": "每小题3分,共24分",',
      '          "questions": [',
      "            {",
      '              "type": "single_choice",',
      '              "score": 3,',
      '              "content": {',
      '                "text": "下面加点字的注音全部正确的一项是",',
      '                "options": [{"id":"A","text":"解剖(pō) 蹭饭(cènɡ)"},{"id":"B","text":"栖息(qī) 譬如(pì)"}],',
      '                "blanks": [],',
      '                "dottedTexts": ["解","蹭","徉"],',
      '                "subQuestions": []',
      "              }",
      "            }",
      "          ]",
      "        }",
      "      ]",
      "    }",
      "  ],",
      '  "groups": []',
      "}",
      "```",
      "",
      "## 注意事项",
      "- 不要输出 markdown 代码块标记(```),直接输出 JSON。",
      "- 不要输出 ... 或 [...] 等占位符,必须输出完整数据。",
      "- 如果没有 volumes,可直接返回 { \"groups\": [...] } 或 { \"questions\": [...] }。",
      "- 阅读理解的选段文本放到 content.readingMaterial,不要混入题干 text。",
      "- 作文题的\"不少于300字\"等要求保留在 text 中。",
      "- \"共24分\"等总分信息不要写进 title,放到 instruction;实际总分由子题自动累加。",
    ].join("\n")

    const { createAiChatCompletion } = await import("@/shared/lib/ai")
    const { env } = await import("@/env.mjs")
    const { parseAiResponse } = await import("./parse")

    const aiResult = await createAiChatCompletion({
      model: String(env.AI_MODEL ?? "gpt-4o-mini"),
      providerId: aiProviderId,
      messages: [
        { role: "system", content: systemPrompt },
        { role: "user", content: sourceText },
      ],
      temperature: 0,
      maxTokens: 8000,
    })

    const parsedJson = await parseAiResponse(aiResult.content, aiProviderId)
    const doc = buildTiptapDocFromAiResponse(parsedJson)
    const title = extractTitleFromAiResponse(parsedJson)

    return successState({ doc, title }, t("exam.actionMessages.autoMarkCompleted"))
  } catch (error) {
    if (error instanceof PermissionDeniedError) {
      return failState<AutoMarkResult>(error.message)
    }
    console.error("[autoMarkExamAction]", error instanceof Error ? error.message : String(error))
    return handleActionError(error)
  }
}
