"use client";

/**
 * 考试作答主组件（ai14）
 *
 * 编排所有考试作答能力：状态机 / 服务器时间同步 / IDB 草稿恢复 /
 * 自动保存 / 防作弊 / 多标签检测 / 提交去重 / 倒计时
 *
 * 布局：顶部栏（标题+倒计时+服务器时间）| 左侧题目导航 | 主内容区 | 底部导航栏
 */

import { useState, useEffect, useCallback, useRef } from "react";
import { useRouter } from "next/navigation";
import { cn } from "@/lib/exam-types";
import type { AnswerInput, ExamDetail, ExamQuestion } from "@/lib/exam-types";
import { CountdownTimer } from "./countdown-timer";
import { DraftRecovery } from "./draft-recovery";
import { HandwritingPad } from "@/components/handwriting-pad";
import { ImageUpload } from "@/components/image-upload";
import { useExamStateMachine } from "@/hooks/use-exam-state-machine";
import { useServerTimeSync } from "@/hooks/use-server-time-sync";
import { useExamDraft } from "@/hooks/use-exam-draft";
import { useAntiCheat } from "@/hooks/use-anti-cheat";
import { useMultiTabGuard } from "@/hooks/use-multi-tab-guard";
import { useSubmitDedup } from "@/hooks/use-submit-dedup";

export interface ExamTakingProps {
  examId: string;
}

const GRAPHQL_ENDPOINT =
  process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT ?? "/api/v1/student/graphql";
const IS_MOCKING = process.env.NEXT_PUBLIC_API_MOCKING === "enabled";

/** 创建模拟考试数据（开发环境 / API 未就绪时使用） */
function createMockExamDetail(examId: string): ExamDetail {
  return {
    id: examId,
    name: "期末考试 · 数学综合测试",
    durationSeconds: 5400,
    startsAt: new Date(Date.now() - 60_000).toISOString(),
    expiresAt: new Date(Date.now() + 5400_000).toISOString(),
    questionCount: 5,
    questions: [
      {
        id: "q1",
        type: "single-choice",
        content: "已知函数 f(x) = x² + 2x + 1，求 f(2) 的值。",
        options: [
          { id: "a", text: "7" },
          { id: "b", text: "8" },
          { id: "c", text: "9" },
          { id: "d", text: "10" },
        ],
        score: 0,
        maxScore: 10,
      },
      {
        id: "q2",
        type: "multiple-choice",
        content: "下列哪些是质数？（多选）",
        options: [
          { id: "a", text: "2" },
          { id: "b", text: "4" },
          { id: "c", text: "7" },
          { id: "d", text: "9" },
        ],
        score: 0,
        maxScore: 15,
      },
      {
        id: "q3",
        type: "fill-blank",
        content: "sin(30°) = ______",
        score: 0,
        maxScore: 10,
      },
      {
        id: "q4",
        type: "short-answer",
        content: "简述导数的定义。",
        score: 0,
        maxScore: 20,
      },
      {
        id: "q5",
        type: "essay",
        content: "论述微积分的基本思想及其在现代科学中的应用。",
        score: 0,
        maxScore: 45,
      },
    ],
  };
}

/** 获取考试详情（API 或 Mock） */
async function fetchExamDetail(examId: string): Promise<ExamDetail> {
  if (IS_MOCKING) {
    await new Promise((r) => setTimeout(r, 300));
    return createMockExamDetail(examId);
  }
  const response = await fetch(GRAPHQL_ENDPOINT, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      query: `query ExamDetail($examId: ID!) {
        exam(id: $examId) {
          id name durationSeconds startsAt expiresAt questionCount
          questions { id type content options { id text } score maxScore }
        }
      }`,
      variables: { examId },
    }),
  });
  if (!response.ok) throw new Error(`考试详情加载失败: ${response.status}`);
  const result = (await response.json()) as {
    data?: { exam?: ExamDetail };
    errors?: Array<{ message: string }>;
  };
  if (result.errors?.length)
    throw new Error(result.errors[0]?.message ?? "查询失败");
  if (!result.data?.exam) throw new Error("考试数据为空");
  return result.data.exam;
}

/** 提交考试（API 或 Mock） */
async function submitExamApi(
  examId: string,
  idempotencyKey: string,
): Promise<boolean> {
  if (IS_MOCKING) {
    await new Promise((r) => setTimeout(r, 1000));
    return true;
  }
  const response = await fetch(GRAPHQL_ENDPOINT, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      query: `mutation SubmitExam($examId: ID!, $idempotencyKey: String!) {
        submitExam(examId: $examId, idempotencyKey: $idempotencyKey) { submissionId submittedAt }
      }`,
      variables: { examId, idempotencyKey },
    }),
  });
  return response.ok;
}

export function ExamTaking({ examId }: ExamTakingProps): JSX.Element {
  const router = useRouter();

  // === Hooks ===
  const { state, actions, canSubmit } = useExamStateMachine();
  const { offset, isSyncing, lastSyncAt, isDegraded } = useServerTimeSync();
  const {
    draft,
    saveDraft,
    setAnswers,
    clearDraft,
    isSaving,
    hasDraft,
    lastSavedAt,
  } = useExamDraft(examId);
  const {
    isFullscreen,
    requestFullscreen,
    showWarning,
    warningMessage,
    dismissWarning,
  } = useAntiCheat(examId);
  const { isDuplicate, dismissWarning: dismissTabWarning } =
    useMultiTabGuard(examId);
  const {
    idempotencyKey,
    isSubmitting,
    markSubmitting,
    markComplete,
    canSubmit: canDedupSubmit,
  } = useSubmitDedup(examId);

  // === State ===
  const [exam, setExam] = useState<ExamDetail | null>(null);
  const [answers, setAnswersState] = useState<Record<string, AnswerInput>>({});
  const [currentIndex, setCurrentIndex] = useState(0);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [showDraftModal, setShowDraftModal] = useState(false);
  const [showSubmitConfirm, setShowSubmitConfirm] = useState(false);
  const [submitError, setSubmitError] = useState<string | null>(null);
  const submittedRef = useRef(false);

  // === 加载考试详情 ===
  useEffect(() => {
    fetchExamDetail(examId)
      .then((data) => {
        setExam(data);
        setLoading(false);
      })
      .catch((err: unknown) => {
        setError(err instanceof Error ? err.message : "加载失败");
        setLoading(false);
      });
  }, [examId]);

  // === 检测到草稿时弹恢复弹窗 ===
  useEffect(() => {
    if (hasDraft && draft && !submittedRef.current) {
      setShowDraftModal(true);
    }
  }, [hasDraft, draft]);

  // === 同步答案到 hook 供自动保存 ===
  useEffect(() => {
    setAnswers(answers);
  }, [answers, setAnswers]);

  // === 考试数据就绪后启动状态机 ===
  useEffect(() => {
    if (exam && state === "NotStarted") {
      actions.start();
    }
  }, [exam, state, actions]);

  // === 离开页面警告 ===
  useEffect(() => {
    const onBeforeUnload = (e: BeforeUnloadEvent): void => {
      if (state === "InProgress" || state === "AutoSaving") {
        e.preventDefault();
        e.returnValue = "";
      }
    };
    window.addEventListener("beforeunload", onBeforeUnload);
    return () => window.removeEventListener("beforeunload", onBeforeUnload);
  }, [state]);

  // === 时间到自动提交 ===
  const handleTimeUp = useCallback((): void => {
    if (submittedRef.current) return;
    setSubmitError(null);
    markSubmitting();
    void submitExamApi(examId, idempotencyKey)
      .then(async (ok) => {
        if (ok) {
          submittedRef.current = true;
          actions.beginSubmit();
          actions.completeSubmit();
          await clearDraft();
          router.push(`/my-exams/${examId}/result`);
        } else {
          markComplete();
          setSubmitError("自动提交失败，请手动重试");
        }
      })
      .catch(() => {
        markComplete();
        setSubmitError("网络错误，自动提交失败");
      });
  }, [
    examId,
    idempotencyKey,
    markSubmitting,
    markComplete,
    actions,
    clearDraft,
    router,
  ]);

  // === 答案变更 ===
  const handleAnswerChange = useCallback(
    (questionId: string, answer: AnswerInput): void => {
      setAnswersState((prev) => ({ ...prev, [questionId]: answer }));
    },
    [],
  );

  // === 题目失焦保存草稿 ===
  const handleBlur = useCallback((): void => {
    if (Object.keys(answers).length > 0) {
      void saveDraft(answers);
    }
  }, [answers, saveDraft]);

  // === 提交确认 ===
  const handleConfirmSubmit = useCallback((): void => {
    setShowSubmitConfirm(true);
  }, []);

  // === 执行提交 ===
  const handleExecuteSubmit = useCallback(async (): Promise<void> => {
    if (!canDedupSubmit || submittedRef.current) return;
    setShowSubmitConfirm(false);
    setSubmitError(null);
    markSubmitting();
    try {
      const ok = await submitExamApi(examId, idempotencyKey);
      if (ok) {
        submittedRef.current = true;
        actions.beginSubmit();
        actions.completeSubmit();
        await clearDraft();
        router.push(`/my-exams/${examId}/result`);
      } else {
        markComplete();
        setSubmitError("提交失败，请重试");
      }
    } catch {
      markComplete();
      setSubmitError("网络错误，提交失败，请重试");
    }
  }, [
    canDedupSubmit,
    examId,
    idempotencyKey,
    markSubmitting,
    markComplete,
    actions,
    clearDraft,
    router,
  ]);

  // === 草稿恢复 ===
  const handleRestoreDraft = useCallback((): void => {
    if (draft) {
      setAnswersState(draft.answers);
      setAnswers(draft.answers);
    }
    setShowDraftModal(false);
  }, [draft, setAnswers]);

  const handleDiscardDraft = useCallback((): void => {
    void clearDraft();
    setAnswersState({});
    setShowDraftModal(false);
  }, [clearDraft]);

  // === 加载态 ===
  if (loading) {
    return (
      <div className="flex h-screen items-center justify-center bg-paper">
        <div className="text-center">
          <div className="font-serif text-xl text-ink">加载考试中…</div>
          <div className="mt-sm text-sm text-ink-muted">正在获取考试详情</div>
        </div>
      </div>
    );
  }

  // === 错误态 ===
  if (error || !exam) {
    return (
      <div className="flex h-screen items-center justify-center bg-paper">
        <div className="text-center">
          <div className="font-serif text-xl text-danger">加载失败</div>
          <div className="mt-sm text-sm text-ink-muted">
            {error ?? "未知错误"}
          </div>
          <button
            type="button"
            onClick={() => router.back()}
            className="mt-lg rounded bg-accent px-lg py-sm text-sm text-paper hover:bg-accent-hover"
          >
            返回
          </button>
        </div>
      </div>
    );
  }

  const currentQuestion = exam.questions[currentIndex];
  if (!currentQuestion) {
    return (
      <div className="flex h-screen items-center justify-center bg-paper">
        <p className="text-sm text-ink-muted">题目不存在</p>
      </div>
    );
  }
  const unansweredCount = exam.questions.filter((q) => !answers[q.id]).length;
  const answeredCount = exam.questions.length - unansweredCount;

  return (
    <div className="flex h-screen flex-col bg-paper">
      {/* === 顶部栏 === */}
      <header className="flex items-center justify-between border-b border-rule px-xl py-md">
        <div className="flex items-center gap-md">
          <h1 className="font-serif text-lg text-ink">{exam.name}</h1>
          {isSaving && (
            <span className="text-xs text-ink-muted" aria-live="polite">
              保存中…
            </span>
          )}
          {lastSavedAt && !isSaving && (
            <span className="text-xs text-ink-subtle">
              已保存 {new Date(lastSavedAt).toLocaleTimeString("zh-CN")}
            </span>
          )}
        </div>
        <div className="flex items-center gap-lg">
          <CountdownTimer
            endTime={exam.expiresAt}
            onTimeUp={handleTimeUp}
            serverTimeOffset={offset}
          />
          <div className="flex items-center gap-sm text-xs text-ink-muted">
            <span
              className={cn(
                "inline-block h-2 w-2 rounded-full",
                isDegraded
                  ? "bg-warning"
                  : isSyncing
                    ? "bg-info"
                    : "bg-success",
              )}
              aria-hidden="true"
            />
            <span aria-live="polite">
              {isDegraded ? "时间同步降级" : isSyncing ? "同步中" : "已同步"}
            </span>
            {lastSyncAt && (
              <span className="text-ink-subtle">
                {new Date(lastSyncAt).toLocaleTimeString("zh-CN")}
              </span>
            )}
          </div>
          <button
            type="button"
            onClick={requestFullscreen}
            className="rounded border border-rule px-sm py-xs text-xs text-ink-muted hover:bg-accent-muted"
            aria-label={isFullscreen ? "退出全屏" : "进入全屏"}
          >
            {isFullscreen ? "退出全屏" : "全屏"}
          </button>
        </div>
      </header>

      {/* === 主内容区 === */}
      <div className="flex flex-1 overflow-hidden">
        {/* 左侧题目导航 */}
        <aside className="w-48 border-r border-rule bg-paper p-md lg:w-64">
          <div className="mb-md text-xs font-medium text-ink-muted">
            答题进度 {answeredCount}/{exam.questions.length}
          </div>
          <nav aria-label="题目导航">
            <ul className="space-y-sm">
              {exam.questions.map((q, idx) => {
                const isAnswered = !!answers[q.id];
                const isCurrent = idx === currentIndex;
                return (
                  <li key={q.id}>
                    <button
                      type="button"
                      onClick={() => setCurrentIndex(idx)}
                      aria-current={isCurrent ? "true" : undefined}
                      aria-label={`第 ${idx + 1} 题${isAnswered ? "（已作答）" : "（未作答）"}`}
                      className={cn(
                        "flex w-full items-center gap-sm rounded px-sm py-xs text-sm transition-colors",
                        isCurrent && "bg-accent text-paper",
                        !isCurrent &&
                          isAnswered &&
                          "text-success hover:bg-accent-muted",
                        !isCurrent &&
                          !isAnswered &&
                          "text-ink-muted hover:bg-accent-muted",
                      )}
                    >
                      <span className="font-mono">{idx + 1}</span>
                      <span className="flex-1 text-left">
                        {q.type === "single-choice" && "单选"}
                        {q.type === "multiple-choice" && "多选"}
                        {q.type === "fill-blank" && "填空"}
                        {q.type === "short-answer" && "简答"}
                        {q.type === "essay" && "论述"}
                      </span>
                      {isAnswered && (
                        <span aria-hidden="true" className="text-xs">
                          {isCurrent ? "✓" : "✓"}
                        </span>
                      )}
                    </button>
                  </li>
                );
              })}
            </ul>
          </nav>
        </aside>

        {/* 题目内容区 */}
        <main className="flex-1 overflow-y-auto p-xl">
          <div className="mx-auto max-w-3xl">
            <div className="mb-lg flex items-center justify-between">
              <span className="font-serif text-lg text-ink">
                第 {currentIndex + 1} 题 / 共 {exam.questions.length} 题
              </span>
              <span className="text-sm text-ink-muted">
                本题 {currentQuestion.maxScore} 分
              </span>
            </div>
            <div className="rounded-lg border border-rule bg-paper p-xl shadow-paper">
              <p className="mb-xl text-base text-ink">
                {currentQuestion.content}
              </p>
              <QuestionInput
                question={currentQuestion}
                answer={answers[currentQuestion.id] ?? null}
                onChange={(ans) => handleAnswerChange(currentQuestion.id, ans)}
                onBlur={handleBlur}
              />
            </div>
          </div>
        </main>
      </div>

      {/* === 底部导航栏 === */}
      <footer className="flex items-center justify-between border-t border-rule px-xl py-md">
        <button
          type="button"
          onClick={() => setCurrentIndex((i) => Math.max(0, i - 1))}
          disabled={currentIndex === 0}
          className="rounded border border-rule px-lg py-sm text-sm text-ink hover:bg-accent-muted disabled:opacity-40"
          aria-label="上一题"
        >
          上一题
        </button>
        <span className="text-sm text-ink-muted">
          {currentIndex + 1} / {exam.questions.length}
        </span>
        <div className="flex items-center gap-sm">
          <button
            type="button"
            onClick={() =>
              setCurrentIndex((i) => Math.min(exam.questions.length - 1, i + 1))
            }
            disabled={currentIndex === exam.questions.length - 1}
            className="rounded border border-rule px-lg py-sm text-sm text-ink hover:bg-accent-muted disabled:opacity-40"
            aria-label="下一题"
          >
            下一题
          </button>
          <button
            type="button"
            onClick={handleConfirmSubmit}
            disabled={
              !canSubmit ||
              !canDedupSubmit ||
              isSubmitting ||
              state === "Submitted"
            }
            className="rounded bg-accent px-xl py-sm text-sm text-paper hover:bg-accent-hover disabled:opacity-50"
            aria-label="提交考试"
          >
            {isSubmitting ? "提交中…" : "提交考试"}
          </button>
        </div>
      </footer>

      {/* === 草稿恢复弹窗 === */}
      <DraftRecovery
        hasDraft={showDraftModal}
        onRestore={handleRestoreDraft}
        onDiscard={handleDiscardDraft}
      />

      {/* === 提交确认弹窗 === */}
      {showSubmitConfirm && (
        <div
          className="fixed inset-0 z-50 flex items-center justify-center bg-ink/40 backdrop-blur-sm"
          role="dialog"
          aria-modal="true"
          aria-labelledby="submit-confirm-title"
        >
          <div className="mx-md w-full max-w-md rounded-lg border border-rule bg-paper p-xl shadow-card">
            <h2
              id="submit-confirm-title"
              className="font-serif text-xl text-ink"
            >
              确认提交
            </h2>
            {unansweredCount > 0 ? (
              <p className="mt-sm text-sm text-warning">
                您还有 {unansweredCount} 题未作答，确定要提交吗？
              </p>
            ) : (
              <p className="mt-sm text-sm text-ink-muted">
                所有题目已作答，确定要提交吗？
              </p>
            )}
            {submitError && (
              <p className="mt-sm text-sm text-danger" role="alert">
                {submitError}
              </p>
            )}
            <div className="mt-xl flex justify-end gap-sm">
              <button
                type="button"
                onClick={() => setShowSubmitConfirm(false)}
                className="rounded border border-rule px-lg py-sm text-sm text-ink-muted hover:bg-paper"
              >
                取消
              </button>
              <button
                type="button"
                onClick={handleExecuteSubmit}
                disabled={isSubmitting}
                className="rounded bg-accent px-lg py-sm text-sm text-paper hover:bg-accent-hover disabled:opacity-50"
              >
                {isSubmitting ? "提交中…" : "确认提交"}
              </button>
            </div>
          </div>
        </div>
      )}

      {/* === 防作弊警告遮罩 === */}
      {showWarning && warningMessage && (
        <div
          className="fixed inset-0 z-40 flex items-center justify-center bg-ink/60"
          role="alertdialog"
          aria-modal="true"
          aria-labelledby="warning-title"
        >
          <div className="mx-md w-full max-w-sm rounded-lg border border-danger bg-paper p-xl shadow-card">
            <h2 id="warning-title" className="font-serif text-lg text-danger">
              警告
            </h2>
            <p className="mt-sm text-sm text-ink">{warningMessage}</p>
            <button
              type="button"
              onClick={dismissWarning}
              className="mt-lg w-full rounded bg-danger px-lg py-sm text-sm text-paper hover:opacity-90"
            >
              我知道了
            </button>
          </div>
        </div>
      )}

      {/* === 多标签检测遮罩 === */}
      {isDuplicate && (
        <div
          className="fixed inset-0 z-50 flex items-center justify-center bg-ink/80"
          role="alertdialog"
          aria-modal="true"
          aria-labelledby="multitab-title"
        >
          <div className="mx-md w-full max-w-sm rounded-lg border border-danger bg-paper p-xl shadow-card text-center">
            <h2 id="multitab-title" className="font-serif text-lg text-danger">
              考试已在其他标签页打开
            </h2>
            <p className="mt-sm text-sm text-ink-muted">
              请关闭其他标签页后重试，或点击下方按钮继续（仅当前标签有效）。
            </p>
            <button
              type="button"
              onClick={dismissTabWarning}
              className="mt-lg rounded bg-accent px-lg py-sm text-sm text-paper hover:bg-accent-hover"
            >
              在此标签继续
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

// === 题目输入子组件 ===

interface QuestionInputProps {
  question: ExamQuestion;
  answer: AnswerInput | null;
  onChange: (answer: AnswerInput) => void;
  onBlur: () => void;
}

function QuestionInput({
  question,
  answer,
  onChange,
  onBlur,
}: QuestionInputProps): JSX.Element {
  switch (question.type) {
    case "single-choice":
      return (
        <div role="radiogroup" aria-label="单选题选项" className="space-y-sm">
          {question.options?.map((opt) => {
            const isChecked =
              answer?.type === "single-choice" && answer.optionId === opt.id;
            return (
              <label
                key={opt.id}
                className={cn(
                  "flex cursor-pointer items-center gap-md rounded border px-lg py-sm transition-colors",
                  isChecked
                    ? "border-accent bg-accent-muted"
                    : "border-rule hover:bg-accent-muted",
                )}
              >
                <input
                  type="radio"
                  name={question.id}
                  checked={isChecked}
                  onChange={() =>
                    onChange({ type: "single-choice", optionId: opt.id })
                  }
                  onBlur={onBlur}
                  className="accent-accent"
                />
                <span className="text-sm text-ink">{opt.text}</span>
              </label>
            );
          })}
        </div>
      );

    case "multiple-choice":
      return (
        <div role="group" aria-label="多选题选项" className="space-y-sm">
          {question.options?.map((opt) => {
            const isChecked =
              answer?.type === "multiple-choice" &&
              answer.optionIds.includes(opt.id);
            return (
              <label
                key={opt.id}
                className={cn(
                  "flex cursor-pointer items-center gap-md rounded border px-lg py-sm transition-colors",
                  isChecked
                    ? "border-accent bg-accent-muted"
                    : "border-rule hover:bg-accent-muted",
                )}
              >
                <input
                  type="checkbox"
                  checked={isChecked}
                  onChange={(e) => {
                    const current =
                      answer?.type === "multiple-choice"
                        ? answer.optionIds
                        : [];
                    const next = e.target.checked
                      ? [...current, opt.id]
                      : current.filter((id) => id !== opt.id);
                    onChange({ type: "multiple-choice", optionIds: next });
                  }}
                  onBlur={onBlur}
                  className="accent-accent"
                />
                <span className="text-sm text-ink">{opt.text}</span>
              </label>
            );
          })}
        </div>
      );

    case "fill-blank":
      return (
        <input
          type="text"
          value={answer?.type === "fill-blank" ? (answer.values[0] ?? "") : ""}
          onChange={(e) =>
            onChange({ type: "fill-blank", values: [e.target.value] })
          }
          onBlur={onBlur}
          placeholder="请输入答案"
          className="w-full rounded border border-rule px-lg py-sm text-sm text-ink focus:border-accent focus:outline-none"
          aria-label="填空题答案"
        />
      );

    case "short-answer":
      return (
        <textarea
          value={answer?.type === "short-answer" ? answer.text : ""}
          onChange={(e) =>
            onChange({ type: "short-answer", text: e.target.value })
          }
          onBlur={onBlur}
          rows={4}
          placeholder="请输入简答内容"
          className="w-full rounded border border-rule px-lg py-sm text-sm text-ink focus:border-accent focus:outline-none"
          aria-label="简答题答案"
        />
      );

    case "essay":
      return (
        <EssayInput
          question={question}
          answer={answer}
          onChange={onChange}
          onBlur={onBlur}
        />
      );

    default:
      return <div className="text-sm text-ink-muted">不支持的题型</div>;
  }
}

// === 论述题输入组件（支持文本/手写/图片）===

type EssayMode = "text" | "handwriting" | "image";

interface EssayInputProps {
  question: ExamQuestion;
  answer: AnswerInput | null;
  onChange: (answer: AnswerInput) => void;
  onBlur: () => void;
}

function EssayInput({
  question,
  answer,
  onChange,
  onBlur,
}: EssayInputProps): JSX.Element {
  const [mode, setMode] = useState<EssayMode>("text");
  const textValue = answer?.type === "essay" ? answer.text : "";

  const handleTextChange = useCallback(
    (e: React.ChangeEvent<HTMLTextAreaElement>): void => {
      onChange({ type: "essay", text: e.target.value });
    },
    [onChange],
  );

  const handleHandwritingExport = useCallback(
    (dataUrl: string): void => {
      if (!dataUrl) return;
      // 将手写图片以标记形式追加到文本
      const marker = `\n[handwriting:${dataUrl}]\n`;
      onChange({ type: "essay", text: textValue + marker });
    },
    [textValue, onChange],
  );

  const handleImageChange = useCallback(
    (images: string[]): void => {
      // 将图片以标记形式追加到文本
      const markers = images
        .map((img) => `[image:${img}]`)
        .join("\n");
      onChange({ type: "essay", text: markers });
    },
    [onChange],
  );

  const modeTabs: Array<{ key: EssayMode; label: string }> = [
    { key: "text", label: "文本" },
    { key: "handwriting", label: "手写" },
    { key: "image", label: "图片" },
  ];

  return (
    <div className="space-y-sm">
      {/* 模式切换 */}
      <div
        className="flex gap-xs"
        role="tablist"
        aria-label="论述题作答方式"
      >
        {modeTabs.map((tab) => (
          <button
            key={tab.key}
            type="button"
            role="tab"
            aria-selected={mode === tab.key}
            onClick={() => setMode(tab.key)}
            className={cn(
              "px-md py-xs text-xs rounded-sm transition-colors",
              mode === tab.key ? "font-serif" : "",
            )}
            style={{
              background:
                mode === tab.key
                  ? "var(--color-accent)"
                  : "transparent",
              color:
                mode === tab.key
                  ? "var(--color-paper)"
                  : "var(--color-ink-muted)",
              border: "1px solid var(--color-rule)",
            }}
          >
            {tab.label}
          </button>
        ))}
      </div>

      {/* 文本输入 */}
      {mode === "text" && (
        <textarea
          value={textValue}
          onChange={handleTextChange}
          onBlur={onBlur}
          rows={8}
          placeholder="请输入论述内容"
          className="w-full rounded border border-rule px-lg py-sm text-sm text-ink focus:border-accent focus:outline-none"
          aria-label="论述题文本答案"
        />
      )}

      {/* 手写板 */}
      {mode === "handwriting" && (
        <div>
          <HandwritingPad
            onExport={handleHandwritingExport}
            height={280}
            ariaLabel={`${question.id} 手写作答区`}
          />
          <p
            className="mt-xs text-xs"
            style={{ color: "var(--color-ink-muted)" }}
          >
            完成手写后，笔迹将自动作为图片附件追加到答案中。
          </p>
        </div>
      )}

      {/* 图片上传 */}
      {mode === "image" && (
        <div>
          <ImageUpload
            value={[]}
            onChange={handleImageChange}
            maxCount={3}
            ariaLabel={`${question.id} 图片作答上传`}
          />
          <p
            className="mt-xs text-xs"
            style={{ color: "var(--color-ink-muted)" }}
          >
            上传手写答案照片或其他参考图片（最多 3 张）。
          </p>
        </div>
      )}

      {/* 当前文本答案预览 */}
      {textValue && mode !== "text" && (
        <div
          className="mt-sm p-sm rounded border border-rule"
          style={{ background: "var(--color-paper)" }}
        >
          <p
            className="text-xs mb-xs"
            style={{ color: "var(--color-ink-muted)" }}
          >
            当前答案（含附件标记）
          </p>
          <p
            className="text-xs text-clamp-2"
            style={{ color: "var(--color-ink)" }}
          >
            {textValue.replace(/\[(handwriting|image):[^\]]+\]/g, "[附件]")}
          </p>
        </div>
      )}
    </div>
  );
}
