import {
  ArrowRightOutlined,
  BookOutlined,
  BulbOutlined,
  CheckCircleOutlined,
  ClockCircleOutlined,
  DeleteOutlined,
  HomeOutlined,
  PlayCircleOutlined,
  ReloadOutlined,
  StarOutlined,
} from '@ant-design/icons';
import { Button, Empty, Popconfirm, Progress, Select, Space, Switch, Tag, Typography, message } from 'antd';
import { useEffect, useMemo, useState } from 'react';
import './CbtPlayAppView.css';
import { CBT_EXAMS, CBT_QUESTIONS, CBT_QUESTION_SETS, CBT_SUBJECTS, QUICK_QUESTION_COUNTS } from './cbtData';
import type {
  BookmarkItem,
  CbtMode,
  CbtSessionRecord,
  CbtStorageState,
  ProgressStat,
  QuestionRecord,
  SessionAnswer,
  WrongNoteItem,
} from './cbtTypes';

const { Paragraph, Text, Title } = Typography;

const CBT_STORAGE_KEY = 'play-app.cbt.storage.v1';
const MODE_LABELS: Record<CbtMode, string> = {
  sequential: '순차',
  random: '랜덤',
  'wrong-only': '오답만',
  'bookmarks-only': '즐겨찾기만',
};
type CbtSectionKey = 'index' | 'setup' | 'resume' | 'solver' | 'results';
type CbtResultViewKey = 'history' | 'wrong' | 'progress';
type StartOptions = {
  examId: string;
  subjectIds: string[];
  questionSetId: string | null;
  mode: CbtMode;
  questionCount: number;
  difficultyFilter: Array<QuestionRecord['difficulty']>;
  tagFilter: string[];
  excludeRecent: boolean;
  prioritizeWeak: boolean;
  balancedSubjects: boolean;
};

function createInitialStorageState(): CbtStorageState {
  return {
    sessions: [],
    wrongNotes: [],
    bookmarks: [],
    progressStats: [],
    activeSessionId: null,
  };
}

function readStorageState() {
  if (typeof window === 'undefined') {
    return createInitialStorageState();
  }

  const raw = window.localStorage.getItem(CBT_STORAGE_KEY);

  if (!raw) {
    return createInitialStorageState();
  }

  try {
    const parsed = JSON.parse(raw) as Partial<CbtStorageState>;
    return {
      sessions: Array.isArray(parsed.sessions)
        ? parsed.sessions.map((session) => ({
            ...session,
            currentQuestionIndex:
              typeof session?.currentQuestionIndex === 'number' && Number.isFinite(session.currentQuestionIndex)
                ? session.currentQuestionIndex
                : 0,
            revealedExplanationQuestionIds: Array.isArray(session?.revealedExplanationQuestionIds)
              ? session.revealedExplanationQuestionIds.filter((value): value is string => typeof value === 'string')
              : [],
          }))
        : [],
      wrongNotes: Array.isArray(parsed.wrongNotes) ? parsed.wrongNotes : [],
      bookmarks: Array.isArray(parsed.bookmarks) ? parsed.bookmarks : [],
      progressStats: Array.isArray(parsed.progressStats) ? parsed.progressStats : [],
      activeSessionId: typeof parsed.activeSessionId === 'string' ? parsed.activeSessionId : null,
    };
  } catch {
    return createInitialStorageState();
  }
}

function sortQuestionRecords(records: QuestionRecord[], prioritizeWeak: boolean) {
  if (!prioritizeWeak) {
    return [...records];
  }

  return [...records].sort((left, right) => left.correctRate - right.correctRate);
}

function shuffleRecords<T>(items: T[]) {
  const copied = [...items];

  for (let index = copied.length - 1; index > 0; index -= 1) {
    const swapIndex = Math.floor(Math.random() * (index + 1));
    [copied[index], copied[swapIndex]] = [copied[swapIndex], copied[index]];
  }

  return copied;
}

function pickBalancedQuestions(records: QuestionRecord[], subjectIds: string[], maxCount: number) {
  if (subjectIds.length < 2) {
    return records.slice(0, maxCount);
  }

  const buckets = new Map<string, QuestionRecord[]>();
  subjectIds.forEach((subjectId) => {
    buckets.set(
      subjectId,
      records.filter((record) => record.subjectId === subjectId),
    );
  });

  const picked: QuestionRecord[] = [];

  while (picked.length < maxCount) {
    let appendedInRound = false;

    for (const subjectId of subjectIds) {
      const bucket = buckets.get(subjectId) ?? [];

      if (!bucket.length || picked.length >= maxCount) {
        continue;
      }

      picked.push(bucket.shift() as QuestionRecord);
      appendedInRound = true;
    }

    if (!appendedInRound) {
      break;
    }
  }

  return picked;
}

function buildSessionId() {
  return `cbt-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
}

function resolveQuestionLookup(questionId: string) {
  return CBT_QUESTIONS.find((question) => question.id === questionId) ?? null;
}

function createSessionAnswers(questionIds: string[]): SessionAnswer[] {
  return questionIds.map((questionId) => ({
    questionId,
    selectedValue: null,
    isMarked: false,
    isBookmarked: false,
    answeredAt: null,
  }));
}

function formatModeLabel(mode: CbtMode) {
  return MODE_LABELS[mode] ?? mode;
}

function formatDifficultyLabel(value: QuestionRecord['difficulty']) {
  if (value === 'easy') {
    return '쉬움';
  }

  if (value === 'medium') {
    return '보통';
  }

  return '어려움';
}

function formatPercent(value: number) {
  return `${Math.round(value * 100)}%`;
}

function getSubjectQuestionCount(examId: string, subjectId: string) {
  return CBT_QUESTIONS.filter((question) => question.isActive && question.examId === examId && question.subjectId === subjectId).length;
}

function getSubjectQuestionSetCount(examId: string, subjectId: string) {
  return CBT_QUESTION_SETS.filter((questionSet) => questionSet.examId === examId && questionSet.subjectId === subjectId).length;
}

function getAnsweredSessionCount(session: CbtSessionRecord) {
  return session.answers.filter((answer) => Boolean(answer.selectedValue)).length;
}

function rebuildResultState(sessions: CbtSessionRecord[]) {
  const wrongNoteMap = new Map<string, WrongNoteItem>();
  const progressMap = new Map<string, ProgressStat>();

  sessions
    .filter((session) => session.submittedAt)
    .forEach((session) => {
      session.answers.forEach((answer) => {
        if (!answer.selectedValue) {
          return;
        }

        const question = resolveQuestionLookup(answer.questionId);

        if (!question) {
          return;
        }

        const progress = progressMap.get(question.subjectId) ?? {
          subjectId: question.subjectId,
          solvedCount: 0,
          correctCount: 0,
          wrongCount: 0,
          accuracyRate: 0,
        };

        progress.solvedCount += 1;

        if (answer.selectedValue === question.answerValue) {
          progress.correctCount += 1;
        } else {
          progress.wrongCount += 1;
          const existingWrong = wrongNoteMap.get(answer.questionId);
          wrongNoteMap.set(answer.questionId, {
            questionId: answer.questionId,
            createdAt: session.submittedAt ?? session.startedAt,
            subjectId: question.subjectId,
            examId: question.examId,
            wrongCount: (existingWrong?.wrongCount ?? 0) + 1,
            lastSessionId: session.id,
          });
        }

        progress.accuracyRate = progress.solvedCount > 0 ? progress.correctCount / progress.solvedCount : 0;
        progressMap.set(question.subjectId, progress);
      });
    });

  return {
    wrongNotes: [...wrongNoteMap.values()].sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt)),
    progressStats: [...progressMap.values()].sort((left, right) => right.solvedCount - left.solvedCount),
  };
}

export function CbtPlayAppView() {
  const [storageState, setStorageState] = useState<CbtStorageState>(() => readStorageState());
  const [selectedExamId, setSelectedExamId] = useState(CBT_EXAMS[0]?.id ?? '');
  const [selectedSubjectIds, setSelectedSubjectIds] = useState<string[]>([]);
  const [selectedSetId, setSelectedSetId] = useState<string | null>(null);
  const [selectedMode, setSelectedMode] = useState<CbtMode>('sequential');
  const [selectedCount, setSelectedCount] = useState<number>(60);
  const [difficultyFilter, setDifficultyFilter] = useState<Array<QuestionRecord['difficulty']>>([]);
  const [tagFilter, setTagFilter] = useState<string[]>([]);
  const [excludeRecent, setExcludeRecent] = useState(true);
  const [prioritizeWeak, setPrioritizeWeak] = useState(false);
  const [balancedSubjects, setBalancedSubjects] = useState(true);
  const [activeSection, setActiveSection] = useState<CbtSectionKey>('index');
  const [resultView, setResultView] = useState<CbtResultViewKey>('history');
  const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
  const [messageApi, contextHolder] = message.useMessage();

  const subjectOptions = useMemo(
    () => CBT_SUBJECTS.filter((subject) => subject.examId === selectedExamId),
    [selectedExamId],
  );
  const selectedSubjects = useMemo(
    () => (selectedSubjectIds.length ? selectedSubjectIds : subjectOptions.map((subject) => subject.id)),
    [selectedSubjectIds, subjectOptions],
  );
  const selectedSubjectLabels = useMemo(
    () => subjectOptions.filter((subject) => selectedSubjects.includes(subject.id)).map((subject) => subject.label),
    [selectedSubjects, subjectOptions],
  );
  const questionSetOptions = useMemo(
    () => CBT_QUESTION_SETS.filter(
      (questionSet) => questionSet.examId === selectedExamId && selectedSubjects.includes(questionSet.subjectId),
    ),
    [selectedExamId, selectedSubjects],
  );
  const availableTags = useMemo(
    () =>
      Array.from(
        new Set(
          CBT_QUESTIONS.filter((question) => selectedSubjects.includes(question.subjectId)).flatMap((question) => question.tags),
        ),
      ),
    [selectedSubjects],
  );
  const activeSession = useMemo(
    () => storageState.sessions.find((session) => session.id === storageState.activeSessionId) ?? null,
    [storageState.activeSessionId, storageState.sessions],
  );
  const resumableSessions = useMemo(
    () =>
      [...storageState.sessions]
        .filter((session) => !session.submittedAt)
        .sort((left, right) => Date.parse(right.startedAt) - Date.parse(left.startedAt)),
    [storageState.sessions],
  );
  const resumableSession = resumableSessions[0] ?? null;
  const activeQuestions = useMemo(
    () => activeSession?.questionIds.map((questionId) => resolveQuestionLookup(questionId)).filter(Boolean) as QuestionRecord[] ?? [],
    [activeSession],
  );
  const activeAnswer = activeSession?.answers[currentQuestionIndex] ?? null;
  const currentQuestion = activeQuestions[currentQuestionIndex] ?? null;
  const bookmarkQuestionIds = useMemo(() => new Set(storageState.bookmarks.map((item) => item.questionId)), [storageState.bookmarks]);
  const solvedCount = storageState.sessions.length;
  const wrongQuestionCount = storageState.wrongNotes.length;
  const overallAccuracyRate = useMemo(() => {
    const totals = storageState.progressStats.reduce(
      (result, stat) => {
        result.correct += stat.correctCount;
        result.total += stat.solvedCount;
        return result;
      },
      { correct: 0, total: 0 },
    );

    return totals.total > 0 ? totals.correct / totals.total : 0;
  }, [storageState.progressStats]);
  const sessionProgressPercent = activeSession ? Math.round(((currentQuestionIndex + 1) / activeSession.questionIds.length) * 100) : 0;
  const deferredCount = activeSession?.answers.filter((answer) => answer.isMarked).length ?? 0;
  const submittedSession = activeSession?.submittedAt ? activeSession : null;
  const isActiveSolverSession = Boolean(activeSession && currentQuestion && activeAnswer && activeSection === 'solver');
  const recentSessions = useMemo(
    () =>
      [...storageState.sessions]
        .filter((session) => session.submittedAt)
        .sort((left, right) => Date.parse(right.submittedAt ?? right.startedAt) - Date.parse(left.submittedAt ?? left.startedAt)),
    [storageState.sessions],
  );
  const wrongNoteDetails = useMemo(
    () =>
      [...storageState.wrongNotes]
        .sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))
        .map((item) => ({
          item,
          question: resolveQuestionLookup(item.questionId),
        }))
        .filter((entry) => entry.question),
    [storageState.wrongNotes],
  );
  useEffect(() => {
    if (!selectedSubjectIds.length) {
      setSelectedSubjectIds(subjectOptions.map((subject) => subject.id));
    }
  }, [selectedSubjectIds.length, subjectOptions]);

  useEffect(() => {
    if (selectedSetId && !questionSetOptions.some((item) => item.id === selectedSetId)) {
      setSelectedSetId(null);
    }
  }, [questionSetOptions, selectedSetId]);

  useEffect(() => {
    if (typeof window === 'undefined') {
      return;
    }

    window.localStorage.setItem(CBT_STORAGE_KEY, JSON.stringify(storageState));
  }, [storageState]);

  useEffect(() => {
    if (!activeSession) {
      setCurrentQuestionIndex(0);
      setActiveSection((current) => (current === 'solver' ? 'index' : current));
      return;
    }

    const fallbackIndex = activeSession.answers.findIndex((answer) => !answer.selectedValue || answer.isMarked);
    const nextIndex =
      typeof activeSession.currentQuestionIndex === 'number' &&
      activeSession.currentQuestionIndex >= 0 &&
      activeSession.currentQuestionIndex < activeSession.questionIds.length
        ? activeSession.currentQuestionIndex
        : fallbackIndex >= 0
          ? fallbackIndex
          : 0;
    setCurrentQuestionIndex(nextIndex);
  }, [activeSession?.id, activeSession?.currentQuestionIndex]);

  const updateStorageState = (updater: (current: CbtStorageState) => CbtStorageState) => {
    setStorageState((current) => updater(current));
  };

  const moveToQuestionIndex = (nextIndex: number) => {
    setCurrentQuestionIndex(nextIndex);
    updateStorageState((current) => ({
      ...current,
      sessions: current.sessions.map((session) =>
        session.id === current.activeSessionId
          ? {
              ...session,
              currentQuestionIndex: nextIndex,
            }
          : session,
      ),
    }));
  };

  const handleSelectSubjects = (values: string[]) => {
    if (!values.length) {
      setSelectedSubjectIds(subjectOptions[0] ? [subjectOptions[0].id] : []);
      return;
    }

    setSelectedSubjectIds(values);
  };

  const handleStartSubjectSequence = (subjectId: string) => {
    const questionCount = getSubjectQuestionCount(selectedExamId, subjectId);

    setSelectedSubjectIds([subjectId]);
    setSelectedSetId(null);
    setSelectedMode('sequential');
    setSelectedCount(questionCount);
    setDifficultyFilter([]);
    setTagFilter([]);

    startSession({
      examId: selectedExamId,
      subjectIds: [subjectId],
      questionSetId: null,
      mode: 'sequential',
      questionCount,
      difficultyFilter: [],
      tagFilter: [],
      excludeRecent: false,
      prioritizeWeak: false,
      balancedSubjects: false,
    });
  };

  const upsertBookmark = (questionId: string, enabled: boolean) => {
    updateStorageState((current) => {
      const now = new Date().toISOString();
      const nextBookmarks = enabled
        ? current.bookmarks.some((item) => item.questionId === questionId)
          ? current.bookmarks.map((item) => (item.questionId === questionId ? { ...item, updatedAt: now } : item))
          : [...current.bookmarks, { questionId, createdAt: now, updatedAt: now } satisfies BookmarkItem]
        : current.bookmarks.filter((item) => item.questionId !== questionId);
      const nextSessions = current.sessions.map((session) =>
        session.id === current.activeSessionId
          ? {
              ...session,
              answers: session.answers.map((answer) =>
                answer.questionId === questionId ? { ...answer, isBookmarked: enabled } : answer,
              ),
            }
          : session,
      );

      return {
        ...current,
        bookmarks: nextBookmarks,
        sessions: nextSessions,
      };
    });
  };

  const updateActiveAnswer = (questionId: string, updater: (answer: SessionAnswer) => SessionAnswer) => {
    updateStorageState((current) => ({
      ...current,
      sessions: current.sessions.map((session) =>
        session.id === current.activeSessionId
          ? {
              ...session,
              answers: session.answers.map((answer) => (answer.questionId === questionId ? updater(answer) : answer)),
            }
          : session,
      ),
    }));
  };

  const buildQuestionPool = (options: StartOptions) => {
    const recentQuestionIds = new Set(
      storageState.sessions
        .filter((session) => session.submittedAt)
        .slice(-3)
        .flatMap((session) => session.questionIds),
    );
    const wrongQuestionIds = new Set(storageState.wrongNotes.map((item) => item.questionId));
    const bookmarkIds = new Set(storageState.bookmarks.map((item) => item.questionId));
    let filtered = CBT_QUESTIONS.filter((question) => question.isActive && question.examId === options.examId);

    filtered = filtered.filter((question) => options.subjectIds.includes(question.subjectId));

    if (options.questionSetId) {
      filtered = filtered.filter((question) => question.setId === options.questionSetId);
    }

    if (options.difficultyFilter.length) {
      filtered = filtered.filter((question) => options.difficultyFilter.includes(question.difficulty));
    }

    if (options.tagFilter.length) {
      filtered = filtered.filter((question) => question.tags.some((tag) => options.tagFilter.includes(tag)));
    }

    let recentFallbackPool: QuestionRecord[] = [];

    if (options.excludeRecent) {
      recentFallbackPool = filtered.filter((question) => recentQuestionIds.has(question.id));
      filtered = filtered.filter((question) => !recentQuestionIds.has(question.id));
    }

    if (options.mode === 'wrong-only') {
      filtered = filtered.filter((question) => wrongQuestionIds.has(question.id));
    }

    if (options.mode === 'bookmarks-only') {
      filtered = filtered.filter((question) => bookmarkIds.has(question.id));
    }

    filtered = sortQuestionRecords(filtered, options.prioritizeWeak);
    recentFallbackPool = sortQuestionRecords(recentFallbackPool, options.prioritizeWeak);

    if (options.mode === 'random' || options.mode === 'wrong-only' || options.mode === 'bookmarks-only') {
      filtered = shuffleRecords(filtered);
      recentFallbackPool = shuffleRecords(recentFallbackPool);
    }

    if (filtered.length < options.questionCount && recentFallbackPool.length) {
      const seenQuestionIds = new Set(filtered.map((question) => question.id));
      filtered = [
        ...filtered,
        ...recentFallbackPool.filter((question) => {
          if (seenQuestionIds.has(question.id)) {
            return false;
          }

          seenQuestionIds.add(question.id);
          return true;
        }),
      ];
    }

    if (options.balancedSubjects) {
      filtered = pickBalancedQuestions(filtered, options.subjectIds, options.questionCount);
    }

    return filtered.slice(0, options.questionCount);
  };

  const startSession = (overrides?: Partial<StartOptions>) => {
    const options: StartOptions = {
      examId: overrides?.examId ?? selectedExamId,
      subjectIds: overrides?.subjectIds ?? selectedSubjects,
      questionSetId: overrides?.questionSetId ?? selectedSetId,
      mode: overrides?.mode ?? selectedMode,
      questionCount: overrides?.questionCount ?? selectedCount,
      difficultyFilter: overrides?.difficultyFilter ?? difficultyFilter,
      tagFilter: overrides?.tagFilter ?? tagFilter,
      excludeRecent: overrides?.excludeRecent ?? excludeRecent,
      prioritizeWeak: overrides?.prioritizeWeak ?? prioritizeWeak,
      balancedSubjects: overrides?.balancedSubjects ?? balancedSubjects,
    };
    const selectedQuestions = buildQuestionPool(options);

    if (!selectedQuestions.length) {
      void messageApi.warning('조건에 맞는 문제가 없습니다. 필터를 조금 넓혀주세요.');
      return;
    }

    const questionIds = selectedQuestions.map((question) => question.id);
    const sessionId = buildSessionId();
    const nextSession: CbtSessionRecord = {
      id: sessionId,
      examId: options.examId,
      subjectIds: [...options.subjectIds],
      questionSetId: options.questionSetId,
      mode: options.mode,
      questionIds,
      questionCount: questionIds.length,
      startedAt: new Date().toISOString(),
      submittedAt: null,
      score: null,
      correctCount: 0,
      wrongCount: 0,
      currentQuestionIndex: 0,
      revealedExplanationQuestionIds: [],
      answers: createSessionAnswers(questionIds).map((answer) => ({
        ...answer,
        isBookmarked: bookmarkQuestionIds.has(answer.questionId),
      })),
    };

    updateStorageState((current) => ({
      ...current,
      activeSessionId: sessionId,
      sessions: [...current.sessions, nextSession],
    }));
    setCurrentQuestionIndex(0);
    setActiveSection('solver');
    void messageApi.success(`${questionIds.length}문항 세트를 시작했습니다.`);
  };

  const handleSelectChoice = (questionId: string, value: string) => {
    updateActiveAnswer(questionId, (answer) => ({
      ...answer,
      selectedValue: value,
      answeredAt: new Date().toISOString(),
      isMarked: false,
    }));
  };

  const handleResumeSession = (sessionId: string) => {
    updateStorageState((current) => ({
      ...current,
      activeSessionId: sessionId,
    }));
    setActiveSection('solver');
    void messageApi.success('이전 풀이를 이어서 불러왔습니다.');
  };

  const handleToggleDeferred = () => {
    if (!currentQuestion) {
      return;
    }

    updateActiveAnswer(currentQuestion.id, (answer) => ({
      ...answer,
      isMarked: !answer.isMarked,
    }));
  };

  const handleToggleBookmark = () => {
    if (!currentQuestion) {
      return;
    }

    upsertBookmark(currentQuestion.id, !bookmarkQuestionIds.has(currentQuestion.id));
  };

  const handleSubmitSession = () => {
    if (!activeSession) {
      return;
    }

    const evaluatedAnswers = activeSession.answers.map((answer) => {
      const question = resolveQuestionLookup(answer.questionId);
      return {
        ...answer,
        isCorrect: question ? answer.selectedValue === question.answerValue : false,
      };
    });
    const correctCount = evaluatedAnswers.filter((answer) => answer.isCorrect).length;
    const wrongAnswers = evaluatedAnswers.filter((answer) => !answer.isCorrect);
    const score = activeSession.questionIds.length > 0 ? Math.round((correctCount / activeSession.questionIds.length) * 100) : 0;
    const now = new Date().toISOString();

    updateStorageState((current) => {
      const nextWrongNotes = [...current.wrongNotes];
      const nextProgressMap = new Map<string, ProgressStat>(
        current.progressStats.map((stat) => [stat.subjectId, { ...stat }]),
      );

      wrongAnswers.forEach((answer) => {
        const question = resolveQuestionLookup(answer.questionId);

        if (!question) {
          return;
        }

        const existingWrong = nextWrongNotes.find((item) => item.questionId === answer.questionId);

        if (existingWrong) {
          existingWrong.createdAt = now;
          existingWrong.lastSessionId = activeSession.id;
          existingWrong.wrongCount += 1;
        } else {
          nextWrongNotes.push({
            questionId: answer.questionId,
            createdAt: now,
            subjectId: question.subjectId,
            examId: question.examId,
            wrongCount: 1,
            lastSessionId: activeSession.id,
          } satisfies WrongNoteItem);
        }
      });

      activeSession.questionIds.forEach((questionId) => {
        const question = resolveQuestionLookup(questionId);
        const answer = evaluatedAnswers.find((item) => item.questionId === questionId);

        if (!question || !answer) {
          return;
        }

        const existing = nextProgressMap.get(question.subjectId) ?? {
          subjectId: question.subjectId,
          solvedCount: 0,
          correctCount: 0,
          wrongCount: 0,
          accuracyRate: 0,
        };
        existing.solvedCount += 1;
        if (answer.isCorrect) {
          existing.correctCount += 1;
        } else {
          existing.wrongCount += 1;
        }
        existing.accuracyRate = existing.solvedCount > 0 ? existing.correctCount / existing.solvedCount : 0;
        nextProgressMap.set(question.subjectId, existing);
      });

      return {
        ...current,
        activeSessionId: activeSession.id,
        wrongNotes: nextWrongNotes,
        progressStats: Array.from(nextProgressMap.values()),
        sessions: current.sessions.map((session) =>
          session.id === activeSession.id
            ? {
                ...session,
                submittedAt: now,
                score,
                correctCount,
                wrongCount: wrongAnswers.length,
                currentQuestionIndex: 0,
                revealedExplanationQuestionIds: [],
                answers: session.answers.map((answer) => {
                  const evaluated = evaluatedAnswers.find((item) => item.questionId === answer.questionId);
                  return evaluated ?? answer;
                }),
              }
            : session,
        ),
      };
    });
    setCurrentQuestionIndex(0);
    void messageApi.success(`제출 완료: ${score}점`);
  };

  const handleRetrySession = (session: CbtSessionRecord) => {
    setSelectedExamId(session.examId);
    setSelectedSubjectIds(session.subjectIds);
    setSelectedSetId(session.questionSetId);
    setSelectedMode(session.mode);
    setSelectedCount(session.questionCount);
    startSession({
      examId: session.examId,
      subjectIds: session.subjectIds,
      questionSetId: session.questionSetId,
      mode: session.mode,
      questionCount: session.questionCount,
      excludeRecent: false,
    });
  };

  const handleDeleteSessionRecord = (sessionId: string) => {
    updateStorageState((current) => {
      const nextSessions = current.sessions.filter((session) => session.id !== sessionId);
      const nextResultState = rebuildResultState(nextSessions);
      const nextActiveSessionId = current.activeSessionId === sessionId ? nextSessions[0]?.id ?? null : current.activeSessionId;

      return {
        ...current,
        activeSessionId: nextActiveSessionId,
        sessions: nextSessions,
        wrongNotes: nextResultState.wrongNotes,
        progressStats: nextResultState.progressStats,
      };
    });
    void messageApi.success('결과 기록을 삭제했습니다.');
  };

  const handleToggleExplanation = () => {
    if (!currentQuestion) {
      return;
    }

    updateStorageState((current) => ({
      ...current,
      sessions: current.sessions.map((session) => {
        if (session.id !== current.activeSessionId) {
          return session;
        }

        const isVisible = session.revealedExplanationQuestionIds.includes(currentQuestion.id);
        return {
          ...session,
          revealedExplanationQuestionIds: isVisible
            ? session.revealedExplanationQuestionIds.filter((questionId) => questionId !== currentQuestion.id)
            : [...session.revealedExplanationQuestionIds, currentQuestion.id],
        };
      }),
    }));
  };

  const renderHomePanel = () => (
    <div className="cbt-play-app__stack">
      <div className="cbt-play-app__sheet">
        <div className="cbt-play-app__grid">
          <label className="cbt-play-app__field">
            <span className="cbt-play-app__field-label">시험</span>
            <Select
              className="cbt-play-app__select"
              value={selectedExamId}
              options={CBT_EXAMS.map((exam) => ({ value: exam.id, label: exam.label }))}
              onChange={(value) => {
                setSelectedExamId(value);
                setSelectedSubjectIds([]);
                setSelectedSetId(null);
              }}
            />
          </label>
          <label className="cbt-play-app__field">
            <span className="cbt-play-app__field-label">문제집</span>
            <Select
              className="cbt-play-app__select"
              allowClear
              placeholder="전체 문제집"
              value={selectedSetId ?? undefined}
              options={questionSetOptions.map((item) => ({
                value: item.id,
                label: `${item.label} · ${item.sourceLabel}`,
              }))}
              onChange={(value) => {
                setSelectedSetId(value ?? null);
              }}
            />
          </label>
          <label className="cbt-play-app__field cbt-play-app__field--full">
            <span className="cbt-play-app__field-label">과목</span>
            <div className="cbt-play-app__subject-picker">
              <div className="cbt-play-app__subject-actions">
                <Text type="secondary">{selectedSubjectLabels.join(', ')}</Text>
                <Button
                  size="small"
                  type={selectedSubjects.length === subjectOptions.length ? 'primary' : 'default'}
                  onClick={() => setSelectedSubjectIds(subjectOptions.map((subject) => subject.id))}
                >
                  전체 선택
                </Button>
              </div>
              <Select
                className="cbt-play-app__select cbt-play-app__select--multi"
                mode="multiple"
                value={selectedSubjects}
                maxTagCount="responsive"
                optionFilterProp="label"
                options={subjectOptions.map((subject) => ({
                  value: subject.id,
                  label: subject.label,
                }))}
                placeholder="과목을 선택하세요"
                onChange={(value) => {
                  handleSelectSubjects(value);
                }}
              />
            </div>
          </label>
          <label className="cbt-play-app__field cbt-play-app__field--full">
            <span className="cbt-play-app__field-label">문항 수</span>
            <Select
              className="cbt-play-app__select"
              value={selectedCount}
              options={QUICK_QUESTION_COUNTS.map((count) => ({ label: `${count}문항`, value: count }))}
              onChange={(value) => {
                setSelectedCount(Number(value));
              }}
            />
          </label>
          <label className="cbt-play-app__field">
            <span className="cbt-play-app__field-label">난이도</span>
            <Select
              className="cbt-play-app__select cbt-play-app__select--multi"
              mode="multiple"
              allowClear
              value={difficultyFilter}
              options={[
                { value: 'easy', label: '쉬움' },
                { value: 'medium', label: '보통' },
                { value: 'hard', label: '어려움' },
              ]}
              onChange={(value) => {
                setDifficultyFilter(value as Array<QuestionRecord['difficulty']>);
              }}
            />
          </label>
          <label className="cbt-play-app__field">
            <span className="cbt-play-app__field-label">태그</span>
            <Select
              className="cbt-play-app__select cbt-play-app__select--multi"
              mode="multiple"
              allowClear
              value={tagFilter}
              options={availableTags.map((tag) => ({ value: tag, label: tag }))}
              onChange={setTagFilter}
            />
          </label>
          <div className="cbt-play-app__field cbt-play-app__field--full">
            <span className="cbt-play-app__field-label">시작 방식</span>
            <Select
              className="cbt-play-app__select"
              value={selectedMode}
              options={[
                { label: '순차', value: 'sequential' },
                { label: '랜덤', value: 'random' },
                { label: '오답만', value: 'wrong-only' },
                { label: '즐겨찾기만', value: 'bookmarks-only' },
              ]}
              onChange={(value) => {
                setSelectedMode(value as CbtMode);
              }}
            />
          </div>
        </div>
        <div className="cbt-play-app__option-group" style={{ marginTop: 16 }}>
          <div className="cbt-play-app__summary-row">
            <Text strong>새 세션 시작하기</Text>
            <Text type="secondary">이전 세션과 별개로 새 문제 세트를 생성합니다.</Text>
          </div>
          <div className="cbt-play-app__summary-row">
            <Text strong>랜덤 세트 규칙</Text>
            <Tag bordered={false} color="orange">
              중복 제외 기본
            </Tag>
          </div>
          <div className="cbt-play-app__option-line">
            <Switch checked={excludeRecent} onChange={setExcludeRecent} />
            <Text>최근 푼 문제 제외</Text>
          </div>
          <div className="cbt-play-app__option-line">
            <Switch checked={prioritizeWeak} onChange={setPrioritizeWeak} />
            <Text>정답률 낮은 문제 우선</Text>
          </div>
          <div className="cbt-play-app__option-line">
            <Switch checked={balancedSubjects} onChange={setBalancedSubjects} />
            <Text>과목 비율 균등 배치</Text>
          </div>
        </div>
        <div className="cbt-play-app__toolbar" style={{ marginTop: 16 }}>
          <Button type="primary" size="large" onClick={() => startSession()}>
            새 세션 바로 시작
          </Button>
          <Button size="large" icon={<ReloadOutlined />} onClick={() => startSession({ mode: 'random' })}>
            새 랜덤 모의 시작
          </Button>
          <Button size="large" icon={<ClockCircleOutlined />} onClick={() => startSession({ mode: 'wrong-only' })}>
            새 오답 세트 시작
          </Button>
        </div>
      </div>
    </div>
  );

  const renderIndexPanel = () => (
    <div className="cbt-play-app__card-scroll">
      <div className="cbt-play-app__stack">
        <section className="cbt-play-app__hero cbt-play-app__hero--compact">
          <div className="cbt-play-app__summary-row">
            <div>
              <span className="cbt-play-app__eyebrow">play &gt; 일반 &gt; CBT</span>
              <Title level={3} style={{ marginTop: 14, marginBottom: 8 }}>
                인덱스에서 바로 다음 작업을 고르세요
              </Title>
              <Paragraph style={{ marginBottom: 0 }}>
                먼저 새로 진행할지, 저장된 세션을 이어갈지, 결과만 확인할지 고른 뒤 다음 화면으로 이동합니다.
              </Paragraph>
            </div>
            <div className="cbt-play-app__hero-stats">
              <div className="cbt-play-app__metric">
                <span className="cbt-play-app__metric-value">{solvedCount}</span>
                <span className="cbt-play-app__metric-label">누적 세션</span>
              </div>
              <div className="cbt-play-app__metric">
                <span className="cbt-play-app__metric-value">{resumableSessions.length}</span>
                <span className="cbt-play-app__metric-label">이어풀 세션</span>
              </div>
              <div className="cbt-play-app__metric">
                <span className="cbt-play-app__metric-value">{wrongQuestionCount}</span>
                <span className="cbt-play-app__metric-label">오답노트</span>
              </div>
              <div className="cbt-play-app__metric">
                <span className="cbt-play-app__metric-value">{Math.round(overallAccuracyRate * 100)}%</span>
                <span className="cbt-play-app__metric-label">전체 정답률</span>
              </div>
            </div>
          </div>
        </section>

        <section className="cbt-play-app__action-grid">
          <button
            type="button"
            className="cbt-play-app__action-card cbt-play-app__action-card--primary"
            onClick={() => setActiveSection('setup')}
          >
            <div className="cbt-play-app__action-card-copy">
              <Text strong>새로 진행</Text>
              <Text type="secondary">과목, 비공식 문제집, 모드, 문항 수를 고르고 새 세션을 시작합니다.</Text>
            </div>
            <Tag>{selectedCount}문항 기본</Tag>
          </button>

          <button
            type="button"
            className="cbt-play-app__action-card"
            onClick={() => {
              setActiveSection('resume');
            }}
          >
            <div className="cbt-play-app__action-card-copy">
              <Text strong>기존 세션 이어서</Text>
              <Text type="secondary">
                {resumableSession
                  ? `${Math.max(resumableSession.questionCount - getAnsweredSessionCount(resumableSession), 0)}문항 남은 세션부터 확인하고 이어서 진행합니다.`
                  : '저장된 이어풀 세션이 있는지 먼저 확인합니다.'}
              </Text>
            </div>
            <Tag color={resumableSession ? 'gold' : 'default'}>
              {resumableSession ? `${resumableSessions.length}개 대기` : '없음'}
            </Tag>
          </button>

          <button
            type="button"
            className="cbt-play-app__action-card"
            onClick={() => {
              setActiveSection('results');
              setResultView('history');
            }}
          >
            <div className="cbt-play-app__action-card-copy">
              <Text strong>결과물 보기</Text>
              <Text type="secondary">최근 기록, 오답노트, 성취도를 한 화면에서 확인합니다.</Text>
            </div>
            <Tag color="blue">{recentSessions.length}개 기록</Tag>
          </button>
        </section>

        <section className="cbt-play-app__panel">
          <div className="cbt-play-app__summary-row">
            <Text strong>현재 과목 구성</Text>
            <Text type="secondary">과목을 고르면 해당 과목 문항만 순차 세션으로 바로 들어갑니다.</Text>
          </div>
          <div className="cbt-play-app__subject-grid">
            {subjectOptions.map((subject) => (
              <div key={subject.id} className="cbt-play-app__subject-stat">
                <div className="cbt-play-app__subject-stat-head">
                  <Text strong>{subject.label}</Text>
                </div>
                <Paragraph type="secondary" style={{ marginBottom: 0 }}>
                  {getSubjectQuestionCount(selectedExamId, subject.id)}문항 · {getSubjectQuestionSetCount(selectedExamId, subject.id)}문제집
                </Paragraph>
                <Button
                  type="primary"
                  block
                  className="cbt-play-app__subject-start"
                  onClick={() => {
                    handleStartSubjectSequence(subject.id);
                  }}
                >
                  이 과목 순차 확인
                </Button>
              </div>
            ))}
          </div>
        </section>
      </div>
    </div>
  );

  const renderResumePanel = () => (
    <div className="cbt-play-app__card-scroll">
      <div className="cbt-play-app__stack">
        <section className="cbt-play-app__panel cbt-play-app__panel--section-head">
          <div className="cbt-play-app__summary-row">
            <div>
              <Text strong>기존 세션 이어서</Text>
              <div className="cbt-play-app__resume-meta">
                <Text type="secondary">저장된 진행 중 세션을 고른 뒤 바로 문제 풀이로 이동합니다.</Text>
              </div>
            </div>
            <div className="cbt-play-app__section-actions">
              <Button icon={<HomeOutlined />} onClick={() => setActiveSection('index')}>
                인덱스
              </Button>
              <Button type="primary" icon={<PlayCircleOutlined />} onClick={() => setActiveSection('setup')}>
                새 세션 설정
              </Button>
            </div>
          </div>
        </section>

        {resumableSessions.length ? (
          <section className="cbt-play-app__panel">
            <div className="cbt-play-app__resume-list">
              {resumableSessions.map((session) => {
                const answeredSessionCount = getAnsweredSessionCount(session);
                const isActiveResumeSession = storageState.activeSessionId === session.id;
                return (
                  <div key={session.id} className="cbt-play-app__resume-card">
                    <div className="cbt-play-app__summary-row">
                      <Space wrap size={8}>
                        <Tag color="blue">{formatModeLabel(session.mode)}</Tag>
                        <Tag>{session.questionCount}문항</Tag>
                        <Tag>{answeredSessionCount}개 응답</Tag>
                        {isActiveResumeSession ? <Tag color="gold">현재 선택</Tag> : null}
                      </Space>
                      <Button type="primary" onClick={() => handleResumeSession(session.id)}>
                        {isActiveResumeSession ? '이 세션 계속 풀기' : '이 세션 이어하기'}
                      </Button>
                    </div>
                    <div className="cbt-play-app__resume-meta">
                      <Text type="secondary">
                        {new Date(session.startedAt).toLocaleString()} 시작 · {Math.max(session.questionCount - answeredSessionCount, 0)}문항 남음
                      </Text>
                    </div>
                  </div>
                );
              })}
            </div>
          </section>
        ) : (
          <section className="cbt-play-app__panel">
            <div className="cbt-play-app__empty">
              <Empty
                description="이어서 진행할 세션이 없습니다."
                image={Empty.PRESENTED_IMAGE_SIMPLE}
              >
                <Button type="primary" onClick={() => setActiveSection('setup')}>
                  새 세션 설정으로 이동
                </Button>
              </Empty>
            </div>
          </section>
        )}
      </div>
    </div>
  );

  const renderQuestionPanel = () => {
    if (!activeSession || !currentQuestion || !activeAnswer) {
      return renderHomePanel();
    }

    const isSubmitted = Boolean(activeSession.submittedAt);
    const isCorrect = activeAnswer.selectedValue === currentQuestion.answerValue;
    const isExplanationVisible = activeSession.revealedExplanationQuestionIds.includes(currentQuestion.id);

    return (
      <div className="cbt-play-app__stack cbt-play-app__stack--fill">
        <div className="cbt-play-app__solver-scroll">
          <div className="cbt-play-app__panel">
            <div className="cbt-play-app__summary-row">
              <Text strong>{currentQuestionIndex + 1} / {activeSession.questionIds.length}</Text>
              <div className="cbt-play-app__solver-actions">
                <Space wrap size={8}>
                  {isSubmitted ? <Tag color="blue">{submittedSession?.score ?? 0}점</Tag> : null}
                  {!isSubmitted && deferredCount > 0 ? <Tag>{deferredCount}보류</Tag> : null}
                  <Tag color={isSubmitted ? 'default' : 'blue'}>{formatModeLabel(activeSession.mode)}</Tag>
                </Space>
                <Space wrap size={8}>
                  <Button size="small" icon={<BulbOutlined />} onClick={handleToggleExplanation}>
                    {isExplanationVisible ? '힌트 숨기기' : '힌트 보기'}
                  </Button>
                  <Button size="small" icon={<HomeOutlined />} onClick={() => setActiveSection('index')}>
                    홈으로
                  </Button>
                </Space>
              </div>
            </div>
            <Progress percent={sessionProgressPercent} strokeColor="#3d7cf1" trailColor="rgba(61, 124, 241, 0.12)" />
          </div>
          <div className="cbt-play-app__question">
            <div className="cbt-play-app__question-head">
              <div className="cbt-play-app__meta-strip">
                <Tag color="gold">{formatDifficultyLabel(currentQuestion.difficulty)}</Tag>
                <Tag>{formatPercent(currentQuestion.correctRate)}</Tag>
                {currentQuestion.tags.map((tag) => (
                  <Tag key={tag}>{tag}</Tag>
                ))}
              </div>
              <div className="cbt-play-app__question-body">{currentQuestion.body}</div>
            </div>
            <div className="cbt-play-app__choice-list">
              {currentQuestion.choices.map((choice, index) => (
                <Button
                  key={choice.value}
                  type={activeAnswer.selectedValue === choice.value ? 'primary' : 'default'}
                  className="cbt-play-app__choice-button"
                  onClick={() => {
                    if (!isSubmitted) {
                      handleSelectChoice(currentQuestion.id, choice.value);
                    }
                  }}
                >
                  <span className="cbt-play-app__choice-prefix">{index + 1}.</span>
                  <span>{choice.label}</span>
                </Button>
              ))}
            </div>
            {isExplanationVisible || isSubmitted ? (
              <div className="cbt-play-app__question-feedback">
                <Space direction="vertical" size={8} style={{ width: '100%' }}>
                  <div className="cbt-play-app__summary-row">
                    <Text strong>{isSubmitted ? (isCorrect ? '정답입니다.' : '오답입니다.') : '힌트'}</Text>
                    <Button size="small" onClick={handleToggleExplanation}>
                      {isExplanationVisible ? '힌트 숨기기' : '힌트 보기'}
                    </Button>
                  </div>
                  {isSubmitted ? <Text>내 답: {activeAnswer.selectedValue ?? '미응답'}</Text> : null}
                  {isSubmitted ? <Text>정답: {currentQuestion.answerValue}</Text> : null}
                  {isExplanationVisible ? <Text type="secondary">{currentQuestion.explanation}</Text> : null}
                </Space>
              </div>
            ) : null}
          </div>
        </div>
        <div className="cbt-play-app__sticky-bar">
          <div className="cbt-play-app__sticky-inner">
            <Button
              onClick={() => {
                moveToQuestionIndex(Math.max(currentQuestionIndex - 1, 0));
              }}
              disabled={currentQuestionIndex <= 0}
            >
              이전
            </Button>
            <Button onClick={handleToggleDeferred} type={activeAnswer.isMarked ? 'primary' : 'default'}>
              {activeAnswer.isMarked ? '보류 해제' : '보류'}
            </Button>
            <Button onClick={handleToggleBookmark} type={bookmarkQuestionIds.has(currentQuestion.id) ? 'primary' : 'default'}>
              {bookmarkQuestionIds.has(currentQuestion.id) ? '즐겨찾기 해제' : '즐겨찾기'}
            </Button>
            {isSubmitted ? (
              currentQuestionIndex >= activeQuestions.length - 1 ? (
                <Button
                  type="primary"
                  icon={<ReloadOutlined />}
                  onClick={() => {
                    if (!submittedSession) {
                      return;
                    }
                    handleRetrySession(submittedSession);
                  }}
                >
                  다시 풀기
                </Button>
              ) : (
                <Button
                  type="primary"
                  onClick={() => {
                    moveToQuestionIndex(Math.min(currentQuestionIndex + 1, activeQuestions.length - 1));
                  }}
                >
                  다음 해설
                </Button>
              )
            ) : currentQuestionIndex >= activeQuestions.length - 1 ? (
              <Button type="primary" onClick={handleSubmitSession}>
                제출
              </Button>
            ) : (
              <Button
                type="primary"
                onClick={() => {
                  moveToQuestionIndex(Math.min(currentQuestionIndex + 1, activeQuestions.length - 1));
                }}
              >
                다음
              </Button>
            )}
          </div>
        </div>
      </div>
    );
  };

  const renderWrongNotesPanel = () =>
    wrongNoteDetails.length ? (
      <div className="cbt-play-app__card-scroll">
        <div className="cbt-play-app__stack">
          {wrongNoteDetails.map(({ item, question }) => (
            <div key={item.questionId} className="cbt-play-app__wrong-card">
              <Space direction="vertical" size={10} style={{ width: '100%' }}>
                <div className="cbt-play-app__summary-row">
                  <Space wrap size={8}>
                    <Tag color="error">오답 {item.wrongCount}회</Tag>
                    <Tag>{formatDifficultyLabel(question?.difficulty ?? 'easy')}</Tag>
                    {(question?.tags ?? []).map((tag) => (
                      <Tag key={`${item.questionId}-${tag}`}>{tag}</Tag>
                    ))}
                  </Space>
                  <Button size="small" onClick={() => upsertBookmark(item.questionId, !bookmarkQuestionIds.has(item.questionId))}>
                    {bookmarkQuestionIds.has(item.questionId) ? '즐겨찾기 해제' : '즐겨찾기'}
                  </Button>
                </div>
                <Text strong>{question?.body}</Text>
                <Text type="secondary">{question?.explanation}</Text>
              </Space>
            </div>
          ))}
          <div className="cbt-play-app__toolbar">
            <Button type="primary" icon={<ReloadOutlined />} onClick={() => startSession({ mode: 'wrong-only' })}>
              오답 세트 다시 시작
            </Button>
          </div>
        </div>
      </div>
    ) : (
      <div className="cbt-play-app__empty cbt-play-app__empty--fill">
        <Empty description="아직 저장된 오답노트가 없습니다." />
      </div>
    );

  const renderHistoryPanel = () =>
    recentSessions.length ? (
      <div className="cbt-play-app__card-scroll">
        <div className="cbt-play-app__stack">
          {recentSessions.map((session) => (
            <div key={session.id} className="cbt-play-app__history-card">
              <div className="cbt-play-app__summary-row">
                <Space wrap size={8}>
                  <Tag icon={<CheckCircleOutlined />}>{session.score ?? 0}점</Tag>
                  <Tag icon={<BookOutlined />}>{session.questionCount}문항</Tag>
                  <Tag icon={<StarOutlined />}>{formatModeLabel(session.mode)}</Tag>
                </Space>
                <Space wrap size={8}>
                  <Button size="small" onClick={() => handleRetrySession(session)}>
                    재응시
                  </Button>
                  <Popconfirm
                    title="이 결과를 삭제할까요?"
                    description="오답노트와 성취도 집계도 함께 다시 계산됩니다."
                    okText="삭제"
                    cancelText="취소"
                    okButtonProps={{ danger: true }}
                    onConfirm={() => handleDeleteSessionRecord(session.id)}
                  >
                    <Button size="small" danger icon={<DeleteOutlined />}>
                      삭제
                    </Button>
                  </Popconfirm>
                </Space>
              </div>
              <div className="cbt-play-app__history-meta" style={{ marginTop: 10 }}>
                <Text type="secondary">{new Date(session.submittedAt ?? session.startedAt).toLocaleString()}</Text>
                <Text type="secondary">정답 {session.correctCount} / 오답 {session.wrongCount}</Text>
              </div>
            </div>
          ))}
        </div>
      </div>
    ) : (
      <div className="cbt-play-app__empty cbt-play-app__empty--fill">
        <Empty description="아직 제출된 풀이 기록이 없습니다." />
      </div>
    );

  const renderProgressPanel = () => (
    <div className="cbt-play-app__card-scroll">
      <div className="cbt-play-app__stack">
        <div className="cbt-play-app__summary-row">
          <Text strong>과목별 성취도</Text>
          <Text type="secondary">풀이 누적 후 정답률이 갱신됩니다.</Text>
        </div>
        {storageState.progressStats.length ? (
          <div className="cbt-play-app__subject-grid">
            {storageState.progressStats.map((stat) => {
              const subject = CBT_SUBJECTS.find((item) => item.id === stat.subjectId);
              return (
                <div key={stat.subjectId} className="cbt-play-app__subject-stat">
                  <div className="cbt-play-app__subject-stat-head">
                    <Text strong>{subject?.label ?? stat.subjectId}</Text>
                    <Text className="cbt-play-app__subject-stat-rate">{Math.round(stat.accuracyRate * 100)}%</Text>
                  </div>
                  <Paragraph type="secondary" style={{ marginBottom: 0 }}>
                    풀이 {stat.solvedCount} / 정답 {stat.correctCount} / 오답 {stat.wrongCount}
                  </Paragraph>
                </div>
              );
            })}
          </div>
        ) : (
          <div className="cbt-play-app__empty">
            <Empty description="풀이 누적 후 과목별 성취도가 표시됩니다." />
          </div>
        )}
      </div>
    </div>
  );

  const renderResultsPanel = () => (
    <div className="cbt-play-app__card-scroll">
      <div className="cbt-play-app__stack cbt-play-app__stack--results">
        <section className="cbt-play-app__panel">
          <div className="cbt-play-app__summary-row">
            <div>
              <Text strong>결과물</Text>
              <div className="cbt-play-app__resume-meta">
                <Text type="secondary">최근 풀이 기록과 누적 결과를 여기서만 모아 봅니다.</Text>
              </div>
            </div>
            <div className="cbt-play-app__section-actions">
              <Button icon={<HomeOutlined />} onClick={() => setActiveSection('index')}>
                인덱스
              </Button>
              {activeSession ? (
                <Button onClick={() => setActiveSection('solver')}>
                  현재 세션
                </Button>
              ) : null}
            </div>
          </div>
          <div className="cbt-play-app__result-switch">
            <Button type={resultView === 'history' ? 'primary' : 'default'} onClick={() => setResultView('history')}>
              결과기록
            </Button>
            <Button type={resultView === 'wrong' ? 'primary' : 'default'} onClick={() => setResultView('wrong')}>
              오답노트
            </Button>
            <Button type={resultView === 'progress' ? 'primary' : 'default'} onClick={() => setResultView('progress')}>
              성취도
            </Button>
          </div>
        </section>
        {resultView === 'history' ? renderHistoryPanel() : null}
        {resultView === 'wrong' ? renderWrongNotesPanel() : null}
        {resultView === 'progress' ? renderProgressPanel() : null}
      </div>
    </div>
  );

  return (
    <div className={`cbt-play-app${isActiveSolverSession ? ' cbt-play-app--immersive' : ''}`} data-focus-id="play-cbt">
      {contextHolder}
      <div className="cbt-play-app__viewport-shell">
        <div className="cbt-play-app__content-card">
          {activeSection === 'index' ? renderIndexPanel() : null}
          {activeSection === 'setup' ? (
            <div className="cbt-play-app__card-scroll">
              <div className="cbt-play-app__stack">
                <section className="cbt-play-app__panel cbt-play-app__panel--section-head">
                  <div className="cbt-play-app__summary-row">
                    <div>
                      <Text strong>새 세션 설정</Text>
                      <div className="cbt-play-app__resume-meta">
                        <Text type="secondary">인덱스에서 새로 진행을 선택한 뒤, 여기서 세션 조건을 정하고 시작합니다.</Text>
                      </div>
                    </div>
                    <div className="cbt-play-app__section-actions">
                      <Button icon={<HomeOutlined />} onClick={() => setActiveSection('index')}>
                        인덱스
                      </Button>
                      {resumableSessions.length ? (
                        <Button icon={<ArrowRightOutlined />} onClick={() => setActiveSection('resume')}>
                          세션 이어가기
                        </Button>
                      ) : null}
                    </div>
                  </div>
                </section>
                {renderHomePanel()}
              </div>
            </div>
          ) : null}
          {activeSection === 'resume' ? renderResumePanel() : null}
          {activeSection === 'solver' ? renderQuestionPanel() : null}
          {activeSection === 'results' ? renderResultsPanel() : null}
        </div>
      </div>
    </div>
  );
}
