import { ArrowLeftOutlined } from '@ant-design/icons';
import { App, Button, Card, Checkbox, Empty, Input, List, Select, Space, Spin, Tag, Typography } from 'antd';
import { useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { fetchServerCommands } from '../../features/serverCommand/api';
import type { ServerCommandItem } from '../../features/serverCommand/types';
import { CodexDiffBlock, parseCodexDiffSections } from '../../components/previewer';
import { fetchChatSourceChanges } from './mainChatPanel';
import { ChatPromptCard } from './mainChatPanel/ChatPromptCard';
import { extractChatMessageParts } from './mainChatPanel/messageParts';
import type { ChatSourceChangeSnapshot } from './mainChatPanel/types';
import { buildChatPath } from './routes';
import { stashCodexLiveDraft } from './codexLiveDraftBridge';

const { Paragraph, Text, Title } = Typography;

type DeploymentFilterValue = 'all' | 'pre-deploy' | 'deployed';
type CurrentSourceStatus = 'applied' | 'not-applied';
type ReviewFilterValue = 'all' | 'reviewed' | 'not-reviewed';

type SourceChangeEntry = ChatSourceChangeSnapshot & {
  deploymentStatus: DeploymentFilterValue;
  currentSourceStatus: CurrentSourceStatus;
};

type VerificationFeatureGroup = {
  key: string;
  label: string;
  entries: SourceChangeEntry[];
};

const DEPLOYMENT_FILTER_OPTIONS: Array<{ label: string; value: DeploymentFilterValue }> = [
  { label: '전체', value: 'all' },
  { label: '배포 전', value: 'pre-deploy' },
  { label: '배포됨', value: 'deployed' },
];

const REVIEW_FILTER_OPTIONS: Array<{ label: string; value: ReviewFilterValue }> = [
  { label: '전체', value: 'all' },
  { label: '검수 완료', value: 'reviewed' },
  { label: '미검수', value: 'not-reviewed' },
];

function formatDateTime(value: string | null | undefined) {
  if (!value) {
    return '미기록';
  }

  return new Date(value).toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' });
}

function getTimeValue(value: string | null | undefined) {
  const parsed = new Date(value ?? '').getTime();
  return Number.isFinite(parsed) ? parsed : 0;
}

function resolveDeploymentStatus(
  updatedAt: string,
  latestTestServerBuiltAt: string | null,
  testServerCommand: Pick<ServerCommandItem, 'buildRequired' | 'updateAvailable'> | null,
): DeploymentFilterValue {
  if (testServerCommand && !testServerCommand.buildRequired && !testServerCommand.updateAvailable) {
    return 'deployed';
  }

  if (!latestTestServerBuiltAt) {
    return 'pre-deploy';
  }

  return getTimeValue(updatedAt) > getTimeValue(latestTestServerBuiltAt) ? 'pre-deploy' : 'deployed';
}

function getDeploymentStatusLabel(value: DeploymentFilterValue) {
  if (value === 'deployed') {
    return 'test 배포됨';
  }

  if (value === 'pre-deploy') {
    return 'test 배포 전';
  }

  return '전체';
}

function hasMeaningfulSourceArtifacts(entry: Pick<ChatSourceChangeSnapshot, 'changedFiles' | 'currentSourceFiles' | 'diffBlocks'>) {
  return [entry.changedFiles, entry.currentSourceFiles, entry.diffBlocks].some((items) =>
    items.some((item) => item.trim().length > 0),
  );
}

function combineCodexDiffBlocks(diffBlocks: string[]) {
  const normalizedBlocks = diffBlocks.map((block) => block.trim()).filter(Boolean);
  const diffText = normalizedBlocks.join('\n\n');
  const fileCount = normalizedBlocks.reduce((count, block) => count + parseCodexDiffSections(block).length, 0);

  return {
    diffText,
    fileCount,
  };
}

function buildFeatureKeyCandidate(value: string) {
  return value
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9가-힣]+/g, '-')
    .replace(/^-+|-+$/g, '')
    .slice(0, 40);
}

function resolveEntryFeatureGroup(entry: SourceChangeEntry) {
  const firstFeatureTag = entry.featureTags.find((tag) => tag.trim().length > 0);

  if (firstFeatureTag) {
    return {
      key: buildFeatureKeyCandidate(firstFeatureTag) || `feature-${entry.id}`,
      label: firstFeatureTag,
    };
  }

  const firstPath = [...entry.currentSourceFiles, ...entry.changedFiles].find((file) => file.trim().length > 0) ?? '';
  const firstSegment = firstPath.split('/').filter(Boolean).slice(0, 2).join('/');

  if (firstSegment) {
    return {
      key: buildFeatureKeyCandidate(firstSegment) || `feature-${entry.id}`,
      label: firstSegment,
    };
  }

  return {
    key: buildFeatureKeyCandidate(entry.requestTitle) || `feature-${entry.id}`,
    label: entry.requestTitle,
  };
}

function groupEntriesForVerification(entries: SourceChangeEntry[]) {
  const groups = new Map<string, VerificationFeatureGroup>();

  entries.forEach((entry) => {
    const feature = resolveEntryFeatureGroup(entry);
    const existing = groups.get(feature.key);

    if (existing) {
      existing.entries.push(entry);
      return;
    }

    groups.set(feature.key, {
      key: feature.key,
      label: feature.label,
      entries: [entry],
    });
  });

  return Array.from(groups.values()).sort((left, right) => left.label.localeCompare(right.label, 'ko-KR'));
}

function createVerificationDraft(groups: VerificationFeatureGroup[]) {
  const promptPayload = {
    title: '기능별 검증 결과',
    description: '실제 확인이 끝난 기능만 체크했습니다.',
    multiple: true,
    readOnly: true,
    selectedValues: [] as string[],
    resultText: '검증 완료 기능만 체크했습니다.',
    options: groups.map((group) => ({
      value: group.key,
      label: group.label,
      description: `${group.entries.length}건 · ${group.entries
        .map((entry) => entry.requestTitle.trim())
        .filter(Boolean)
        .slice(0, 3)
        .join(' / ')}`,
    })),
  };
  const metadata = {
    version: 1,
    features: groups.map((group) => ({
      key: group.key,
      label: group.label,
      entryRefs: group.entries.map((entry) => ({
        sessionId: entry.sessionId,
        requestId: entry.requestId,
      })),
    })),
  };

  return [
    '선택한 변경 이력을 기능별로 묶어 정상 동작 검증을 진행해 주세요.',
    '',
    '처리 기준',
    '- 기능별로 직접 확인 가능한 동작만 검증 완료로 판단합니다.',
    '- 오류가 보이면 먼저 수정하고 다시 확인합니다.',
    '- 최종 답변에는 아래 형식의 readOnly prompt를 정확히 1개 포함합니다.',
    '- `selectedValues`에는 실제 검증 완료 기능만 넣고, 실패하거나 검증하지 못한 기능은 체크하지 않습니다.',
    '- prompt의 `options` 배열은 아래 템플릿의 값을 유지하고, description은 실제 검증 결과에 맞게 짧게 보정해도 됩니다.',
    '- 최종 검증 스크린샷이 있으면 `[[preview:...]]` 줄을 함께 남깁니다.',
    '',
    '검증 대상 기능',
    ...groups.map((group, index) =>
      `${index + 1}. ${group.label}: ${group.entries
        .map((entry) => `${entry.requestTitle} (${entry.changedFiles.slice(0, 2).join(', ') || '파일 정보 없음'})`)
        .join(' / ')}`,
    ),
    '',
    '응답용 prompt 템플릿',
    `[[prompt:${JSON.stringify(promptPayload)}]]`,
    '',
    `[[source-change-verification:${JSON.stringify(metadata)}]]`,
  ].join('\n');
}

export function ChatSourceChangesPage() {
  const navigate = useNavigate();
  const { message: messageApi } = App.useApp();
  const [entries, setEntries] = useState<SourceChangeEntry[]>([]);
  const [selectedEntryId, setSelectedEntryId] = useState<string | null>(null);
  const [selectedRequestEntryIds, setSelectedRequestEntryIds] = useState<string[]>([]);
  const [searchText, setSearchText] = useState('');
  const [deploymentSearchCondition, setDeploymentSearchCondition] = useState<DeploymentFilterValue>('pre-deploy');
  const [reviewSearchCondition, setReviewSearchCondition] = useState<ReviewFilterValue>('not-reviewed');
  const [latestTestServerBuiltAt, setLatestTestServerBuiltAt] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const [loadingProgress, setLoadingProgress] = useState({ completed: 0, total: 0 });
  const [errorMessage, setErrorMessage] = useState<string | null>(null);
  const [isMobileViewport, setIsMobileViewport] = useState(false);
  const [mobileView, setMobileView] = useState<'list' | 'detail'>('list');

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

    const mediaQuery = window.matchMedia('(max-width: 960px)');
    const update = () => {
      setIsMobileViewport(mediaQuery.matches);
      if (!mediaQuery.matches) {
        setMobileView('list');
      }
    };

    update();
    mediaQuery.addEventListener('change', update);

    return () => {
      mediaQuery.removeEventListener('change', update);
    };
  }, []);

  useEffect(() => {
    let cancelled = false;

    const loadChanges = async () => {
      setLoading(true);
      setLoadingProgress({ completed: 0, total: 0 });
      setErrorMessage(null);
      setEntries([]);
      setSelectedEntryId(null);

      try {
        let testServerCommand: ServerCommandItem | null = null;
        let nextLatestTestServerBuiltAt: string | null = null;

        try {
          const serverCommands = await fetchServerCommands();
          testServerCommand = serverCommands.find((item) => item.key === 'test') ?? null;
          nextLatestTestServerBuiltAt = testServerCommand?.runningBuiltAt ?? testServerCommand?.startedAt ?? null;
        } catch {
          testServerCommand = null;
          nextLatestTestServerBuiltAt = null;
        }

        const sourceChanges = await fetchChatSourceChanges(300);

        if (cancelled) {
          return;
        }

        setLatestTestServerBuiltAt(nextLatestTestServerBuiltAt);
        setLoadingProgress({ completed: sourceChanges.length, total: sourceChanges.length });

        const nextEntries = sourceChanges
          .filter((entry) => entry.hasSourceChanges && hasMeaningfulSourceArtifacts(entry))
          .map((entry) => ({
            ...entry,
            deploymentStatus: entry.currentSourceFiles.length > 0
              ? resolveDeploymentStatus(entry.sourceChangedAt, nextLatestTestServerBuiltAt, testServerCommand)
              : 'pre-deploy' as const,
            currentSourceStatus: entry.currentSourceFiles.length > 0 ? 'applied' as const : 'not-applied' as const,
          }))
          .sort((left, right) => getTimeValue(right.updatedAt) - getTimeValue(left.updatedAt));

        setEntries(nextEntries);
        setSelectedEntryId((previous) => {
          if (previous && nextEntries.some((entry) => entry.id === previous)) {
            return previous;
          }

          return nextEntries[0]?.id ?? null;
        });
        setSelectedRequestEntryIds((previous) =>
          previous.filter((entryId) => nextEntries.some((entry) => entry.id === entryId && entry.sourceChangeKind === 'request')),
        );
      } catch (error) {
        if (!cancelled) {
          setErrorMessage(error instanceof Error ? error.message : 'Codex Live 변경 이력을 불러오지 못했습니다.');
        }
      } finally {
        if (!cancelled) {
          setLoading(false);
        }
      }
    };

    void loadChanges();

    return () => {
      cancelled = true;
    };
  }, []);

  const filteredEntries = useMemo(() => {
    const keyword = searchText.trim().toLowerCase();

    return entries.filter((entry) => {
      if (deploymentSearchCondition !== 'all' && entry.deploymentStatus !== deploymentSearchCondition) {
        return false;
      }

      if (reviewSearchCondition !== 'all' && entry.reviewStatus !== reviewSearchCondition) {
        return false;
      }

      if (!keyword) {
        return true;
      }

      return [
        entry.conversationTitle,
        entry.requestTitle,
        entry.questionText,
        entry.answerText,
        ...entry.featureTags,
        ...entry.changedFiles,
        ...entry.currentSourceFiles,
      ]
        .join(' ')
        .toLowerCase()
        .includes(keyword);
    });
  }, [deploymentSearchCondition, entries, reviewSearchCondition, searchText]);

  const selectedEntry = filteredEntries.find((entry) => entry.id === selectedEntryId) ?? filteredEntries[0] ?? null;
  const selectedEntryIndex = selectedEntry ? filteredEntries.findIndex((entry) => entry.id === selectedEntry.id) : -1;
  const previousEntry = selectedEntryIndex > 0 ? filteredEntries[selectedEntryIndex - 1] : null;
  const nextEntry = selectedEntryIndex >= 0 && selectedEntryIndex < filteredEntries.length - 1
    ? filteredEntries[selectedEntryIndex + 1]
    : null;
  const showListPane = !isMobileViewport || mobileView === 'list';
  const showDetailPane = !isMobileViewport || mobileView === 'detail';
  const hasPartialEntries = entries.length > 0;
  const isInitialLoading = loading && !hasPartialEntries;
  const selectedRequestEntries = useMemo(
    () => entries.filter((entry) => selectedRequestEntryIds.includes(entry.id) && entry.sourceChangeKind === 'request'),
    [entries, selectedRequestEntryIds],
  );
  const selectedVerificationGroups = useMemo(
    () => groupEntriesForVerification(selectedRequestEntries),
    [selectedRequestEntries],
  );
  const selectedAnswerRender = useMemo(() => {
    if (!selectedEntry) {
      return {
        visibleText: '',
        prompts: [],
      };
    }

    const extracted = extractChatMessageParts(selectedEntry.answerText || '');

    return {
      visibleText: extracted.strippedText.trim(),
      prompts: extracted.parts.filter((part): part is Extract<(typeof extracted.parts)[number], { type: 'prompt' }> => part.type === 'prompt'),
    };
  }, [selectedEntry]);

  const openConversationFromEntry = (entry: ChatSourceChangeSnapshot) => {
    const searchParams = new URLSearchParams();
    searchParams.set('sessionId', entry.sessionId);
    navigate({
      pathname: buildChatPath('live'),
      search: `?${searchParams.toString()}`,
    });
  };

  const selectEntryById = (entryId: string) => {
    setSelectedEntryId(entryId);
    if (isMobileViewport) {
      setMobileView('detail');
    }
  };

  const toggleRequestSelection = (entry: SourceChangeEntry) => {
    if (entry.sourceChangeKind !== 'request') {
      return;
    }

    setSelectedRequestEntryIds((previous) =>
      previous.includes(entry.id) ? previous.filter((entryId) => entryId !== entry.id) : [...previous, entry.id],
    );
  };

  const startVerificationRequest = () => {
    if (selectedVerificationGroups.length === 0) {
      messageApi.warning('검증 요청할 변경 이력을 먼저 선택해 주세요.');
      return;
    }

    const draftText = createVerificationDraft(selectedVerificationGroups);
    const stored = stashCodexLiveDraft({
      text: draftText,
      source: 'chat-source-changes',
      createdAt: new Date().toISOString(),
      autoSend: true,
      sendMode: 'queue',
    });

    if (!stored) {
      messageApi.error('검증 요청 초안을 만들지 못했습니다.');
      return;
    }

    navigate({
      pathname: buildChatPath('live'),
    });
  };

  const renderSearchControls = (compact = false) => (
    <Space direction="vertical" size={compact ? 6 : 8} style={{ width: '100%' }}>
      {!compact ? (
        <Title level={4} className="chat-source-changes-page__title">
          Codex Live 변경 이력
        </Title>
      ) : null}
      <Input
        value={searchText}
        placeholder="채팅방명, 요청명, 기능, 파일 경로 검색"
        onChange={(event) => {
          setSearchText(event.target.value);
        }}
      />
      <Space direction="vertical" size={compact ? 2 : 4}>
        <div className={`chat-source-changes-page__filters${compact ? ' is-compact' : ''}`}>
          <Text type="secondary" className="chat-source-changes-page__filters-label">
            검수 관련 조건
          </Text>
          <Select
            value={deploymentSearchCondition}
            options={DEPLOYMENT_FILTER_OPTIONS}
            onChange={(value) => {
              setDeploymentSearchCondition(value);
            }}
          />
          <Select
            value={reviewSearchCondition}
            options={REVIEW_FILTER_OPTIONS}
            onChange={(value) => {
              setReviewSearchCondition(value);
            }}
          />
        </div>
        <div className={`chat-source-changes-page__selection-bar${compact ? ' is-compact' : ''}`}>
          <Text type="secondary">
            선택된 요청 {selectedRequestEntries.length}건
          </Text>
          <Space size={8} wrap>
            <Button type="primary" disabled={selectedRequestEntries.length === 0} onClick={startVerificationRequest}>
              검증 요청
            </Button>
            <Button disabled={selectedRequestEntries.length === 0} onClick={() => setSelectedRequestEntryIds([])}>
              선택 해제
            </Button>
          </Space>
        </div>
        <Text type="secondary" className={`chat-source-changes-page__status-copy${compact ? ' is-compact' : ''}`}>
          {latestTestServerBuiltAt
            ? `현재 TEST 서버 빌드 시각 기준: ${formatDateTime(latestTestServerBuiltAt)}`
            : 'TEST 서버 빌드 시각을 읽지 못해 현재 항목은 모두 배포 전으로 표시됩니다.'}
        </Text>
        {loading ? (
          <Space size={8} className={`chat-source-changes-page__loading-inline${compact ? ' is-compact' : ''}`}>
            <Spin size="small" />
            <Text type="secondary">
              변경 이력을 조회 중입니다.
              {loadingProgress.total > 0 ? ` (${loadingProgress.completed}/${loadingProgress.total})` : ''}
            </Text>
          </Space>
        ) : null}
      </Space>
    </Space>
  );

  return (
    <Space
      direction="vertical"
      size={isMobileViewport ? 8 : 16}
      className={`chat-source-changes-page${isMobileViewport ? ' is-mobile' : ''}`}
    >
      {!isMobileViewport ? (
        <Card className="chat-source-changes-page__card chat-source-changes-page__toolbar-card" bordered={false}>
          {renderSearchControls(false)}
        </Card>
      ) : !isInitialLoading ? (
        <Card className="chat-source-changes-page__card chat-source-changes-page__toolbar-card" bordered={false}>
          <div className="chat-source-changes-page__mobile-toolbar">{renderSearchControls(true)}</div>
        </Card>
      ) : null}

      {isInitialLoading ? (
        <Card className="chat-source-changes-page__card" bordered={false}>
          <div className="chat-source-changes-page__loading">
            <Spin />
          </div>
        </Card>
      ) : errorMessage ? (
        <Card className="chat-source-changes-page__card" bordered={false}>
          <Text type="danger">{errorMessage}</Text>
        </Card>
      ) : filteredEntries.length === 0 ? (
        <Card className="chat-source-changes-page__card" bordered={false}>
          <Empty
            description={
              entries.length === 0
                ? '소스 수정 흔적이 있는 Codex Live 요청이 아직 없습니다.'
                : '현재 검색어 또는 검색조건에 맞는 변경 이력이 없습니다.'
            }
          />
        </Card>
      ) : (
        <div className={`chat-source-changes-page__grid${isMobileViewport ? ' is-mobile' : ''}`}>
          <Card
            className={`chat-source-changes-page__card chat-source-changes-page__list-card${showListPane ? '' : ' is-hidden'}`}
            bordered={false}
          >
            <List
              dataSource={filteredEntries}
              renderItem={(entry) => {
                const isSelectable = entry.sourceChangeKind === 'request';
                const isChecked = selectedRequestEntryIds.includes(entry.id);

                return (
                  <List.Item
                    key={entry.id}
                    className={entry.id === selectedEntry?.id ? 'chat-source-changes-page__list-item is-active' : 'chat-source-changes-page__list-item'}
                    onClick={() => {
                      selectEntryById(entry.id);
                    }}
                  >
                    <Space align="start" size={10} style={{ width: '100%' }}>
                      <Checkbox
                        checked={isChecked}
                        disabled={!isSelectable}
                        onClick={(event) => {
                          event.stopPropagation();
                        }}
                        onChange={() => {
                          toggleRequestSelection(entry);
                        }}
                      />
                      <Space direction="vertical" size={6} style={{ width: '100%' }}>
                        <Space size={8} wrap className="chat-source-changes-page__list-head">
                          <Text strong className="chat-source-changes-page__list-title">
                            {entry.conversationTitle}
                          </Text>
                          <Tag color={entry.status === 'failed' ? 'error' : 'blue'}>{entry.status}</Tag>
                          <Tag color={entry.reviewStatus === 'reviewed' ? 'gold' : 'default'}>
                            {entry.reviewStatus === 'reviewed' ? '검수 완료' : '미검수'}
                          </Tag>
                          <Tag color={entry.sourceChangeKind === 'verification-group' ? 'purple' : 'default'}>
                            {entry.sourceChangeKind === 'verification-group' ? '기능별 검증 이력' : '원본 변경 이력'}
                          </Tag>
                          {entry.conversationDeletedAt ? <Tag color="default">채팅방 삭제됨</Tag> : null}
                          <Tag color={entry.currentSourceStatus === 'applied' ? 'cyan' : 'default'}>
                            {entry.currentSourceStatus === 'applied' ? '현재 소스 적용' : '현재 소스 미적용'}
                          </Tag>
                          <Tag color={entry.deploymentStatus === 'deployed' ? 'green' : 'orange'}>
                            {getDeploymentStatusLabel(entry.deploymentStatus)}
                          </Tag>
                        </Space>
                        <Text>{entry.requestTitle}</Text>
                        <Text type="secondary">{formatDateTime(entry.updatedAt)}</Text>
                        <Space size={[6, 6]} wrap>
                          {entry.featureTags.map((tag) => (
                            <Tag key={`${entry.id}:${tag}`}>{tag}</Tag>
                          ))}
                        </Space>
                      </Space>
                    </Space>
                  </List.Item>
                );
              }}
            />
          </Card>

          <Card
            className={`chat-source-changes-page__card chat-source-changes-page__detail-card${showDetailPane ? '' : ' is-hidden'}`}
            extra={
              isMobileViewport && showDetailPane ? (
                <Space size={8} className="chat-source-changes-page__detail-card-actions">
                  <Space size={8} wrap className="chat-source-changes-page__detail-nav is-card-extra">
                    <Text type="secondary" className="chat-source-changes-page__detail-nav-index">
                      {selectedEntryIndex >= 0 ? `${selectedEntryIndex + 1} / ${filteredEntries.length}` : ''}
                    </Text>
                    <Button
                      size="small"
                      disabled={!previousEntry}
                      onClick={() => {
                        if (previousEntry) {
                          selectEntryById(previousEntry.id);
                        }
                      }}
                    >
                      이전
                    </Button>
                    <Button
                      size="small"
                      disabled={!nextEntry}
                      onClick={() => {
                        if (nextEntry) {
                          selectEntryById(nextEntry.id);
                        }
                      }}
                    >
                      다음
                    </Button>
                  </Space>
                  <Button icon={<ArrowLeftOutlined />} onClick={() => setMobileView('list')}>
                    목록
                  </Button>
                </Space>
              ) : null
            }
            bordered={false}
          >
            {selectedEntry ? (
              <Space direction="vertical" size={16} className="chat-source-changes-page__detail">
                <Space direction="vertical" size={4}>
                  <div className="chat-source-changes-page__detail-header">
                    <Space direction="vertical" size={4} className="chat-source-changes-page__detail-heading">
                      <Title level={5} className="chat-source-changes-page__detail-title">
                        {selectedEntry.requestTitle}
                      </Title>
                      <Text type="secondary">
                        {selectedEntry.conversationTitle} · 변경 반영 시각 {formatDateTime(selectedEntry.sourceChangedAt)}
                      </Text>
                    </Space>
                    {!isMobileViewport ? (
                      <Space size={8} wrap className="chat-source-changes-page__detail-nav">
                        <Button
                          size="small"
                          type="primary"
                          disabled={Boolean(selectedEntry.conversationDeletedAt)}
                          onClick={() => {
                            openConversationFromEntry(selectedEntry);
                          }}
                        >
                          {selectedEntry.conversationDeletedAt ? '삭제된 채팅방' : '원본 채팅방 열기'}
                        </Button>
                        {selectedEntry.sourceChangeKind === 'request' ? (
                          <Button
                            size="small"
                            onClick={() => {
                              toggleRequestSelection(selectedEntry);
                            }}
                          >
                            {selectedRequestEntryIds.includes(selectedEntry.id) ? '선택 해제' : '검증 대상에 추가'}
                          </Button>
                        ) : null}
                        <Text type="secondary" className="chat-source-changes-page__detail-nav-index">
                          {selectedEntryIndex >= 0 ? `${selectedEntryIndex + 1} / ${filteredEntries.length}` : ''}
                        </Text>
                        <Button
                          size="small"
                          disabled={!previousEntry}
                          onClick={() => {
                            if (previousEntry) {
                              selectEntryById(previousEntry.id);
                            }
                          }}
                        >
                          이전
                        </Button>
                        <Button
                          size="small"
                          disabled={!nextEntry}
                          onClick={() => {
                            if (nextEntry) {
                              selectEntryById(nextEntry.id);
                            }
                          }}
                        >
                          다음
                        </Button>
                      </Space>
                    ) : null}
                  </div>
                </Space>

                {isMobileViewport ? (
                  <Space direction="vertical" size={8} style={{ width: '100%' }}>
                    <Button
                      type="primary"
                      disabled={Boolean(selectedEntry.conversationDeletedAt)}
                      onClick={() => {
                        openConversationFromEntry(selectedEntry);
                      }}
                    >
                      {selectedEntry.conversationDeletedAt ? '삭제된 채팅방' : '원본 채팅방 열기'}
                    </Button>
                    {selectedEntry.sourceChangeKind === 'request' ? (
                      <Button
                        onClick={() => {
                          toggleRequestSelection(selectedEntry);
                        }}
                      >
                        {selectedRequestEntryIds.includes(selectedEntry.id) ? '선택 해제' : '검증 대상에 추가'}
                      </Button>
                    ) : null}
                  </Space>
                ) : null}

                <Space size={[8, 8]} wrap>
                  <Tag color={selectedEntry.status === 'failed' ? 'error' : 'blue'}>{selectedEntry.status}</Tag>
                  <Tag color={selectedEntry.reviewStatus === 'reviewed' ? 'gold' : 'default'}>
                    {selectedEntry.reviewStatus === 'reviewed' ? '검수 완료' : '미검수'}
                  </Tag>
                  <Tag color={selectedEntry.sourceChangeKind === 'verification-group' ? 'purple' : 'default'}>
                    {selectedEntry.sourceChangeKind === 'verification-group' ? '기능별 검증 이력' : '원본 변경 이력'}
                  </Tag>
                  {selectedEntry.conversationDeletedAt ? <Tag color="default">채팅방 삭제됨</Tag> : null}
                  <Tag color={selectedEntry.currentSourceStatus === 'applied' ? 'cyan' : 'default'}>
                    {selectedEntry.currentSourceStatus === 'applied' ? '현재 소스 적용' : '현재 소스 미적용'}
                  </Tag>
                  <Tag color={selectedEntry.deploymentStatus === 'deployed' ? 'green' : 'orange'}>
                    {getDeploymentStatusLabel(selectedEntry.deploymentStatus)}
                  </Tag>
                  {selectedEntry.featureTags.map((tag) => (
                    <Tag key={`detail:${selectedEntry.id}:${tag}`}>{tag}</Tag>
                  ))}
                </Space>

                <Card size="small" title="검수 상태">
                  <Space direction="vertical" size={4}>
                    <Text strong>{selectedEntry.reviewStatus === 'reviewed' ? '검수 완료' : '미검수'}</Text>
                    {selectedEntry.sourceEntryIds.length > 0 ? (
                      <Text type="secondary">
                        묶인 원본 변경 이력 {selectedEntry.sourceEntryIds.length}건
                      </Text>
                    ) : (
                      <Text type="secondary">아직 기능별 검증 결과로 재정리되지 않은 원본 변경 이력입니다.</Text>
                    )}
                  </Space>
                </Card>

                <Card size="small" title="질문">
                  <Paragraph style={{ whiteSpace: 'pre-wrap', marginBottom: 0 }}>
                    {selectedEntry.questionText || '기록된 질문이 없습니다.'}
                  </Paragraph>
                </Card>

                <Card size="small" title="답변">
                  <Space direction="vertical" size={12} style={{ width: '100%' }}>
                    {selectedAnswerRender.visibleText ? (
                      <Paragraph style={{ whiteSpace: 'pre-wrap', marginBottom: 0 }}>
                        {selectedAnswerRender.visibleText}
                      </Paragraph>
                    ) : null}
                    {selectedAnswerRender.prompts.map((prompt, index) => (
                      <ChatPromptCard
                        key={`${selectedEntry.id}-answer-prompt-${index}-${prompt.title}`}
                        target={prompt}
                        readOnly
                        onSubmit={async () => false}
                      />
                    ))}
                    {!selectedAnswerRender.visibleText && selectedAnswerRender.prompts.length === 0 ? (
                      <Text type="secondary">기록된 답변이 없습니다.</Text>
                    ) : null}
                  </Space>
                </Card>

                <Card size="small" title={`변경 파일 (${selectedEntry.changedFiles.length})`}>
                  {selectedEntry.changedFiles.length ? (
                    <Space size={[8, 8]} wrap>
                      {selectedEntry.changedFiles.map((file) => (
                        <Tag key={`file:${selectedEntry.id}:${file}`}>{file}</Tag>
                      ))}
                    </Space>
                  ) : (
                    <Text type="secondary">추출된 변경 파일이 없습니다.</Text>
                  )}
                </Card>

                <Card size="small" title={`현재 소스 반영 파일 (${selectedEntry.currentSourceFiles.length})`}>
                  {selectedEntry.currentSourceFiles.length ? (
                    <Space size={[8, 8]} wrap>
                      {selectedEntry.currentSourceFiles.map((file) => (
                        <Tag key={`current-file:${selectedEntry.id}:${file}`} color="cyan">
                          {file}
                        </Tag>
                      ))}
                    </Space>
                  ) : (
                    <Text type="secondary">현재 소스 기준으로 바로 연결되는 파일은 아직 추출되지 않았습니다.</Text>
                  )}
                </Card>

                <Card size="small" title={`변경 소스 / diff (${selectedEntry.diffBlocks.length})`}>
                  {(() => {
                    if (!selectedEntry.diffBlocks.length) {
                      return <Text type="secondary">추출된 diff 블록이 없습니다. 설명과 파일 목록만 확인할 수 있습니다.</Text>;
                    }

                    const combinedDiff = combineCodexDiffBlocks(selectedEntry.diffBlocks);
                    return (
                      <CodexDiffBlock
                        diffText={combinedDiff.diffText}
                        summary={`파일 ${combinedDiff.fileCount}개 diff preview`}
                      />
                    );
                  })()}
                </Card>
              </Space>
            ) : (
              <Empty description="선택된 변경 이력이 없습니다." />
            )}
          </Card>
        </div>
      )}
    </Space>
  );
}
