import {
  AppstoreOutlined,
  ArrowLeftOutlined,
  CheckOutlined,
  CloseOutlined,
  ControlOutlined,
  CopyOutlined,
  DeleteOutlined,
  DisconnectOutlined,
  DownloadOutlined,
  DownOutlined,
  ExclamationOutlined,
  ExclamationCircleOutlined,
  FilterOutlined,
  FullscreenExitOutlined,
  FullscreenOutlined,
  LeftOutlined,
  RightOutlined,
  NodeIndexOutlined,
  PaperClipOutlined,
  PlusOutlined,
  ProfileOutlined,
  RedoOutlined,
  SendOutlined,
  ShareAltOutlined,
  SettingOutlined,
  MinusOutlined,
  SyncOutlined,
  ThunderboltOutlined,
  UpOutlined,
} from '@ant-design/icons';
import { Alert, Button, Checkbox, Dropdown, Input, Modal, Segmented, Select, Spin, Tag, message } from 'antd';
import type { TextAreaRef } from 'antd/es/input/TextArea';
import type { MenuProps } from 'antd';
import {
  Fragment,
  memo,
  useEffect,
  useMemo,
  useRef,
  useState,
  type CSSProperties,
  type ChangeEvent,
  type ClipboardEvent,
  type ReactNode,
  type RefObject,
  type TouchEvent,
  type UIEvent,
} from 'react';
import { createPortal } from 'react-dom';
import { InlineImage } from '../../../components/common/InlineImage';
import { CodexDiffBlock, parseCodexDiffSections } from '../../../components/previewer';
import type { ComposerFilePickResult } from '../chatV2/hooks/useConversationComposerController';
import {
  ChatPreviewBody,
  resolveChatPreviewGlyph,
  resolveChatPreviewKindLabel,
  type ChatPreviewKind,
} from './ChatPreviewBody';
import { triggerResourceDownload } from './downloadUtils';
import { extractHiddenPreviewUrls, stripHiddenPreviewTags } from './previewMarkers';
import { normalizeChatResourceUrl } from './chatResourceUrl';
import { copyPreviewContent, copyText, isExecutionFailureMessage, isMissingRequestMessage, sharePreviewLink } from './chatUtils';
import { ChatLinkCardPreview } from './ChatLinkCardPreview';
import { normalizeCodexModel } from '../codexModelOptions';
import { ChatActivityChecklist, buildChatActivityChecklistEntries } from './ChatActivityChecklist';
import { describeExecutorCommand } from './executorActivitySummary';
import { buildComposerFilePickKey } from './composerFilePickKey';
import { ChatPromptCard, buildPromptTargetSignature, type PromptDraftSelection, type PromptSubmitPayload } from './ChatPromptCard';
import { ChatStructuredPreviewCard } from './ChatStructuredPreviewCard';
import { openChatExternalLink } from './linkNavigation';
import { classifyPreviewKind } from './previewKind';
import { isPromptResolved } from './promptState';
import { ChatRankedLinkPreview, type RankedLinkPreviewTarget } from './ChatRankedLinkPreview';
import { extractAttachmentPreviewUrls, extractChatMessageParts } from './messageParts';
import type {
  ChatComposerAttachment,
  ChatConversationRequest,
  ChatConversationRequestStatus,
  ChatMessage,
  ChatMessagePart,
} from './types';

const KST_TIME_ZONE = 'Asia/Seoul';
const KST_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
const KST_DATE_TIME_FORMATTER = new Intl.DateTimeFormat('sv-SE', {
  timeZone: KST_TIME_ZONE,
  year: 'numeric',
  month: '2-digit',
  day: '2-digit',
  hour: '2-digit',
  minute: '2-digit',
  second: '2-digit',
  hour12: false,
});
type ChatTypeOption = {
  value: string;
  label: string;
  description: string;
  disabled?: boolean;
};

type CodexModelOption = {
  value: string;
  label: string;
  description: string;
};

type ChatConversationRequestWithModel = ChatConversationRequest & {
  codexModel?: string | null;
};

function resolveRequestModelTokenUsage(request: ChatConversationRequest) {
  return Math.max(
    0,
    Math.round(
      Number(
        request.usageSnapshot?.tokenTotals?.total ?? request.usageSnapshot?.totalTokens ?? request.totalTokens ?? 0,
      ) || 0,
    ),
  );
}

function resolveRequestCodexModel(request: ChatConversationRequest) {
  const requestWithModel = request as ChatConversationRequestWithModel;
  return normalizeCodexModel(requestWithModel.codexModel?.trim() || '');
}

type PreviewOption = {
  id: string;
  label: string;
  url: string;
  kind: string;
};

type QueuedRequestOption = {
  requestId: string;
  order: number;
  text: string;
};

type ComposerAssistAction = {
  key: string;
  label: string;
  description?: string | null;
  resolveText: () => string | Promise<string>;
};

function resolveComposerAssistIcon(actionKey: string) {
  if (actionKey === 'menu') {
    return <AppstoreOutlined />;
  }

  if (actionKey === 'focus') {
    return <ControlOutlined />;
  }

  if (actionKey === 'selection') {
    return <NodeIndexOutlined />;
  }

  if (actionKey === 'errors') {
    return <ExclamationOutlined />;
  }

  return <ProfileOutlined />;
}

function buildComposerAssistDropdownLabel(title?: string | null) {
  const normalizedTitle = title?.trim();
  return normalizedTitle ? `${normalizedTitle} 작업환경 구성` : '작업환경 구성';
}

type InlinePreviewKind = ChatPreviewKind;

type InlinePreviewTarget = {
  url: string;
  label: string;
  kind: InlinePreviewKind;
};

type OpenPreviewTarget =
  | string
  | {
      id: string;
      label: string;
      url: string;
      kind: InlinePreviewKind;
      source?: 'message' | 'context';
    };

type PendingComposerUpload = {
  attemptId: string;
  key: string;
  name: string;
  status: 'uploading' | 'failed';
  reason?: string;
};

type ComposerSendResult = 'sent' | 'pending' | 'blocked';

type SystemExecutionFilter = 'all' | 'active' | 'attention' | 'active-attention';
type SystemExecutionSort = 'latest' | 'answered' | 'status';

type PendingPromptSelection = PromptDraftSelection & {
  status: 'draft' | 'submitted';
  promptTitle: string;
  target: Extract<ChatMessagePart, { type: 'prompt' }>;
  requestId: string | null;
};

type StoredPromptSelection = Pick<
  PendingPromptSelection,
  'selectedValues' | 'freeText' | 'stepSelections' | 'summaryText' | 'status' | 'promptTitle' | 'requestId'
>;

type SystemExecutionDisplayRequest = {
  request: ChatConversationRequest;
  depth: number;
};

type SystemExecutionDisplayMode = 'expanded' | 'collapsed' | 'hidden';
type RoomShareExpandMode = 'latest' | 'pending' | 'all';

type SystemExecutionAttentionState = {
  activityLines: string[];
  promptTargets: Extract<ChatMessagePart, { type: 'prompt' }>[];
  promptSubmittedCount: number;
  isPromptManuallyCompleted: boolean;
  hasVerificationTarget: boolean;
  hasConfirmedVerificationTarget: boolean;
  isVerificationManuallyCompleted: boolean;
  hasPendingPromptBadge: boolean;
  hasPendingVerificationBadge: boolean;
  hasOwnAttentionState: boolean;
};

type SystemExecutionActivityOverview = {
  lines: string[];
  planTitle: string | null;
  executors: Array<{
    key: string;
    title: string;
    line: string;
    focus: string;
    command: string | null;
    commandSummary: string | null;
    updateCount: number;
  }>;
};

const ROOM_SHARE_EXPAND_MODE_LABELS: Record<RoomShareExpandMode, string> = {
  latest: '마지막건',
  pending: '처리중·미확인',
  all: '전체',
};

function summarizeRoomShareActivityLines(lines: string[]) {
  const normalizedLines = lines.map((line) => line.trim()).filter(Boolean);

  if (normalizedLines.length === 0) {
    return [] as string[];
  }

  const summaries: string[] = [];
  const pushUnique = (label: string) => {
    if (!summaries.includes(label)) {
      summaries.push(label);
    }
  };

  normalizedLines.forEach((line) => {
    const lower = line.toLowerCase();

    if (/(read|search|inspect|context|analysis|analy|조사|확인|정리|검토|조회)/.test(lower)) {
      pushUnique('요청 내용을 확인하고 필요한 정보를 정리하고 있어요.');
    }

    if (/(edit|patch|write|implement|fix|update|modify|반영|수정|작성)/.test(lower)) {
      pushUnique('답변에 반영할 내용을 만들고 있어요.');
    }

    if (/(test|build|verify|check|validate|검증|점검|실행)/.test(lower)) {
      pushUnique('결과를 점검하고 있어요.');
    }

    if (/(file|resource|upload|preview|첨부|파일)/.test(lower)) {
      pushUnique('참고 자료와 첨부 내용을 함께 살펴보고 있어요.');
    }
  });

  if (summaries.length > 0) {
    return summaries.slice(0, 3);
  }

  const latestLine = normalizedLines[normalizedLines.length - 1]
    ?.replace(/[\`*_>#-]+/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();

  return latestLine ? [`현재 작업을 진행하고 있어요. `] : ['현재 작업을 진행하고 있어요.'];
}


type SystemExecutionBadgeTone =
  | 'queued'
  | 'started'
  | 'completed'
  | 'failed'
  | 'cancelled'
  | 'neutral'
  | 'attention'
  | 'prompt'
  | 'unread';

function buildPendingPromptSelectionKey(
  messageId: number,
  promptIndex: number,
  targetTitle: string,
  target: Extract<ChatMessagePart, { type: 'prompt' }>,
) {
  return `${messageId}:${promptIndex}:${targetTitle}:${buildPromptTargetSignature(target)}`;
}

function buildPromptSelectionStorageKey(sessionId: string) {
  const normalizedSessionId = sessionId.trim();
  return normalizedSessionId ? `main-chat-panel:prompt-selections:${normalizedSessionId}` : '';
}

function readStoredPromptSelections(sessionId: string) {
  if (typeof window === 'undefined') {
    return {} as Record<string, StoredPromptSelection>;
  }

  const storageKey = buildPromptSelectionStorageKey(sessionId);

  if (!storageKey) {
    return {} as Record<string, StoredPromptSelection>;
  }

  try {
    const rawValue = window.localStorage.getItem(storageKey);

    if (!rawValue) {
      return {} as Record<string, StoredPromptSelection>;
    }

    const parsed = JSON.parse(rawValue);

    if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
      return {} as Record<string, StoredPromptSelection>;
    }

    return Object.fromEntries(
      Object.entries(parsed as Record<string, unknown>).flatMap(([key, value]) => {
        if (!value || typeof value !== 'object' || Array.isArray(value)) {
          return [];
        }

        const record = value as Record<string, unknown>;
        const status = record.status === 'submitted' || record.status === 'draft' ? record.status : null;

        if (!status) {
          return [];
        }

        const selectedValues = Array.isArray(record.selectedValues)
          ? record.selectedValues.map((item) => String(item ?? '').trim()).filter(Boolean)
          : [];
        const stepSelections = Array.isArray(record.stepSelections)
          ? record.stepSelections.flatMap((item) => {
              if (!item || typeof item !== 'object' || Array.isArray(item)) {
                return [];
              }

              const stepRecord = item as Record<string, unknown>;
              const stepKey = String(stepRecord.stepKey ?? '').trim();
              const stepTitle = String(stepRecord.stepTitle ?? '').trim();

              if (!stepKey || !stepTitle) {
                return [];
              }

              return [{
                stepKey,
                stepTitle,
                selectedValues: Array.isArray(stepRecord.selectedValues)
                  ? stepRecord.selectedValues.map((entry) => String(entry ?? '').trim()).filter(Boolean)
                  : [],
                freeText: String(stepRecord.freeText ?? ''),
                skipped: stepRecord.skipped === true,
              }];
            })
          : undefined;

        return [[
          key,
          {
            status,
            selectedValues,
            freeText: String(record.freeText ?? ''),
            stepSelections,
            summaryText: String(record.summaryText ?? '').trim() || null,
            promptTitle: String(record.promptTitle ?? '').trim(),
            requestId: String(record.requestId ?? '').trim() || null,
          } satisfies StoredPromptSelection,
        ]];
      }),
    );
  } catch {
    return {} as Record<string, StoredPromptSelection>;
  }
}

function writeStoredPromptSelections(
  sessionId: string,
  selections: Record<string, PendingPromptSelection>,
) {
  if (typeof window === 'undefined') {
    return;
  }

  const storageKey = buildPromptSelectionStorageKey(sessionId);

  if (!storageKey) {
    return;
  }

  const nextValue = Object.fromEntries(
    Object.entries(selections)
      .map(([key, selection]) => [
        key,
        {
          status: selection.status,
          selectedValues: selection.selectedValues,
          freeText: selection.freeText,
          stepSelections: selection.stepSelections,
          summaryText: selection.summaryText ?? null,
          promptTitle: selection.promptTitle,
          requestId: selection.requestId,
        } satisfies StoredPromptSelection,
      ]),
  );

  try {
    if (Object.keys(nextValue).length === 0) {
      window.localStorage.removeItem(storageKey);
      return;
    }

    window.localStorage.setItem(storageKey, JSON.stringify(nextValue));
  } catch {
    // Ignore storage quota and serialization failures. Prompt state should remain usable in-memory.
  }
}

type SystemExecutionBadge = {
  label: string;
  shortLabel: string;
  tone: SystemExecutionBadgeTone;
};

type SystemExecutionBadgeDisplay = SystemExecutionBadge & {
  hideOnMobile?: boolean;
};

type AggregatedRequestStatusSummary = {
  label: string | null;
  tone: SystemExecutionBadgeTone;
};

type SystemExecutionFilterOption = {
  value: SystemExecutionFilter;
  label: string;
  compactLabel: string;
  ariaLabel: string;
};

type ConversationMessageEntry =
  | {
      kind: 'single';
      key: string;
      message: ChatMessage;
    }
  | {
      kind: 'group';
      key: string;
      groupId: string;
      request: ChatConversationRequest | undefined;
      requestIds: string[];
      messages: ChatMessage[];
    };

type PreviewFetchError = Error & {
  status?: number;
};

const MARKDOWN_IMAGE_LINE_PATTERN = /^\s*!\[([^\]]*)\]\(([^)]+)\)\s*$/;
const MARKDOWN_LINK_PATTERN = /\[([^\]]+)\]\(([^)]+)\)/g;
const CHAT_ACTIVITY_MESSAGE_PREFIX = '[[activity-log]]';
const CHAT_MISSING_REQUEST_MESSAGE_PREFIX = '[[missing-request]]';
const CHAT_EXECUTION_FAILURE_MESSAGE_PREFIX = '[[execution-failure]]';
const VERIFICATION_REQUEST_PATTERN = /(검증|확인|점검|테스트|캡처|스크린샷|실화면|재확인)/iu;
const COLLAPSIBLE_MESSAGE_LINE_COUNT = 6;
const COLLAPSIBLE_MESSAGE_CHAR_COUNT = 280;
const DIFF_CODE_BLOCK_PATTERN = /```diff[^\n]*\n([\s\S]*?)```/g;
const IMMEDIATE_SEND_TOGGLE_HOLD_MS = 2000;
const SYSTEM_EXECUTION_JUMP_MAX_RETRIES = 4;
const MOBILE_SYSTEM_EXECUTION_AUTO_HIDE_MAX_WIDTH = 767;
const DEFAULT_QUEUE_SUMMARY_MAX_LENGTH = 32;
const TABLET_SYSTEM_EXECUTION_SUMMARY_MAX_LENGTH = 88;
const DESKTOP_SYSTEM_EXECUTION_SUMMARY_MAX_LENGTH = 120;

type MessageRenderPayload = {
  previewSourceText: string;
  visibleText: string;
  diffBlocks: string[];
  rankedLinkTargets: RankedLinkPreviewTarget[];
  linkCardTargets: Extract<ChatMessagePart, { type: 'link_card' }>[];
  previewCardTargets: Extract<ChatMessagePart, { type: 'preview_card' }>[];
  promptTargets: Extract<ChatMessagePart, { type: 'prompt' }>[];
};

const RANK_LINE_PATTERN = /(?:\b(?:rank|score)\b|랭크|점수)\s*[:=]?\s*[-+]?\d+(?:\.\d+)?(?:e[-+]?\d+)?\b/i;
const TITLE_VALUE_PATTERN = /^(?:[-*]\s*)?(?:\d+\.\s*)?(?:title|제목)\s*[:=-]\s*(.+)$/i;
const LINK_VALUE_PATTERN = /^(?:[-*]\s*)?(?:\d+\.\s*)?(?:link|url|href|링크)\s*[:=-]\s*(https?:\/\/\S+|\/\S+)$/i;

function normalizeInlinePreviewUrl(value: string) {
  return normalizeChatResourceUrl(value);
}

function resolveConversationRootRequestId(
  requestId: string,
  requestStateMap: Map<string, ChatConversationRequest>,
) {
  let currentRequestId = requestId.trim();

  if (!currentRequestId) {
    return '';
  }

  const visitedRequestIds = new Set<string>([currentRequestId]);
  let currentRequest = requestStateMap.get(currentRequestId);

  while (currentRequest) {
    const parentRequestId = currentRequest.parentRequestId?.trim() || '';

    if (!parentRequestId || visitedRequestIds.has(parentRequestId)) {
      break;
    }

    const parentRequest = requestStateMap.get(parentRequestId);

    if (!parentRequest) {
      break;
    }

    currentRequestId = parentRequest.requestId;
    currentRequest = parentRequest;
    visitedRequestIds.add(parentRequestId);
  }

  return currentRequestId;
}

function collectRequestLineageIds(
  requestId: string,
  requestStateMap: Map<string, ChatConversationRequest>,
) {
  const normalizedRequestId = requestId.trim();

  if (!normalizedRequestId) {
    return [];
  }

  const lineageIds: string[] = [];
  const visitedRequestIds = new Set<string>();
  let currentRequestId = normalizedRequestId;

  while (currentRequestId && !visitedRequestIds.has(currentRequestId)) {
    lineageIds.push(currentRequestId);
    visitedRequestIds.add(currentRequestId);

    const currentRequest = requestStateMap.get(currentRequestId);
    const parentRequestId = currentRequest?.parentRequestId?.trim() || '';

    if (!parentRequestId) {
      break;
    }

    currentRequestId = parentRequestId;
  }

  return lineageIds;
}

function resolveChildComposerParentRequestId(
  request: ChatConversationRequest | null | undefined,
  requestStateMap: Map<string, ChatConversationRequest>,
) {
  let currentRequest = request ?? null;
  const visitedRequestIds = new Set<string>();

  while (currentRequest) {
    const currentRequestId = currentRequest.requestId.trim();

    if (!currentRequestId || visitedRequestIds.has(currentRequestId)) {
      break;
    }

    visitedRequestIds.add(currentRequestId);

    if (currentRequest.requestOrigin !== 'prompt') {
      return currentRequestId;
    }

    const parentRequestId = currentRequest.parentRequestId?.trim() || '';

    if (!parentRequestId) {
      return currentRequestId;
    }

    currentRequest = requestStateMap.get(parentRequestId) ?? null;
  }

  return request?.requestId?.trim() || '';
}

function resolvePromptSubmissionParentRequestId(
  requestId: string | null | undefined,
  requestStateMap: Map<string, ChatConversationRequest>,
) {
  const normalizedRequestId = requestId?.trim() || '';

  if (!normalizedRequestId) {
    return '';
  }

  const request = requestStateMap.get(normalizedRequestId) ?? null;
  return resolveChildComposerParentRequestId(request, requestStateMap) || normalizedRequestId;
}

function isPromptFollowupRequest(request: ChatConversationRequest) {
  return request.requestOrigin === 'prompt';
}

function resolveConversationMessageGroupRequestId(
  requestId: string | null | undefined,
  requestStateMap: Map<string, ChatConversationRequest>,
) {
  const normalizedRequestId = requestId?.trim() || '';

  if (!normalizedRequestId) {
    return '';
  }

  return resolveConversationRootRequestId(normalizedRequestId, requestStateMap) || normalizedRequestId;
}

function getConversationRequestDepth(
  request: ChatConversationRequest | null | undefined,
  requestStateMap: Map<string, ChatConversationRequest>,
) {
  if (!request) {
    return 0;
  }

  return Math.max(0, collectRequestLineageIds(request.requestId, requestStateMap).length - 1);
}

export function classifyInlinePreviewKind(url: string): InlinePreviewKind {
  return classifyPreviewKind(url);
}

function isHtmlPreviewUrl(url: string) {
  const pathname = url.toLowerCase().split('?')[0] ?? '';
  return pathname.endsWith('.html') || pathname.endsWith('.htm');
}

function downloadTextFile(content: string, fileName: string, mimeType = 'text/plain;charset=utf-8') {
  if (typeof document === 'undefined') {
    return;
  }

  const blob = new Blob([content], { type: mimeType });
  const objectUrl = URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = objectUrl;
  link.download = fileName;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
  URL.revokeObjectURL(objectUrl);
}

function buildInlinePreviewLabel(url: string) {
  try {
    const parsed = new URL(url);
    return parsed.pathname.split('/').filter(Boolean).at(-1) || parsed.hostname;
  } catch {
    return url;
  }
}

function buildPreviewFileName(item: Pick<PreviewOption, 'url' | 'label'>) {
  try {
    const parsed = new URL(item.url, typeof window !== 'undefined' ? window.location.origin : 'https://local.invalid');
    const fileName = parsed.pathname.split('/').filter(Boolean).at(-1)?.trim();
    return fileName || item.label.trim() || item.url;
  } catch {
    return item.label.trim() || item.url;
  }
}

function getElementOffsetWithinContainer(target: HTMLElement, container: HTMLElement) {
  const targetRect = target.getBoundingClientRect();
  const containerRect = container.getBoundingClientRect();
  return targetRect.top - containerRect.top + container.scrollTop;
}

function getContainerScrollPaddingTop(container: HTMLElement) {
  if (typeof window === 'undefined') {
    return 0;
  }

  const paddingTop = Number.parseFloat(window.getComputedStyle(container).paddingTop || '0');
  return Number.isFinite(paddingTop) ? Math.max(0, paddingTop) : 0;
}

function resolveScrollableAnchorContainer(target: HTMLElement, preferredContainer?: HTMLElement | null) {
  const candidates: Array<HTMLElement | null | undefined> = [preferredContainer, target.parentElement];

  let currentAncestor = target.parentElement;
  while (currentAncestor) {
    candidates.push(currentAncestor);
    currentAncestor = currentAncestor.parentElement;
  }

  for (const candidate of candidates) {
    if (!(candidate instanceof HTMLElement)) {
      continue;
    }

    const style = window.getComputedStyle(candidate);
    const overflowY = style.overflowY;
    const isScrollableOverflow = overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay';

    if (!isScrollableOverflow) {
      continue;
    }

    if (candidate.scrollHeight <= candidate.clientHeight + 1) {
      continue;
    }

    return candidate;
  }

  return null;
}

function isPhoneLikeViewport() {
  if (typeof window === 'undefined') {
    return false;
  }

  const isNarrowViewport =
    window.matchMedia?.(`(max-width: ${MOBILE_SYSTEM_EXECUTION_AUTO_HIDE_MAX_WIDTH}px)`).matches === true;
  const hasCoarsePointer = window.matchMedia?.('(pointer: coarse)').matches === true;
  const hasTouchPoints = navigator.maxTouchPoints > 0;

  return isNarrowViewport && (hasCoarsePointer || hasTouchPoints);
}

function getSystemExecutionJumpTargetPriority(
  target: ReturnType<typeof resolveSystemExecutionJumpTarget>,
) {
  if (!target) {
    return -1;
  }

  switch (target.kind) {
    case 'prompt':
      return 3;
    case 'response':
      return 2;
    case 'request':
      return 1;
    default:
      return 0;
  }
}

function resolvePreviewFileExtension(item: Pick<PreviewOption, 'url' | 'label'>) {
  const fileName = buildPreviewFileName(item).toLowerCase();
  const match = fileName.match(/\.([a-z0-9]{1,16})$/i);
  return match?.[1] ?? '';
}

function renderSystemExecutionFilterIcon(filter: SystemExecutionFilter) {
  if (filter === 'active') {
    return <ThunderboltOutlined />;
  }

  if (filter === 'attention') {
    return <ExclamationCircleOutlined />;
  }

  return <FilterOutlined />;
}

function buildResourceChipMeta(item: Pick<PreviewOption, 'url' | 'label' | 'kind'>) {
  const extension = resolvePreviewFileExtension(item);

  if (extension) {
    return extension.toUpperCase();
  }

  return resolveChatPreviewKindLabel(item.kind as ChatPreviewKind)
    .replace(/\s+preview$/i, '')
    .replace(/\s+download$/i, '')
    .toUpperCase();
}

function normalizeRankedLinkTitle(value: string) {
  return value
    .replace(/^\[(.+)\]\([^)]+\)$/u, '$1')
    .replace(/\s+/g, ' ')
    .trim();
}

function extractRankedLinkTargets(text: string) {
  const lines = String(text ?? '').split('\n');
  const keptLines: string[] = [];
  const rankedLinkTargets: RankedLinkPreviewTarget[] = [];
  const seen = new Set<string>();

  const pushRankedLink = (title: string, url: string) => {
    const normalizedUrl = normalizeInlinePreviewUrl(url.trim());
    const normalizedTitle = normalizeRankedLinkTitle(title) || buildInlinePreviewLabel(normalizedUrl);
    const key = `${normalizedTitle}::${normalizedUrl}`;

    if (seen.has(key)) {
      return;
    }

    seen.add(key);
    rankedLinkTargets.push({
      title: normalizedTitle,
      url: normalizedUrl,
    });
  };

  for (let index = 0; index < lines.length; index += 1) {
    const line = lines[index] ?? '';
    const trimmedLine = line.trim();

    if (!trimmedLine) {
      keptLines.push(line);
      continue;
    }

    const markdownMatches = [...trimmedLine.matchAll(MARKDOWN_LINK_PATTERN)];
    if (markdownMatches.length > 0 && RANK_LINE_PATTERN.test(trimmedLine)) {
      markdownMatches.forEach((match) => {
        const [, label, href] = match;
        if (href?.trim()) {
          pushRankedLink(label?.trim() || href.trim(), href);
        }
      });
      continue;
    }

    const titleMatch = trimmedLine.match(TITLE_VALUE_PATTERN);
    if (!titleMatch) {
      keptLines.push(line);
      continue;
    }

    const collectedLines = [line];
    const title = titleMatch[1]?.trim() ?? '';
    let url = '';
    let hasRank = RANK_LINE_PATTERN.test(trimmedLine);
    let cursor = index + 1;

    while (cursor < lines.length) {
      const candidate = lines[cursor] ?? '';
      const trimmedCandidate = candidate.trim();

      if (!trimmedCandidate) {
        collectedLines.push(candidate);
        cursor += 1;
        continue;
      }

      if (trimmedCandidate.match(TITLE_VALUE_PATTERN) && cursor !== index + 1) {
        break;
      }

      const linkMatch = trimmedCandidate.match(LINK_VALUE_PATTERN);
      if (linkMatch) {
        url = linkMatch[1]?.trim() ?? url;
        collectedLines.push(candidate);
        hasRank ||= RANK_LINE_PATTERN.test(trimmedCandidate);
        cursor += 1;
        continue;
      }

      if (RANK_LINE_PATTERN.test(trimmedCandidate)) {
        hasRank = true;
        collectedLines.push(candidate);
        cursor += 1;
        continue;
      }

      break;
    }

    if (title && url && hasRank) {
      pushRankedLink(title, url);
      index = cursor - 1;
      continue;
    }

    keptLines.push(...collectedLines);
    index = cursor - 1;
  }

  return {
    strippedText: keptLines.join('\n').replace(/\n{3,}/g, '\n\n').trim(),
    rankedLinkTargets,
  };
}

function isClipboardImageFile(file: File) {
  const normalizedType = String(file.type ?? '').trim().toLowerCase();

  if (normalizedType.startsWith('image/')) {
    return true;
  }

  const normalizedName = String(file.name ?? '').trim().toLowerCase();
  return /\.(png|jpe?g|gif|webp|bmp|heic|heif)$/i.test(normalizedName);
}

function isGeneratedClipboardImageName(file: File) {
  const normalizedName = String(file.name ?? '').trim().toLowerCase();

  if (!normalizedName) {
    return true;
  }

  return /^(image|clipboard|pasted image)(?:[-\s]?\d+)?(?: \(\d+\))?\.(png|jpe?g|gif|webp|bmp|tiff?|heic|heif)$/i.test(
    normalizedName,
  );
}

function getClipboardImageMimeRank(file: File) {
  const normalizedType = String(file.type ?? '').trim().toLowerCase();

  switch (normalizedType) {
    case 'image/png':
      return 0;
    case 'image/jpeg':
      return 1;
    case 'image/webp':
      return 2;
    case 'image/gif':
      return 3;
    case 'image/bmp':
      return 4;
    case 'image/heic':
    case 'image/heif':
      return 5;
    case 'image/tiff':
    case 'image/tif':
      return 6;
    default:
      return 7;
  }
}

function resolvePreferredClipboardImageFiles(files: File[]) {
  if (files.length <= 1) {
    return files;
  }
  const sortedFiles = [...files]
    .sort((left, right) => {
      const rankDifference = getClipboardImageMimeRank(left) - getClipboardImageMimeRank(right);

      if (rankDifference !== 0) {
        return rankDifference;
      }

      return right.size - left.size;
    })
    .slice(0, 1);

  if (files.every(isGeneratedClipboardImageName)) {
    return sortedFiles;
  }

  return sortedFiles;
}

function resolveComposerPasteFiles(clipboardData: DataTransfer) {
  const clipboardItemFiles = Array.from(clipboardData.items ?? [])
    .filter((item) => item.kind === 'file')
    .map((item) => item.getAsFile())
    .filter((file): file is File => file instanceof File)
    .filter((file) => file.size > 0);
  const clipboardFiles = Array.from(clipboardData.files ?? []).filter((file) => file.size > 0);
  const candidateFiles = clipboardItemFiles.length > 0 ? clipboardItemFiles : clipboardFiles;
  const imageFiles = candidateFiles.filter(isClipboardImageFile);
  const filesToUse = imageFiles.length > 0 ? resolvePreferredClipboardImageFiles(imageFiles) : candidateFiles;

  return Array.from(new Map(filesToUse.map((file) => [buildComposerFilePickKey(file), file])).values());
}

async function createPreviewFetchError(response: Response): Promise<PreviewFetchError> {
  const contentType = response.headers.get('content-type')?.toLowerCase() ?? '';
  let responseMessage = '';

  try {
    if (contentType.includes('application/json')) {
      const payload = (await response.json()) as { message?: string };
      responseMessage = String(payload.message ?? '').trim();
    } else {
      responseMessage = (await response.text()).trim();
    }
  } catch {
    responseMessage = '';
  }

  const statusLabel =
    response.status === 403
      ? '이 문서는 현재 권한으로 열 수 없습니다.'
      : response.status === 404
        ? '이 문서를 찾을 수 없습니다.'
        : response.status === 401
          ? '이 문서를 열기 위한 인증이 필요합니다.'
          : `preview 요청이 실패했습니다. (${response.status})`;
  const detail = responseMessage && responseMessage !== response.statusText ? responseMessage : response.statusText.trim();
  const error = new Error(detail ? `${statusLabel} ${detail}` : statusLabel) as PreviewFetchError;
  error.status = response.status;
  return error;
}

function extractInlinePreviewTargets(text: string): InlinePreviewTarget[] {
  const seen = new Set<string>();
  const targets: InlinePreviewTarget[] = [];
  const pushTarget = (matchedUrl: string, options?: { allowHtml?: boolean }) => {
    const normalizedUrl = normalizeInlinePreviewUrl(matchedUrl);
    const kind = classifyInlinePreviewKind(normalizedUrl);

    if (kind === 'file') {
      return;
    }

    // Plain HTML artifact paths should stay as text unless the reply explicitly opts into preview rendering.
    if (!options?.allowHtml && isHtmlPreviewUrl(normalizedUrl)) {
      return;
    }

    if (seen.has(normalizedUrl)) {
      return;
    }

    seen.add(normalizedUrl);
    targets.push({
      url: normalizedUrl,
      label: buildInlinePreviewLabel(normalizedUrl),
      kind,
    });
  };

  extractHiddenPreviewUrls(text).forEach((matchedUrl) => {
    pushTarget(matchedUrl, { allowHtml: true });
  });

  return targets;
}

function renderMessageInlineParts(line: string): ReactNode[] {
  const renderedParts: ReactNode[] = [];
  let cursor = 0;

  for (const match of line.matchAll(MARKDOWN_LINK_PATTERN)) {
    const [fullMatch, label, rawHref] = match;
    const start = match.index ?? 0;

    if (start > cursor) {
      renderedParts.push(line.slice(cursor, start));
    }

    const href = normalizeInlinePreviewUrl(rawHref.trim());
    renderedParts.push(
      <a
        key={`${href}-${start}`}
        href={href}
        target="_blank"
        rel="noreferrer noopener"
        onClick={(event) => {
          openChatExternalLink(href, event);
        }}
      >
        {label.trim() || href}
      </a>,
    );
    cursor = start + fullMatch.length;
  }

  if (cursor < line.length) {
    renderedParts.push(line.slice(cursor));
  }

  return renderedParts.length > 0 ? renderedParts : [line];
}

function renderMessageBody(text: string) {
  const lines = text.split('\n');

  return lines.map((line, index) => {
    const imageMatch = line.match(MARKDOWN_IMAGE_LINE_PATTERN);

    if (imageMatch) {
      const [, alt, rawSrc] = imageMatch;
      const src = normalizeInlinePreviewUrl(rawSrc.trim());

      return (
        <div key={`img-${index}`} className="app-chat-message__block app-chat-message__block--image">
          <InlineImage
            src={src}
            alt={alt.trim() || 'chat image'}
            className="app-chat-message__inline-image markdown-preview__image"
            fallbackText="이미지 preview를 불러오지 못했습니다."
          />
        </div>
      );
    }

    if (!line.length) {
      return <div key={`space-${index}`} className="app-chat-message__block app-chat-message__block--spacer" aria-hidden="true" />;
    }

    return (
      <div key={`line-${index}`} className="app-chat-message__block">
        {renderMessageInlineParts(line)}
      </div>
    );
  });
}

const EXECUTOR_HEADER_LINE_PATTERN = /^\[[^\]\n]+ · [^\]\n]+\]$/u;

function collapseDuplicatedLeadingExecutorHeaders(text: string) {
  const lines = text.split('\n');
  const collapsedLines: string[] = [];
  let previousHeaderLine: string | null = null;
  let isLeadingHeaderSection = true;

  lines.forEach((line) => {
    const normalizedLine = line.trim();
    const isExecutorHeaderLine = EXECUTOR_HEADER_LINE_PATTERN.test(normalizedLine);

    if (isLeadingHeaderSection && isExecutorHeaderLine) {
      if (previousHeaderLine !== normalizedLine) {
        collapsedLines.push(line);
        previousHeaderLine = normalizedLine;
      }
      return;
    }

    if (normalizedLine.length > 0) {
      isLeadingHeaderSection = false;
    }

    previousHeaderLine = null;
    collapsedLines.push(line);
  });

  return collapsedLines.join('\n');
}

function isRequestInFlightStatus(status: ChatConversationRequestStatus | null | undefined) {
  return status === 'accepted' || status === 'queued' || status === 'started';
}

function extractMessageRenderPayload(message: ChatMessage): MessageRenderPayload {
  const structuredParts = Array.isArray(message.parts) ? message.parts : [];
  const attachmentExtraction = extractAttachmentPreviewUrls(message.text);
  const extractedMessageParts = extractChatMessageParts(attachmentExtraction.strippedText);
  const text = extractedMessageParts.strippedText;
  const linkCardTargets = [
    ...structuredParts.filter((part): part is Extract<ChatMessagePart, { type: 'link_card' }> => part.type === 'link_card'),
    ...extractedMessageParts.parts.filter((part): part is Extract<ChatMessagePart, { type: 'link_card' }> => part.type === 'link_card'),
  ].filter((part, index, collection) => collection.findIndex((candidate) => `${candidate.title}:${candidate.url}` === `${part.title}:${part.url}`) === index);
  const previewCardTargets = [
    ...structuredParts.filter((part): part is Extract<ChatMessagePart, { type: 'preview_card' }> => part.type === 'preview_card'),
    ...extractedMessageParts.parts.filter((part): part is Extract<ChatMessagePart, { type: 'preview_card' }> => part.type === 'preview_card'),
  ].filter(
    (part, index, collection) =>
      collection.findIndex(
        (candidate) =>
          `${candidate.title}:${candidate.preview.type}:${candidate.preview.url ?? ''}:${candidate.preview.content ?? ''}` ===
          `${part.title}:${part.preview.type}:${part.preview.url ?? ''}:${part.preview.content ?? ''}`,
      ) === index,
  );
  const promptTargets = (() => {
    const promptParts = [
      ...structuredParts.filter((part): part is Extract<ChatMessagePart, { type: 'prompt' }> => part.type === 'prompt'),
      ...extractedMessageParts.parts.filter((part): part is Extract<ChatMessagePart, { type: 'prompt' }> => part.type === 'prompt'),
    ];
    const promptByKey = new Map<string, Extract<ChatMessagePart, { type: 'prompt' }>>();
    const getPromptKey = (part: Extract<ChatMessagePart, { type: 'prompt' }>) =>
      [
        part.title,
        part.options.map((option) => `${option.value}:${option.label}`).join(','),
        (part.steps ?? [])
          .map((step) => `${step.key}:${step.title}:${step.options.map((option) => `${option.value}:${option.label}`).join(',')}`)
          .join(';'),
      ].join('::');
    const getPromptRichness = (part: Extract<ChatMessagePart, { type: 'prompt' }>) =>
      [
        part.selectedValues?.length ?? 0,
        (part.steps ?? []).reduce((count, step) => count + (step.selectedValues?.length ?? 0), 0),
        part.resultText?.trim() ? 1 : 0,
        part.resolvedBy ? 1 : 0,
        part.readOnly === true ? 1 : 0,
      ].reduce((sum, value) => sum + value, 0);

    promptParts.forEach((part) => {
      const key = getPromptKey(part);
      const existing = promptByKey.get(key);

      if (!existing || getPromptRichness(part) >= getPromptRichness(existing)) {
        promptByKey.set(key, part);
      }
    });

    return [...promptByKey.values()];
  })();
  const diffBlocks = Array.from(text.matchAll(DIFF_CODE_BLOCK_PATTERN))
    .map((match) => match[1]?.trim())
    .filter((value): value is string => Boolean(value));

  const diffStrippedText = text.replace(DIFF_CODE_BLOCK_PATTERN, '');
  const { strippedText: previewSourceText, rankedLinkTargets } = extractRankedLinkTargets(diffStrippedText);
  const visibleText = collapseDuplicatedLeadingExecutorHeaders(stripHiddenPreviewTags(previewSourceText));
  return {
    previewSourceText: [previewSourceText, ...attachmentExtraction.urls.map((url) => `[[preview:${url}]]`)].filter(Boolean).join('\n'),
    visibleText,
    diffBlocks,
    rankedLinkTargets,
    linkCardTargets,
    previewCardTargets,
    promptTargets,
  };
}

function extractPromptPreviewUrls(promptTargets: Extract<ChatMessagePart, { type: 'prompt' }>[]) {
  const urls = new Set<string>();

  promptTargets.forEach((target) => {
    target.options.forEach((option) => {
      const previewUrl = option.preview?.url?.trim();

      if (previewUrl) {
        urls.add(previewUrl);
      }
    });

    (target.steps ?? []).forEach((step) => {
      step.options.forEach((option) => {
        const previewUrl = option.preview?.url?.trim();

        if (previewUrl) {
          urls.add(previewUrl);
        }
      });
    });
  });

  return [...urls];
}

function hasPromptPreviewArtifact(promptTargets: Extract<ChatMessagePart, { type: 'prompt' }>[]) {
  return promptTargets.some((target) => {
    const hasPreviewOption = target.options.some((option) => option.preview != null);
    const hasPreviewStepOption = (target.steps ?? []).some((step) => step.options.some((option) => option.preview != null));
    return hasPreviewOption || hasPreviewStepOption;
  });
}

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 summarizeQueuedText(text: string, maxLength = DEFAULT_QUEUE_SUMMARY_MAX_LENGTH) {
  const normalized = text.replace(/\s+/g, ' ').trim();
  return normalized.length > maxLength ? `${normalized.slice(0, maxLength).trimEnd()}...` : normalized;
}

function resolvePromptParentQuestionText(request: ChatConversationRequest | null | undefined) {
  return request?.userText?.replace(/\s+/g, ' ').trim() || '';
}

function resolveSystemExecutionSummaryMaxLength() {
  if (typeof window === 'undefined') {
    return DEFAULT_QUEUE_SUMMARY_MAX_LENGTH;
  }

  if (window.matchMedia?.('(min-width: 1367px)').matches) {
    return DESKTOP_SYSTEM_EXECUTION_SUMMARY_MAX_LENGTH;
  }

  if (window.matchMedia?.('(min-width: 820px)').matches) {
    return TABLET_SYSTEM_EXECUTION_SUMMARY_MAX_LENGTH;
  }

  return DEFAULT_QUEUE_SUMMARY_MAX_LENGTH;
}

function normalizeAttachmentName(value: string) {
  return String(value ?? '').trim().toLowerCase();
}

function isActivityLogMessage(message: ChatMessage) {
  return message.author === 'system' && message.text.startsWith(`${CHAT_ACTIVITY_MESSAGE_PREFIX}\n`);
}

function getMissingRequestMessageText(message: ChatMessage) {
  if (!isMissingRequestMessage(message)) {
    return '';
  }

  return message.text.slice(`${CHAT_MISSING_REQUEST_MESSAGE_PREFIX}\n`.length).trim();
}

function getExecutionFailureMessageText(message: ChatMessage) {
  if (!isExecutionFailureMessage(message)) {
    return '';
  }

  return message.text.slice(`${CHAT_EXECUTION_FAILURE_MESSAGE_PREFIX}\n`.length).trim();
}

function isLikelyCollapsibleMessage(text: string) {
  const normalizedText = String(text ?? '').trim();

  if (!normalizedText) {
    return false;
  }

  if (normalizedText.length > COLLAPSIBLE_MESSAGE_CHAR_COUNT) {
    return true;
  }

  const visualLines = normalizedText
    .split('\n')
    .map((line) => line.trim())
    .filter(Boolean);

  return visualLines.length > COLLAPSIBLE_MESSAGE_LINE_COUNT;
}

function isTerminalRequestStatus(status: ChatConversationRequestStatus | undefined) {
  return status === 'completed' || status === 'failed' || status === 'cancelled' || status === 'removed';
}

function isRenderableConversationMessage(message: ChatMessage) {
  return !isActivityLogMessage(message);
}

function resolveCollapsedGroupSummary(
  request: ChatConversationRequest | undefined,
  groupedRequests: ChatConversationRequest[],
  messages: ChatMessage[],
  attentionStateByRequestId: Map<string, SystemExecutionAttentionState>,
  options?: {
    preferredMessageId?: number | null;
  },
) {
  const renderableMessages = messages.filter(isRenderableConversationMessage);
  const normalizedRequests =
    groupedRequests.length > 0
      ? groupedRequests
      : request
        ? [request]
        : [];

  if (!request || !isTerminalRequestStatus(request.status) || renderableMessages.length <= 1) {
    return {
      shouldCollapseByDefault: false,
      displayedMessages: renderableMessages,
      hiddenMessageCount: 0,
    };
  }

  const preferredMessageId = options?.preferredMessageId ?? null;
  const responseMessageId = preferredMessageId ?? request.responseMessageId;
  let finalMessage =
    (responseMessageId != null ? renderableMessages.find((message) => message.id === responseMessageId) : null) ?? null;

  if (!finalMessage) {
    finalMessage =
      [...renderableMessages].reverse().find((message) => message.author === 'codex' || message.author === 'system') ??
      renderableMessages[renderableMessages.length - 1] ??
      null;
  }

  if (!finalMessage) {
    return {
      shouldCollapseByDefault: false,
      displayedMessages: renderableMessages,
      hiddenMessageCount: 0,
    };
  }

  const unresolvedRequestIdSet = new Set(
    normalizedRequests
      .filter((groupedRequest) =>
        !isRequestUserFinalized(groupedRequest, attentionStateByRequestId.get(groupedRequest.requestId)),
      )
      .map((groupedRequest) => groupedRequest.requestId),
  );
  const firstUserMessage = renderableMessages.find((message) => message.author === 'user') ?? null;

  if (unresolvedRequestIdSet.size === 0) {
    return {
      shouldCollapseByDefault: true,
      displayedMessages: [finalMessage],
      hiddenMessageCount: Math.max(0, renderableMessages.length - 1),
    };
  }

  const displayedMessages: ChatMessage[] = [];
  const displayedMessageIdSet = new Set<number>();
  const pushDisplayedMessage = (message: ChatMessage | null) => {
    if (!message || displayedMessageIdSet.has(message.id)) {
      return;
    }

    displayedMessages.push(message);
    displayedMessageIdSet.add(message.id);
  };

  pushDisplayedMessage(firstUserMessage);
  renderableMessages.forEach((message) => {
    const messageRequestId = message.clientRequestId?.trim() || '';

    if (!messageRequestId || !unresolvedRequestIdSet.has(messageRequestId)) {
      return;
    }

    pushDisplayedMessage(message);
  });

  if (displayedMessages.length === 0) {
    pushDisplayedMessage(finalMessage);
  }

  return {
    shouldCollapseByDefault: displayedMessages.length < renderableMessages.length,
    displayedMessages,
    hiddenMessageCount: Math.max(0, renderableMessages.length - displayedMessages.length),
  };
}

function formatChatTimestamp(timestamp: string) {
  const normalized = String(timestamp ?? '').trim();

  if (!normalized) {
    return '';
  }

  if (KST_TIMESTAMP_PATTERN.test(normalized)) {
    return normalized;
  }

  const parsed = new Date(normalized);

  if (Number.isNaN(parsed.getTime())) {
    return normalized;
  }

  return KST_DATE_TIME_FORMATTER.format(parsed).replace(',', '');
}

function resolveSystemExecutionElapsedEndTime(request: ChatConversationRequest) {
  const terminalAt = String(request.terminalAt ?? '').trim();

  if (terminalAt) {
    return terminalAt;
  }

  const answeredAt = String(request.answeredAt ?? '').trim();

  if (answeredAt && (request.status === 'completed' || request.hasResponse)) {
    return answeredAt;
  }

  if (request.status === 'failed' || request.status === 'cancelled' || request.status === 'removed') {
    const updatedAt = String(request.updatedAt ?? '').trim();
    return updatedAt || null;
  }

  return null;
}

function formatSystemExecutionElapsedLabel(request: ChatConversationRequest) {
  const startedAt = new Date(String(request.createdAt ?? '').trim()).getTime();

  if (!Number.isFinite(startedAt)) {
    return '';
  }

  const endTimeSource = resolveSystemExecutionElapsedEndTime(request);

  if (!endTimeSource) {
    return '';
  }

  const endedAt = new Date(endTimeSource).getTime();

  if (!Number.isFinite(endedAt)) {
    return '';
  }

  const elapsedMs = Math.max(0, endedAt - startedAt);
  const totalSeconds = Math.floor(elapsedMs / 1000);

  if (totalSeconds < 60) {
    return `실행 ${Math.max(totalSeconds, 1)}초`;
  }

  const totalMinutes = Math.floor(totalSeconds / 60);

  if (totalMinutes < 60) {
    return `실행 ${totalMinutes}분`;
  }

  const hours = Math.floor(totalMinutes / 60);
  const minutes = totalMinutes % 60;

  if (minutes === 0) {
    return `실행 ${hours}시간`;
  }

  return `실행 ${hours}시간 ${minutes}분`;
}

function formatOngoingElapsedLabel(startedAt: string | null | undefined, nowMs: number) {
  const normalized = String(startedAt ?? '').trim();

  if (!normalized) {
    return '';
  }

  const startedMs = new Date(normalized).getTime();

  if (!Number.isFinite(startedMs)) {
    return '';
  }

  const diffSeconds = Math.max(0, Math.floor((nowMs - startedMs) / 1000));
  const hours = Math.floor(diffSeconds / 3600);
  const minutes = Math.floor((diffSeconds % 3600) / 60);
  const seconds = diffSeconds % 60;

  if (hours > 0) {
    return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
  }

  return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}

function hasAnsweredRequest(request: ChatConversationRequest | undefined) {
  if (!request || request.hasResponse !== true) {
    return false;
  }

  return Boolean(
    request.responseMessageId != null ||
      String(request.responseText ?? '').trim() ||
      String(request.answeredAt ?? '').trim(),
  );
}

function isRequestTerminalFailureStatus(status: ChatConversationRequestStatus | undefined) {
  return status === 'failed' || status === 'cancelled' || status === 'removed';
}

function isRequestQueueStatus(status: ChatConversationRequestStatus | undefined) {
  return status === 'accepted' || status === 'queued';
}

function isRequestRunningStatus(status: ChatConversationRequestStatus | undefined) {
  return status === 'started';
}

function isDisconnectedRequestNeedingAttention(request: ChatConversationRequest | undefined) {
  if (!request || request.hasResponse) {
    return false;
  }

  if (request.status !== 'failed') {
    return false;
  }

  return (request.statusMessage?.trim() ?? '') === '중단된 오래된 요청';
}

function isRequestUserFinalized(
  request: ChatConversationRequest,
  attentionState?: SystemExecutionAttentionState,
) {
  if (request.status !== 'completed') {
    return false;
  }

  if (!attentionState) {
    return true;
  }

  return !attentionState.hasPendingPromptBadge && !attentionState.hasPendingVerificationBadge;
}

function resolveAggregatedRequestStatusSummary(
  requests: ChatConversationRequest[],
  attentionStateByRequestId: Map<string, SystemExecutionAttentionState>,
): AggregatedRequestStatusSummary {
  if (requests.length === 0) {
    return {
      label: null,
      tone: 'neutral',
    };
  }

  if (requests.some((request) => request.status === 'failed')) {
    return {
      label: '실패',
      tone: 'failed',
    };
  }

  if (requests.some((request) => request.status === 'cancelled' || request.status === 'removed')) {
    return {
      label: '중단됨',
      tone: 'cancelled',
    };
  }

  const allCompleted = requests.every((request) => request.status === 'completed');
  const allAnswered = requests.every((request) => hasAnsweredRequest(request));
  const allUserFinalized = requests.every((request) =>
    isRequestUserFinalized(request, attentionStateByRequestId.get(request.requestId)),
  );

  if (allCompleted && allUserFinalized) {
    return {
      label: '완료',
      tone: 'completed',
    };
  }

  if (allCompleted && !allUserFinalized) {
    return {
      label: '확인대기',
      tone: 'attention',
    };
  }

  if (requests.some((request) => isRequestRunningStatus(request.status))) {
    return {
      label: '처리중',
      tone: 'started',
    };
  }

  if (requests.some((request) => isRequestQueueStatus(request.status))) {
    return {
      label: '대기중',
      tone: 'queued',
    };
  }

  if (requests.some((request) => isRequestTerminalFailureStatus(request.status))) {
    return {
      label: '확인필요',
      tone: 'failed',
    };
  }

  return {
    label: null,
    tone: 'neutral',
  };
}

function resolveActiveSystemExecutionActionTargetRequest(
  requests: ChatConversationRequest[],
  attentionStateByRequestId: Map<string, SystemExecutionAttentionState>,
) {
  const activeRequests = requests.filter(
    (request) => isRequestRunningStatus(request.status) || isRequestQueueStatus(request.status),
  );

  if (activeRequests.length === 0) {
    return null;
  }

  return resolveRepresentativeSystemExecutionRequest(activeRequests, attentionStateByRequestId) ?? activeRequests[0]!;
}

function getRepresentativeAttentionPriority(
  request: ChatConversationRequest,
  attentionState: SystemExecutionAttentionState | undefined,
) {
  if (attentionState?.hasPendingPromptBadge) {
    return 0;
  }

  if (attentionState?.hasPendingVerificationBadge) {
    return 1;
  }

  if (request.status === 'failed') {
    return 2;
  }

  if (request.status === 'started') {
    return 3;
  }

  if (request.status === 'accepted' || request.status === 'queued') {
    return 4;
  }

  if (request.hasResponse) {
    return 5;
  }

  return 6;
}

function compareRepresentativeRequests(
  left: ChatConversationRequest,
  right: ChatConversationRequest,
  attentionStateByRequestId: Map<string, SystemExecutionAttentionState>,
) {
  const leftPriority = getRepresentativeAttentionPriority(left, attentionStateByRequestId.get(left.requestId));
  const rightPriority = getRepresentativeAttentionPriority(right, attentionStateByRequestId.get(right.requestId));

  if (leftPriority !== rightPriority) {
    return leftPriority - rightPriority;
  }

  const leftTimestamp = left.answeredAt || left.terminalAt || left.updatedAt || left.createdAt;
  const rightTimestamp = right.answeredAt || right.terminalAt || right.updatedAt || right.createdAt;

  return (
    rightTimestamp.localeCompare(leftTimestamp) ||
    right.updatedAt.localeCompare(left.updatedAt) ||
    right.createdAt.localeCompare(left.createdAt) ||
    right.requestId.localeCompare(left.requestId)
  );
}

function resolveRepresentativeSystemExecutionRequest(
  requests: ChatConversationRequest[],
  attentionStateByRequestId: Map<string, SystemExecutionAttentionState>,
) {
  if (requests.length === 0) {
    return null;
  }

  return [...requests].sort((left, right) =>
    compareRepresentativeRequests(left, right, attentionStateByRequestId),
  )[0]!;
}

function formatRequestStatusLabel(
  request: ChatConversationRequest | undefined,
  attentionState?: SystemExecutionAttentionState,
  options?: {
    hideFinalizedLabel?: boolean;
  },
) {
  const hideFinalizedLabel = options?.hideFinalizedLabel === true;
  const retryCount = Math.max(0, Number(request?.retryCount ?? 0) || 0);
  const appendRetryLabel = (label: string | null) =>
    label && retryCount > 0 ? `${label} · 재처리 ${retryCount}회` : label;

  if (hasAnsweredRequest(request)) {
    if (request?.status === "completed") {
      if (hideFinalizedLabel) {
        return null;
      }

      return appendRetryLabel(
        attentionState?.hasPendingPromptBadge || attentionState?.hasPendingVerificationBadge ? "확인대기" : "완료",
      );
    }

    return appendRetryLabel(hideFinalizedLabel ? null : "답변도착");
  }

  switch (request?.status) {
    case 'accepted':
      return appendRetryLabel('접수됨');
    case 'queued':
      return appendRetryLabel('대기중');
    case 'started':
      return appendRetryLabel(request.hasResponse ? '응답작성중' : '처리중');
    case 'completed':
      return appendRetryLabel('완료');
    case 'failed':
      return appendRetryLabel('실패');
    case 'cancelled':
      return appendRetryLabel('취소됨');
    case 'removed':
      return appendRetryLabel('삭제됨');
    default:
      return null;
  }
}

function resolveProcessingStageBadgeLabel(stageKey: string | undefined) {
  switch (stageKey) {
    case 'execution':
      return '구현중';
    case 'result':
      return '검증중';
    case 'analysis':
    case 'inspection':
    case 'confirmation':
    default:
      return '분석중';
  }
}

function hasSourceAppliedResult(request: ChatConversationRequest | undefined, lines: string[]) {
  if (!request) {
    return false;
  }

  const responseText = String(request.responseText ?? '').trim();
  const activityText = lines.join('\n');

  return (
    /```diff\b/i.test(responseText) ||
    /변경 파일\s*:/u.test(responseText) ||
    /\bapply_patch\b/i.test(activityText)
  );
}

function normalizeActivityPlanTitle(planTitle: string) {
  const normalizedTitle = planTitle.trim();

  if (!normalizedTitle) {
    return '최초 작업 계획 체크리스트';
  }

  if (normalizedTitle === '관리자 계획') {
    return '최초 작업 계획 체크리스트';
  }

  return normalizedTitle.endsWith('체크리스트') ? normalizedTitle : `${normalizedTitle} 체크리스트`;
}

function splitExecutorActivityLine(line: string) {
  const normalizedLine = line.trim();
  const separatorIndex = normalizedLine.indexOf(' $ ');

  if (separatorIndex < 0) {
    return {
      focus: normalizedLine || '대기 중',
      command: null,
      commandSummary: null,
    };
  }

  const focus = normalizedLine.slice(0, separatorIndex).trim() || '진행 항목';
  const command = normalizedLine.slice(separatorIndex + 3).trim() || null;
  const descriptor = describeExecutorCommand(command, focus);

  return {
    focus,
    command,
    commandSummary: descriptor.message,
  };
}

function extractSystemExecutionActivityOverview(lines: string[]): SystemExecutionActivityOverview {
  const normalizedLines = lines.map((line) => line.trim()).filter(Boolean);
  const planTitleLine = normalizedLines.find((line) => line.startsWith('# 계획:')) ?? '';
  const planTitle = planTitleLine ? planTitleLine.slice('# 계획:'.length).trim() : '';
  const executorMap = new Map<
    string,
    {
      key: string;
      title: string;
      line: string;
      focus: string;
      command: string | null;
      commandSummary: string | null;
      updateCount: number;
    }
  >();
  const fallbackExecutorLines: string[] = [];

  normalizedLines.forEach((line, index) => {
    if (!line.startsWith('실행기:')) {
      if (line.startsWith('# 진행:') && line.includes(' $ ')) {
        fallbackExecutorLines.push(line.slice('# 진행:'.length).trim());
      }
      return;
    }

    const executorPayload = line.slice('실행기:'.length).trim();
    const separatorIndex = executorPayload.indexOf(' · ');
    const title = (separatorIndex >= 0 ? executorPayload.slice(0, separatorIndex) : executorPayload).trim() || '실행기';
    const message = (separatorIndex >= 0 ? executorPayload.slice(separatorIndex + 3) : '').trim() || '대기 중';
    const details = splitExecutorActivityLine(message);
    const existing = Array.from(executorMap.values()).find((entry) => entry.title === title);

    if (existing) {
      existing.line = message;
      existing.focus = details.focus;
      existing.command = details.command;
      existing.commandSummary = details.commandSummary;
      existing.updateCount += 1;
      return;
    }

    executorMap.set(`${title}:${index}`, {
      key: `${title}:${index}`,
      title,
      line: message,
      focus: details.focus,
      command: details.command,
      commandSummary: details.commandSummary,
      updateCount: 1,
    });
  });

  if (executorMap.size === 0 && fallbackExecutorLines.length > 0) {
    const latestExecutorLine = fallbackExecutorLines[fallbackExecutorLines.length - 1] ?? '';
    const details = splitExecutorActivityLine(latestExecutorLine);
    const message = details.command ?? details.focus;

    executorMap.set('generic-executor-log', {
      key: 'generic-executor-log',
      title: '실행기 로그',
      line: message,
      focus: details.focus,
      command: details.command,
      commandSummary: details.commandSummary,
      updateCount: fallbackExecutorLines.length,
    });
  }

  return {
    lines: normalizedLines.filter((line) => !line.startsWith('# 계획:') && !line.startsWith('# 실행기:')),
    planTitle: normalizeActivityPlanTitle(planTitle),
    executors: Array.from(executorMap.values()),
  };
}

function hasVisibleActivityOverviewContent(
  activityOverview: SystemExecutionActivityOverview | null | undefined,
  request?: ChatConversationRequest,
  attentionState?: SystemExecutionAttentionState,
) {
  if (!activityOverview) {
    return false;
  }

  const isUserFinalized = request ? isRequestUserFinalized(request, attentionState) : false;
  const isInProgress =
    request == null ||
    request.status === 'accepted' ||
    request.status === 'queued' ||
    request.status === 'started';
  const hasPendingAttention = attentionState?.hasOwnAttentionState === true;
  const hasChecklistEntries = buildChatActivityChecklistEntries(activityOverview.lines, request).length > 0;
  const hasExecutorEntries = activityOverview.executors.length > 0;

  if (isUserFinalized || (!isInProgress && !hasPendingAttention)) {
    return false;
  }

  return hasChecklistEntries || hasExecutorEntries;
}

function resolveVisibleActivityOverviewTargetRequest(
  requests: ChatConversationRequest[],
  activityOverviewByRequestId: Map<string, SystemExecutionActivityOverview>,
  attentionStateByRequestId: Map<string, SystemExecutionAttentionState>,
) {
  const visibleRequests = resolveVisibleActivityOverviewRequests(
    requests,
    activityOverviewByRequestId,
    attentionStateByRequestId,
  );

  if (visibleRequests.length === 0) {
    return null;
  }

  return resolveRepresentativeSystemExecutionRequest(visibleRequests, attentionStateByRequestId) ?? visibleRequests[0]!;
}

function resolveVisibleActivityOverviewRequests(
  requests: ChatConversationRequest[],
  activityOverviewByRequestId: Map<string, SystemExecutionActivityOverview>,
  attentionStateByRequestId: Map<string, SystemExecutionAttentionState>,
) {
  return requests.filter((request) =>
    hasVisibleActivityOverviewContent(
      activityOverviewByRequestId.get(request.requestId),
      request,
      attentionStateByRequestId.get(request.requestId),
    ),
  );
}

function renderActivityOverviewBody(
  activityOverview: SystemExecutionActivityOverview,
  messageId: number,
  request?: ChatConversationRequest,
  options?: {
    className?: string;
    attentionState?: SystemExecutionAttentionState;
    requestTree?: ReactNode;
  },
) {
  const isUserFinalized = request ? isRequestUserFinalized(request, options?.attentionState) : false;
  const showChecklist =
    buildChatActivityChecklistEntries(activityOverview.lines, request).length > 0 && !isUserFinalized;
  const showExecutors = activityOverview.executors.length > 0 && !isUserFinalized;
  const primaryExecutorSummary = activityOverview.executors.find((executor) => executor.commandSummary)?.commandSummary ?? '';
  const primaryExecutor = activityOverview.executors.find((executor) => executor.commandSummary);
  const primaryExecutorDescriptor = primaryExecutor
    ? describeExecutorCommand(primaryExecutor.command, primaryExecutor.focus)
    : null;
  const checklistStatusLabel = primaryExecutorDescriptor
    ? `현재 작업: ${primaryExecutorDescriptor.focusLabel} · ${primaryExecutorDescriptor.kindLabel} 단계에서 ${primaryExecutorDescriptor.detailLabel}`
    : primaryExecutorSummary
      ? `현재 작업: ${primaryExecutorSummary}`
      : undefined;

  if (!showChecklist && !showExecutors && !options?.requestTree) {
    return null;
  }

  return (
    <div
      className={['app-chat-activity-checklist-stack', options?.className].filter(Boolean).join(' ')}
      role="status"
      aria-live="polite"
    >
      {showChecklist ? (
        <ChatActivityChecklist
          lines={activityOverview.lines}
          request={request}
          title={activityOverview.planTitle || '최초 작업 계획 체크리스트'}
          statusLabel={checklistStatusLabel}
        />
      ) : null}
      {showExecutors
        ? activityOverview.executors.map((executor) => {
            const descriptor = describeExecutorCommand(executor.command, executor.focus);

            return (
              <section
                key={`${messageId}:${executor.key}`}
                className="app-chat-activity-checklist app-chat-activity-checklist--ticker"
                aria-label={`${executor.title} 상태`}
              >
                <div className="app-chat-activity-checklist__header">
                  <div className="app-chat-activity-checklist__title-group">
                    <span className="app-chat-activity-checklist__title">{executor.title} 실행기</span>
                  </div>
                </div>
                <div
                  className="app-chat-activity-ticker"
                  title={[descriptor.focusLabel, descriptor.message].filter(Boolean).join(' · ')}
                >
                  <div className="app-chat-activity-ticker__section">
                    <span className="app-chat-activity-ticker__label">대상 작업</span>
                    <p className="app-chat-activity-ticker__body">{descriptor.focusLabel}</p>
                  </div>
                  <div className="app-chat-activity-ticker__section">
                    <span className="app-chat-activity-ticker__label">현재 단계</span>
                    <p className="app-chat-activity-ticker__body">{descriptor.kindLabel}</p>
                  </div>
                  <div className="app-chat-activity-ticker__section">
                    <span className="app-chat-activity-ticker__label">실행 내용</span>
                    <p className="app-chat-activity-ticker__body">{descriptor.detailLabel}</p>
                  </div>
                </div>
              </section>
            );
          })
        : null}
      {options?.requestTree ?? null}
    </div>
  );
}

function getSystemExecutionSortPriority(request: ChatConversationRequest | undefined) {
  switch (request?.status) {
    case 'started':
      return 0;
    case 'accepted':
      return 1;
    case 'queued':
      return 2;
    case 'failed':
      return 3;
    case 'completed':
      return 4;
    case 'cancelled':
    case 'removed':
      return 5;
    default:
      return 6;
  }
}

function compareSystemExecutionRequests(
  left: ChatConversationRequest,
  right: ChatConversationRequest,
  sort: SystemExecutionSort,
) {
  if (sort === 'answered') {
    const leftAnsweredAt = left.answeredAt || left.terminalAt || left.updatedAt || left.createdAt;
    const rightAnsweredAt = right.answeredAt || right.terminalAt || right.updatedAt || right.createdAt;
    return (
      leftAnsweredAt.localeCompare(rightAnsweredAt) ||
      left.updatedAt.localeCompare(right.updatedAt) ||
      left.requestId.localeCompare(right.requestId)
    );
  }

  if (sort === 'status') {
    return (
      getSystemExecutionSortPriority(left) - getSystemExecutionSortPriority(right) ||
      left.createdAt.localeCompare(right.createdAt) ||
      left.updatedAt.localeCompare(right.updatedAt) ||
      left.requestId.localeCompare(right.requestId)
    );
  }

  return (
    left.createdAt.localeCompare(right.createdAt) ||
    left.updatedAt.localeCompare(right.updatedAt) ||
    left.requestId.localeCompare(right.requestId)
  );
}

function getRequestDetailBadge(request: ChatConversationRequest | undefined): SystemExecutionBadge | null {
  if (!request) {
    return null;
  }

  if (request.status === 'failed') {
    return { label: '확인 필요', shortLabel: '확인', tone: 'failed' as const };
  }

  if (request.status === 'cancelled' || request.status === 'removed') {
    return { label: '중단됨', shortLabel: '중단', tone: 'cancelled' as const };
  }

  if (request.status === 'completed') {
    return null;
  }

  if (request.status === 'started') {
    return null;
  }

  return null;
}

function getRequestRetryBadge(request: ChatConversationRequest | undefined): SystemExecutionBadge | null {
  const retryCount = Math.max(0, Number(request?.retryCount ?? 0) || 0);

  if (retryCount <= 0) {
    return null;
  }

  return {
    label: `재처리 ${retryCount}회`,
    shortLabel: `재처리 ${retryCount}`,
    tone: 'processing',
  };
}

function buildChecklistStageBadge(lines: string[], request?: ChatConversationRequest): SystemExecutionBadge | null {
  const entries = buildChatActivityChecklistEntries(lines, request);
  const activeEntry =
    entries.find((entry) => entry.state === 'error') ??
    entries.find((entry) => entry.state === 'current') ??
    [...entries].reverse().find((entry) => entry.state === 'complete') ??
    null;

  if (!activeEntry) {
    return null;
  }

  if (request?.status === 'started' && !hasAnsweredRequest(request)) {
    const label = resolveProcessingStageBadgeLabel(activeEntry.key);
    return {
      label,
      shortLabel: label.replace('중', ''),
      tone: 'attention',
    };
  }

  return {
    label: activeEntry.state === 'error' ? `${activeEntry.label} 확인 필요` : activeEntry.label,
    shortLabel: activeEntry.label
      .replace('요청 접수', '접수')
      .replace('요청 분석', '분석')
      .replace('관련 확인', '확인')
      .replace('구현·응답 작성', '구현')
      .replace('검증·결과 정리', '검증'),
    tone:
      activeEntry.state === 'error'
        ? 'failed'
        : activeEntry.state === 'complete'
          ? 'completed'
          : 'started',
  };
}

function buildPromptStateBadge(options: {
  promptTargets: Extract<ChatMessagePart, { type: 'prompt' }>[];
  submittedCount: number;
  isManuallyCompleted?: boolean;
  isPromptManuallyCompleted?: boolean;
}): SystemExecutionBadge | null {
  const { promptTargets, submittedCount, isManuallyCompleted } = options;

  if (promptTargets.length === 0) {
    return null;
  }

  const resolvedCount = promptTargets.filter((target) => isPromptResolved(target)).length;
  const unresolvedCount = Math.max(0, promptTargets.length - resolvedCount);
  const autoSubmittedCount = Math.min(Math.max(0, submittedCount), unresolvedCount);
  const pendingCount = Math.max(0, unresolvedCount - autoSubmittedCount);

  if (pendingCount > 0) {
    if (isManuallyCompleted) {
      return {
        label: '선택완료',
        shortLabel: '완료',
        tone: 'completed',
      };
    }

    return {
      label: '선택대기',
      shortLabel: '선택',
      tone: 'attention',
    };
  }

  if (autoSubmittedCount > 0) {
    return {
      label: '선택전송됨',
      shortLabel: '전송됨',
      tone: 'completed',
    };
  }

  return {
    label: '선택완료',
    shortLabel: '완료',
    tone: 'completed',
  };
}

function hasPendingPromptState(options: {
  promptTargets: Extract<ChatMessagePart, { type: 'prompt' }>[];
  submittedCount: number;
  isManuallyCompleted?: boolean;
}) {
  const { promptTargets, submittedCount, isManuallyCompleted } = options;

  if (promptTargets.length === 0 || isManuallyCompleted) {
    return false;
  }

  const resolvedCount = promptTargets.filter((target) => isPromptResolved(target)).length;
  const unresolvedCount = Math.max(0, promptTargets.length - resolvedCount);
  const autoSubmittedCount = Math.min(Math.max(0, submittedCount), unresolvedCount);

  return unresolvedCount - autoSubmittedCount > 0;
}

function buildVerificationStateBadge(options: {
  request: ChatConversationRequest | undefined;
  activityLines: string[];
  promptTargets: Extract<ChatMessagePart, { type: 'prompt' }>[];
  hasVerificationTarget: boolean;
  hasConfirmedVerificationTarget: boolean;
  isManuallyCompleted?: boolean;
  isPromptManuallyCompleted?: boolean;
}): SystemExecutionBadge | null {
  const {
    request,
    activityLines,
    promptTargets,
    hasVerificationTarget,
    hasConfirmedVerificationTarget,
    isManuallyCompleted,
    isPromptManuallyCompleted,
  } = options;

  if (!request || isPromptManuallyCompleted || promptTargets.length > 0 || !hasVerificationTarget) {
    return null;
  }

  const verificationText = [request.userText, request.responseText, ...activityLines].join('\n');
  const usesVerificationLabel = VERIFICATION_REQUEST_PATTERN.test(verificationText);
  const completedLabel = usesVerificationLabel ? '검증 확인' : '응답 확인';
  const pendingLabel = usesVerificationLabel ? '검증 미확인' : '응답 미확인';

  if (!usesVerificationLabel) {
    if (!request.hasResponse && request.status !== 'completed') {
      return null;
    }
  }

  return isManuallyCompleted
    ? {
        label: completedLabel,
        shortLabel: '확인',
        tone: 'completed',
      }
    : {
        label: pendingLabel,
        shortLabel: '미확인',
        tone: 'attention',
      };
}

function hasPendingVerificationState(options: {
  request: ChatConversationRequest | undefined;
  activityLines: string[];
  promptTargets: Extract<ChatMessagePart, { type: 'prompt' }>[];
  hasVerificationTarget: boolean;
  hasConfirmedVerificationTarget: boolean;
  isManuallyCompleted?: boolean;
  isPromptManuallyCompleted?: boolean;
}) {
  return buildVerificationStateBadge(options)?.tone === 'attention';
}

function buildManualCompletionTypes(options: {
  hasPendingPromptBadge: boolean;
  hasPendingVerificationBadge: boolean;
}) {
  const completionTypes: Array<'prompt' | 'verification'> = [];

  if (options.hasPendingPromptBadge) {
    completionTypes.push('prompt');
  }

  if (options.hasPendingVerificationBadge) {
    completionTypes.push('verification');
  }

  return completionTypes;
}

function resolvePrimaryManualCompletionType(options: {
  secondaryBadges: SystemExecutionBadge[];
  promptStateBadge: SystemExecutionBadge | null;
  verificationStateBadge: SystemExecutionBadge | null;
  hasPendingPromptBadge: boolean;
  hasPendingVerificationBadge: boolean;
}): 'prompt' | 'verification' | null {
  const candidates: Array<{
    type: 'prompt' | 'verification';
    badge: SystemExecutionBadge | null;
    pending: boolean;
  }> = [
    {
      type: 'prompt',
      badge: options.promptStateBadge,
      pending: options.hasPendingPromptBadge,
    },
    {
      type: 'verification',
      badge: options.verificationStateBadge,
      pending: options.hasPendingVerificationBadge,
    },
  ];

  const actionableCandidate = candidates
    .filter((candidate) => candidate.pending && candidate.badge != null)
    .sort((left, right) => {
      const leftIndex = options.secondaryBadges.findIndex((badge) => badge === left.badge);
      const rightIndex = options.secondaryBadges.findIndex((badge) => badge === right.badge);

      return leftIndex - rightIndex;
    })[0];

  return actionableCandidate?.type ?? null;
}

function buildPrimaryStatusBadge(
  request: ChatConversationRequest | undefined,
  options: {
    activityLines: string[];
    promptTargets: Extract<ChatMessagePart, { type: 'prompt' }>[];
    hasPendingPromptBadge?: boolean;
    hasPendingVerificationBadge?: boolean;
  },
): SystemExecutionBadge {
  if (hasAnsweredRequest(request) && request?.status !== 'completed') {
    return { label: '답변도착', shortLabel: '답변', tone: 'completed' };
  }

  switch (request?.status) {
    case 'accepted':
      return { label: '접수', shortLabel: '접수', tone: 'queued' };
    case 'queued':
      return { label: '대기중', shortLabel: '대기', tone: 'queued' };
    case 'started':
      return { label: '처리중', shortLabel: '처리', tone: 'started' };
    case 'completed':
      if (options.hasPendingPromptBadge || options.hasPendingVerificationBadge) {
        return { label: "확인대기", shortLabel: "확인", tone: "attention" };
      }

      if (hasSourceAppliedResult(request, options.activityLines)) {
        return { label: '반영완료', shortLabel: '반영', tone: 'completed' };
      }

      if (request?.hasResponse) {
        return { label: '답변완료', shortLabel: '답변', tone: 'completed' };
      }

      return { label: '처리완료', shortLabel: '완료', tone: 'completed' };
    case 'failed':
      return { label: '실패', shortLabel: '실패', tone: 'failed' };
    case 'cancelled':
      return { label: '취소됨', shortLabel: '취소', tone: 'cancelled' };
    case 'removed':
      return { label: '삭제됨', shortLabel: '삭제', tone: 'cancelled' };
    default:
      return { label: '처리중', shortLabel: '처리', tone: 'neutral' };
  }
}

function buildReadStateBadge(
  request: ChatConversationRequest,
  lastReadResponseMessageId: number | null,
): SystemExecutionBadge | null {
  if (request.responseMessageId == null || !request.hasResponse) {
    return null;
  }

  const readWatermark = typeof lastReadResponseMessageId === 'number' ? lastReadResponseMessageId : 0;
  const isRead = request.responseMessageId <= readWatermark;

  return {
    label: isRead ? '답변 읽음' : '답변 안읽음',
    shortLabel: isRead ? '읽음' : '새답',
    tone: isRead ? 'neutral' : 'unread',
  };
}

function hasReadResponse(request: ChatConversationRequest | undefined, lastReadResponseMessageId: number | null) {
  if (!request || request.responseMessageId == null || !request.hasResponse) {
    return false;
  }

  const readWatermark = typeof lastReadResponseMessageId === 'number' ? lastReadResponseMessageId : 0;
  return request.responseMessageId <= readWatermark;
}

function buildHierarchyBadge(
  request: ChatConversationRequest,
  depth: number,
): SystemExecutionBadge | null {
  if (depth <= 0) {
    return null;
  }

  return {
    label: '자식 요청',
    shortLabel: '자식',
    tone: 'neutral',
  };
}

function buildRootRelationshipBadge(
  rootRequest: ChatConversationRequest,
  groupedRequests: ChatConversationRequest[],
): SystemExecutionBadge | null {
  const childRequests = groupedRequests.filter((request) => request.requestId !== rootRequest.requestId);

  if (childRequests.length === 0) {
    return null;
  }

  return {
    label: `자식 ${childRequests.length}건`,
    shortLabel: `자식${childRequests.length}`,
    tone: 'neutral',
  };
}

function compareSystemExecutionHierarchyRequests(
  left: ChatConversationRequest,
  right: ChatConversationRequest,
  sort: SystemExecutionSort,
) {
  return compareSystemExecutionRequests(left, right, sort);
}

function buildMobileSecondaryBadge(
  request: ChatConversationRequest,
  badges: SystemExecutionBadge[],
): SystemExecutionBadge | null {
  if (badges.length === 0) {
    return null;
  }

  const tonePriority: Record<SystemExecutionBadgeTone, number> = {
    failed: 0,
    unread: 1,
    attention: 2,
    prompt: 3,
    cancelled: 4,
    started: 5,
    completed: 6,
    queued: 7,
    neutral: 8,
  };
  const shouldKeepBadge = (badge: SystemExecutionBadge) => {
    if (badge.tone === 'neutral' && badge.shortLabel === '읽음') {
      return false;
    }

    if (request.status === 'accepted' || request.status === 'queued') {
      return badge.shortLabel !== '대기';
    }

    if (request.status === 'started') {
      return badge.shortLabel !== '실행' && badge.shortLabel !== '작성';
    }

    if (request.status === 'failed') {
      return badge.shortLabel !== '확인';
    }

    if (request.status === 'cancelled' || request.status === 'removed') {
      return badge.shortLabel !== '중단';
    }

    return true;
  };
  const deduped = badges.filter((badge, index, collection) => {
    const normalized = badge.shortLabel.trim();

    return normalized.length > 0 && collection.findIndex((item) => item.shortLabel.trim() === normalized) === index;
  });
  const filtered = deduped.filter(shouldKeepBadge);
  const hasActionableStateBadge = filtered.some(
    (badge) =>
      badge.tone === 'attention' ||
      badge.tone === 'completed' ||
      badge.tone === 'failed' ||
      badge.tone === 'unread',
  );
  const prioritized = hasActionableStateBadge
    ? filtered.filter((badge) => !(badge.tone === 'prompt' && badge.shortLabel === '후속'))
    : filtered;
  const selected = prioritized
    .sort((left, right) => tonePriority[left.tone] - tonePriority[right.tone])
    .slice(0, 2);

  if (selected.length === 0) {
    return null;
  }

  return {
    label: selected.map((badge) => badge.label).join(' · '),
    shortLabel: selected.map((badge) => badge.shortLabel).join('·'),
    tone: selected[0]?.tone ?? 'neutral',
  };
}

function getRequestDetailText(request: ChatConversationRequest | undefined) {
  if (!request) {
    return '';
  }

  const normalizedStatusMessage = String(request.statusMessage ?? '').trim();

  if (!normalizedStatusMessage) {
    return '';
  }

  if (request.status === 'failed') {
    return normalizedStatusMessage.startsWith('실패')
      ? normalizedStatusMessage
      : `실패 사유: ${normalizedStatusMessage}`;
  }

  if (request.status === 'cancelled') {
    return normalizedStatusMessage.startsWith('취소')
      ? normalizedStatusMessage
      : `취소 사유: ${normalizedStatusMessage}`;
  }

  if (request.status === 'removed') {
    return normalizedStatusMessage;
  }

  return '';
}

function summarizeSystemExecutionRequestText(
  request: ChatConversationRequest,
  options?: {
    preferResponse?: boolean;
    maxLength?: number;
  },
) {
  const preferResponse = options?.preferResponse === true;
  const maxLength = options?.maxLength ?? DEFAULT_QUEUE_SUMMARY_MAX_LENGTH;
  const responseText = String(request.responseText ?? '').replace(/\s+/g, ' ').trim();
  const requestText = String(request.userText ?? '').replace(/\s+/g, ' ').trim();

  if (preferResponse && responseText) {
    return summarizeQueuedText(responseText, maxLength);
  }

  if (requestText) {
    return summarizeQueuedText(requestText, maxLength);
  }

  if (responseText) {
    return summarizeQueuedText(responseText, maxLength);
  }

  const detailText = getRequestDetailText(request);
  return detailText ? summarizeQueuedText(detailText, maxLength) : '표시할 요청 내용이 없습니다.';
}

function resolveSystemExecutionRequestTimestamp(request: ChatConversationRequest) {
  return formatChatTimestamp(resolveSystemExecutionElapsedEndTime(request) || request.createdAt);
}

function InlineMessagePreview({
  target,
  isExpanded,
  hasModalPreview,
  onOpenModalPreview,
  onToggle,
}: {
  target: InlinePreviewTarget;
  isExpanded: boolean;
  hasModalPreview: boolean;
  onOpenModalPreview: () => void;
  onToggle: () => void;
}) {
  const [textPreview, setTextPreview] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const [previewError, setPreviewError] = useState('');
  const [previewContentType, setPreviewContentType] = useState('');
  const normalizedPreviewUrl = useMemo(() => normalizeChatResourceUrl(target.url), [target.url]);

  useEffect(() => {
    if (!isExpanded) {
      return;
    }

    if (target.kind === 'image' || target.kind === 'video' || target.kind === 'pdf' || target.kind === 'file') {
      return;
    }

    const controller = new AbortController();
    setIsLoading(true);
    setPreviewError('');
    setPreviewContentType('');

    fetch(normalizedPreviewUrl, {
      cache: 'no-store',
      credentials: 'include',
      signal: controller.signal,
    })
      .then(async (response) => {
        if (!response.ok) {
          throw await createPreviewFetchError(response);
        }

        setPreviewContentType(response.headers.get('content-type') ?? '');
        const text = await response.text();
        setTextPreview(text.slice(0, 1600));
      })
      .catch((error: unknown) => {
        if (controller.signal.aborted) {
          return;
        }

        setTextPreview('');
        setPreviewContentType('');
        setPreviewError(error instanceof Error ? error.message : 'preview를 불러오지 못했습니다.');
      })
      .finally(() => {
        if (!controller.signal.aborted) {
          setIsLoading(false);
        }
      });

    return () => {
      controller.abort();
    };
  }, [isExpanded, normalizedPreviewUrl, target.kind]);

  const handleCopyPreview = () => {
    void copyPreviewContent({
      kind: target.kind,
      url: normalizedPreviewUrl,
      fallbackText: textPreview,
    })
      .then((result) => {
        if (result === 'image') {
          message.success('preview 이미지를 복사했습니다.');
          return;
        }

        if (result === 'url') {
          message.success('preview 이미지 URL을 복사했습니다.');
          return;
        }

        message.success('preview 내용을 복사했습니다.');
      })
      .catch((error: unknown) => {
        message.error(error instanceof Error ? error.message : 'preview 내용을 복사하지 못했습니다.');
      });
  };

  const handleSharePreview = () => {
    void sharePreviewLink({
      url: normalizedPreviewUrl,
      title: target.label,
    })
      .then((result) => {
        message.success(result === 'shared' ? 'preview 링크를 공유했습니다.' : 'preview 링크를 복사했습니다.');
      })
      .catch((error: unknown) => {
        if (error instanceof DOMException && error.name === 'AbortError') {
          return;
        }

        message.error(error instanceof Error ? error.message : 'preview 링크를 공유하지 못했습니다.');
      });
  };

  return (
    <section className={`app-chat-preview-card${isExpanded ? ' app-chat-preview-card--expanded' : ' app-chat-preview-card--collapsed'}`}>
      <div className="app-chat-preview-card__header">
        <div className="app-chat-preview-card__meta">
          <div className="app-chat-preview-card__titles">
            <span className="app-chat-preview-card__label">{target.label}</span>
            <span className="app-chat-preview-card__kind">{resolveChatPreviewKindLabel(target.kind)}</span>
          </div>
        </div>
        <div className="app-chat-preview-card__actions">
          <Button
            type="text"
            size="small"
            className="app-chat-preview-card__action"
            icon={<CopyOutlined />}
            aria-label="preview 내용 복사"
            onClick={handleCopyPreview}
          />
          <Button
            type="text"
            size="small"
            className="app-chat-preview-card__action"
            icon={hasModalPreview && isExpanded ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
            aria-label={hasModalPreview && isExpanded ? 'preview 100% 닫기' : 'preview 100%'}
            onClick={hasModalPreview ? onOpenModalPreview : onToggle}
          />
          <Button
            type="text"
            size="small"
            className="app-chat-preview-card__action"
            icon={<ShareAltOutlined />}
            aria-label="preview 공유"
            onClick={handleSharePreview}
          />
          <Button
            type="text"
            size="small"
            className="app-chat-preview-card__action"
            icon={<DownloadOutlined />}
            aria-label="preview 다운로드"
            onClick={() => {
              void triggerResourceDownload(normalizedPreviewUrl, buildPreviewFileName(target)).catch((error: unknown) => {
                message.error(error instanceof Error ? error.message : '다운로드에 실패했습니다.');
              });
            }}
          />
          <Button
            type="link"
            size="small"
            className="app-chat-preview-card__toggle"
            icon={isExpanded ? <UpOutlined /> : <DownOutlined />}
            aria-label={isExpanded ? 'preview 접기' : 'preview 펼치기'}
            onClick={onToggle}
          />
        </div>
      </div>

      {isExpanded ? (
        <div className="app-chat-preview-card__body">
          <ChatPreviewBody
            target={target}
            previewText={textPreview}
            isPreviewLoading={isLoading}
            previewError={previewError}
            previewContentType={previewContentType}
            maxMarkdownBlocks={12}
          />
        </div>
      ) : null}
    </section>
  );
}

function DiffMessagePreview({
  diffText,
  fileCount,
  isExpanded,
  isFullscreen,
  onToggle,
  onToggleFullscreen,
}: {
  diffText: string;
  fileCount: number;
  isExpanded: boolean;
  isFullscreen: boolean;
  onToggle: () => void;
  onToggleFullscreen: () => void;
}) {
  const handleCopyDiff = () => {
    void copyText(diffText)
      .then(() => {
        message.success('diff를 복사했습니다.');
      })
      .catch((error: unknown) => {
        message.error(error instanceof Error ? error.message : 'diff를 복사하지 못했습니다.');
      });
  };

  const content = (
    <section
      className={`app-chat-preview-card${isExpanded || isFullscreen ? ' app-chat-preview-card--expanded' : ' app-chat-preview-card--collapsed'}${
        isFullscreen ? ' app-chat-preview-card--fullscreen' : ''
      }`}
    >
      <div className="app-chat-preview-card__header">
        <div className="app-chat-preview-card__meta">
          <div className="app-chat-preview-card__titles">
            <span className="app-chat-preview-card__label">Codex Diff</span>
            <span className="app-chat-preview-card__kind">{`diff preview · 파일 ${fileCount}개`}</span>
          </div>
        </div>
        <div className="app-chat-preview-card__actions">
          <Button
            type="text"
            size="small"
            className="app-chat-preview-card__action"
            icon={<CopyOutlined />}
            aria-label="diff 복사"
            onClick={handleCopyDiff}
          />
          <Button
            type="text"
            size="small"
            className="app-chat-preview-card__action"
            icon={isFullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
            aria-label={isFullscreen ? 'diff 최대화 해제' : 'diff 최대화'}
            onClick={onToggleFullscreen}
          />
          <Button
            type="text"
            size="small"
            className="app-chat-preview-card__action"
            icon={<DownloadOutlined />}
            aria-label="diff 다운로드"
            onClick={() => {
              downloadTextFile(diffText, 'codex-result.diff', 'text/x-diff;charset=utf-8');
            }}
          />
          <Button
            type="link"
            size="small"
            className="app-chat-preview-card__toggle"
            icon={isExpanded || isFullscreen ? <UpOutlined /> : <DownOutlined />}
            aria-label={isExpanded || isFullscreen ? 'diff 접기' : 'diff 펼치기'}
            onClick={onToggle}
          />
        </div>
      </div>
      {isExpanded || isFullscreen ? (
        <div className="app-chat-preview-card__body">
          <div className="app-chat-panel__preview-rich app-chat-panel__preview-rich--code">
            <CodexDiffBlock
              diffText={diffText}
              showToolbar={false}
            />
          </div>
        </div>
      ) : null}
    </section>
  );

  if (isFullscreen && typeof document !== 'undefined') {
    return createPortal(<div className="app-chat-preview-overlay">{content}</div>, document.body);
  }

  return content;
}

type ChatConversationViewProps = {
  sessionId: string;
  viewportRef: RefObject<HTMLDivElement | null>;
  composerRef: RefObject<TextAreaRef | null>;
  visibleMessages: ChatMessage[];
  activeSystemStatus: string | null;
  isSystemStatusPending: boolean;
  showScrollToBottom: boolean;
  copiedMessageId: number | null;
  draft: string;
  draftVersion: number;
  composerAttachments: ChatComposerAttachment[];
  requestStateMap: Map<string, ChatConversationRequest>;
  lastReadResponseMessageId: number | null;
  isConversationLoading: boolean;
  conversationLoadingLabel: string;
  hasOlderMessages: boolean;
  isLoadingOlderMessages: boolean;
  isPullToLoadArmed: boolean;
  pullToLoadDistance: number;
  selectedChatTypeId: string | null;
  selectedCodexModel: string;
  queuedRequests: QueuedRequestOption[];
  composerAssistActions?: ComposerAssistAction[];
  composerAssistModalTitle?: string;
  chatTypeOptions: ChatTypeOption[];
  codexModelOptions: CodexModelOption[];
  sharedComposerModelTokenLimits?: Record<string, number | null>;
  previewItems: PreviewOption[];
  isResourceStripOpen: boolean;
  showResourceStrip?: boolean;
  showRoomsShareHeader?: boolean;
  roomsShareUseSharedPageNav?: boolean;
  useSharedComposerChrome?: boolean;
  sharedComposerActionVariant?: 'text' | 'icon';
  sharedComposerInputMode?: 'autosize' | 'fixed-scroll';
  useRoomsShareBubbleFlow?: boolean;
  roomsShareHeaderTitle?: string;
  isLiveConnected?: boolean;
  roomsHeaderMenuItems?: MenuProps['items'];
  onRoomsHeaderMenuAction?: (key: string) => void;
  onRoomsHeaderMinimize?: (() => void) | null;
  onRoomsHeaderClose?: (() => void) | null;
  isComposerDisabled: boolean;
  enableExecutionReviewUi?: boolean;
  isMobileViewport: boolean;
  isIpadLikeViewport: boolean;
  isChatTypeSelectionLocked: boolean;
  isComposerAttachmentUploading: boolean;
  isSendWithoutContextEnabled: boolean;
  isImmediateSendPinned: boolean;
  onViewportScroll: () => void;
  onViewportTouchEnd: () => void;
  onViewportTouchMove: (event: TouchEvent<HTMLDivElement>) => void;
  onViewportTouchStart: (event: TouchEvent<HTMLDivElement>) => void;
  onDraftChange: (value: string) => void;
  onPickComposerFiles: (files: File[]) => ComposerFilePickResult | Promise<ComposerFilePickResult>;
  onRemoveComposerAttachment: (attachmentId: string) => void;
  onSelectChatType: (value: string) => void;
  onSelectCodexModel: (value: string) => void;
  onSend: (draftText?: string) => ComposerSendResult;
  onSendReplyToResponse?: (payload: { draftText?: string; parentRequestId: string }) => ComposerSendResult;
  onSendImmediate: (draftText?: string) => ComposerSendResult;
  onToggleImmediateSendPinned: () => void;
  onToggleSendWithoutContext: () => void;
  onClearDraft: () => void;
  onScrollToBottom: () => void;
  onToggleResourceStrip: () => void;
  onLoadOlderMessages?: () => void | Promise<void>;
  onOpenPreview: (preview: OpenPreviewTarget, options?: { fullscreen?: boolean }) => void;
  onCopyMessage: (message: ChatMessage) => void;
  onRetryMessage: (message: ChatMessage) => void;
  onRetryFailedRequest: (request: ChatConversationRequest) => void;
  onCancelMessage: (message: ChatMessage) => void;
  onDeleteRequest: (message: ChatMessage) => void;
  onCompleteManualRequestBadge: (requestId: string, type: 'prompt' | 'verification') => Promise<void> | void;
  onRemoveQueuedRequest: (requestId: string) => void;
  onPromoteQueuedRequest?: (requestId: string, text: string) => void;
  onShareRequestBundle?: (requestId: string) => void;
  onShareInquiryMessage?: (payload: { requestId: string; messageId: number }) => void;
  onSharePromptTarget?: (payload: {
    requestId: string;
    sourceMessageId: number;
    promptIndex: number;
    promptTitle: string;
    promptSignature: string;
  }) => void;
  onSubmitPrompt: (
    payload: PromptSubmitPayload & { parentRequestId?: string | null; promptIndex: number; sourceMessageId: number },
  ) => Promise<boolean>;
  onSubmitChildRequest: (payload: { text: string; parentRequestId: string }) => Promise<boolean>;
};

type ChatComposerInputProps = {
  composerRef: RefObject<TextAreaRef | null>;
  draft: string;
  draftVersion: number;
  forceDraftSyncVersion: number;
  composerPlaceholder: string;
  enableAutoSize?: boolean;
  isComposerDisabled: boolean;
  isComposerAttachmentUploading: boolean;
  queuedRequests: QueuedRequestOption[];
  composerAssistActions?: ComposerAssistAction[];
  composerAssistModalTitle?: string;
  hideAssistTrigger?: boolean;
  hideClearButton?: boolean;
  onPromoteQueuedRequest?: (requestId: string, text: string) => void;
  onRemoveQueuedRequest: (requestId: string) => void;
  onPaste: (event: ClipboardEvent<HTMLTextAreaElement>) => void;
  onCommitDraft: (nextValue: string) => void;
  onLocalDraftChange: (nextValue: string) => void;
  onSend: (nextValue: string) => void;
  onClear: () => void;
};

const ChatComposerInput = memo(function ChatComposerInput({
  composerRef,
  draft,
  draftVersion,
  forceDraftSyncVersion,
  composerPlaceholder,
  enableAutoSize = false,
  isComposerDisabled,
  isComposerAttachmentUploading,
  queuedRequests,
  composerAssistActions,
  composerAssistModalTitle,
  hideAssistTrigger = false,
  hideClearButton = false,
  onPromoteQueuedRequest,
  onRemoveQueuedRequest,
  onPaste,
  onCommitDraft,
  onLocalDraftChange,
  onSend,
  onClear,
}: ChatComposerInputProps) {
  const [localDraft, setLocalDraft] = useState(draft);
  const [hasLocalDraft, setHasLocalDraft] = useState(draft.trim().length > 0);
  const [pendingAssistKey, setPendingAssistKey] = useState('');
  const [isAssistModalOpen, setIsAssistModalOpen] = useState(false);
  const [selectedAssistKeys, setSelectedAssistKeys] = useState<string[]>(() =>
    (composerAssistActions ?? []).map((item) => item.key),
  );
  const isFocusedRef = useRef(false);
  const isComposingRef = useRef(false);
  const lastForceDraftSyncVersionRef = useRef(forceDraftSyncVersion);

  useEffect(() => {
    if (lastForceDraftSyncVersionRef.current !== forceDraftSyncVersion) {
      lastForceDraftSyncVersionRef.current = forceDraftSyncVersion;
      setLocalDraft(draft);
      setHasLocalDraft(draft.trim().length > 0);
      return;
    }

    // While the user is actively typing, keep the textarea driven by local state.
    // The local send/clear handlers already reset the field immediately, so parent
    // draft updates during reconnect/session churn should not wipe in-progress text.
    if ((isFocusedRef.current || isComposingRef.current) && draft !== localDraft) {
      return;
    }

    setLocalDraft(draft);
    setHasLocalDraft(draft.trim().length > 0);
  }, [draft, draftVersion, forceDraftSyncVersion, localDraft]);

  useEffect(() => {
    setSelectedAssistKeys((current) => {
      const nextKeys = (composerAssistActions ?? []).map((item) => item.key);

      if (nextKeys.length === 0) {
        return [];
      }

      const preservedKeys = current.filter((key) => nextKeys.includes(key));
      return preservedKeys.length > 0 ? preservedKeys : nextKeys;
    });
  }, [composerAssistActions]);

  const appendAssistText = (resolvedText: string) => {
    const nextValue = localDraft.trim() ? `${localDraft.trimEnd()}\n${resolvedText}` : resolvedText;
    setLocalDraft(nextValue);
    setHasLocalDraft(true);
    onLocalDraftChange(nextValue);
    composerRef.current?.focus({ cursor: 'end' });
  };

  const handleApplyAssistSelections = async () => {
    const actions = (composerAssistActions ?? []).filter((item) => selectedAssistKeys.includes(item.key));

    if (actions.length === 0) {
      message.warning('추가할 작업환경 항목을 선택하세요.');
      return;
    }

    setPendingAssistKey('assist-modal');

    try {
      const resolvedTexts: string[] = [];

      for (const action of actions) {
        const resolvedText = (await action.resolveText()).trim();
        if (resolvedText) {
          resolvedTexts.push(resolvedText);
        }
      }

      if (resolvedTexts.length === 0) {
        message.warning('추가할 작업환경 정보가 없습니다.');
        return;
      }

      appendAssistText(resolvedTexts.join('\n\n'));
      setIsAssistModalOpen(false);
    } finally {
      setPendingAssistKey((current) => (current === 'assist-modal' ? '' : current));
    }
  };

  const assistDropdownItems = useMemo<MenuProps['items']>(
    () =>
      !hideAssistTrigger && composerAssistActions && composerAssistActions.length > 0
        ? [
            {
              key: 'assist-modal',
              icon: <AppstoreOutlined />,
              label: buildComposerAssistDropdownLabel(composerAssistModalTitle),
            },
          ]
        : [],
    [composerAssistActions, composerAssistModalTitle, hideAssistTrigger],
  );
  const hasAssistDropdown = assistDropdownItems.length > 0;

  return (
    <div
      className={`app-chat-panel__composer-input-shell${
        queuedRequests.length > 0 ? ' app-chat-panel__composer-input-shell--with-queue' : ''
      }${hasAssistDropdown ? ' app-chat-panel__composer-input-shell--with-assist' : ''}${
        enableAutoSize ? ' app-chat-panel__composer-input-shell--autosize' : ''
      }`}
    >
      {queuedRequests.length > 0 ? (
        <div className="app-chat-panel__composer-queue" aria-live="polite">
          <div className="app-chat-panel__composer-queue-count">
            <span>대기열 {queuedRequests.length}건</span>
          </div>
          <div className="app-chat-panel__composer-queue-list">
            {queuedRequests.map((item) => (
              <div key={item.requestId} className="app-chat-panel__composer-queue-chip">
                <div className="app-chat-panel__composer-queue-chip-main">
                  <span className="app-chat-panel__composer-queue-order">{item.order}</span>
                  <span className="app-chat-panel__composer-queue-text">{summarizeQueuedText(item.text)}</span>
                </div>
                <div className="app-chat-panel__composer-queue-chip-actions">
                  <Button
                    type="text"
                    size="small"
                    icon={<ThunderboltOutlined />}
                    aria-label="대기 요청 바로 실행"
                    onClick={() => {
                      onPromoteQueuedRequest?.(item.requestId, item.text);
                    }}
                  />
                  <Button
                    type="text"
                    size="small"
                    danger
                    icon={<CloseOutlined />}
                    aria-label="대기 요청 취소"
                    onClick={() => {
                      onRemoveQueuedRequest(item.requestId);
                    }}
                  />
                </div>
              </div>
            ))}
          </div>
        </div>
      ) : null}

      <Modal
        open={isAssistModalOpen}
        title={composerAssistModalTitle?.trim() || '작업환경 구성'}
        onCancel={() => {
          if (pendingAssistKey === 'assist-modal') {
            return;
          }

          setIsAssistModalOpen(false);
        }}
        onOk={() => {
          void handleApplyAssistSelections();
        }}
        okText="초안에 추가"
        cancelText="닫기"
        confirmLoading={pendingAssistKey === 'assist-modal'}
        destroyOnHidden={false}
      >
        <Alert
          type="info"
          showIcon
          message="현재 채팅방 메뉴 기준으로 필요한 작업환경만 묶어서 초안에 추가합니다."
          className="app-chat-panel__composer-assist-modal-alert"
        />
        <div className="app-chat-panel__composer-assist-modal-list">
          {(composerAssistActions ?? []).map((item) => {
            const isChecked = selectedAssistKeys.includes(item.key);

            return (
              <label key={item.key} className="app-chat-panel__composer-assist-option">
                <div className="app-chat-panel__composer-assist-option-main">
                  <Checkbox
                    checked={isChecked}
                    onChange={(event) => {
                      setSelectedAssistKeys((current) =>
                        event.target.checked ? [...current, item.key] : current.filter((key) => key !== item.key),
                      );
                    }}
                  >
                    <span className="app-chat-panel__composer-assist-option-label">
                      {resolveComposerAssistIcon(item.key)}
                      <span>{item.label}</span>
                    </span>
                  </Checkbox>
                </div>
                {item.description ? (
                  <p className="app-chat-panel__composer-assist-option-description">{item.description}</p>
                ) : null}
              </label>
            );
          })}
        </div>
      </Modal>

      <Input.TextArea
        ref={composerRef}
        value={localDraft}
        autoSize={enableAutoSize ? { minRows: 2, maxRows: 10 } : false}
        placeholder={composerPlaceholder}
        disabled={isComposerDisabled}
        onFocus={() => {
          isFocusedRef.current = true;
        }}
        onChange={(event) => {
          const nextValue = event.target.value;
          setLocalDraft(nextValue);
          setHasLocalDraft(nextValue.trim().length > 0);
          onLocalDraftChange(nextValue);
        }}
        onCompositionStart={() => {
          isComposingRef.current = true;
        }}
        onCompositionEnd={(event) => {
          isComposingRef.current = false;
          const nextValue = event.currentTarget.value;
          setLocalDraft(nextValue);
          setHasLocalDraft(nextValue.trim().length > 0);
          onLocalDraftChange(nextValue);
        }}
        onPaste={onPaste}
        onBlur={() => {
          isFocusedRef.current = false;
          isComposingRef.current = false;
          onCommitDraft(localDraft);
        }}
        onKeyDown={(event) => {
          if (event.key !== 'Enter' || event.nativeEvent.isComposing) {
            return;
          }

          const hasSubmitModifier = event.ctrlKey || event.metaKey;
          if (!hasSubmitModifier) {
            event.stopPropagation();
            return;
          }

          if (isComposerAttachmentUploading) {
            return;
          }

          event.preventDefault();
          event.stopPropagation();
          onSend(localDraft);
        }}
      />
      {hasAssistDropdown ? (
        <Dropdown
          trigger={['click']}
          placement="topRight"
          menu={{
            items: assistDropdownItems,
            onClick: ({ key }) => {
              if (key === 'assist-modal') {
                setIsAssistModalOpen(true);
              }
            },
          }}
        >
          <Button
            type="text"
            size="small"
            icon={<AppstoreOutlined />}
            className="app-chat-panel__composer-assist-trigger"
            title="작업환경 도구"
            aria-label="작업환경 도구"
          />
        </Dropdown>
      ) : null}
      {hideClearButton ? null : (
        <Button
          type="text"
          size="small"
          className={`app-chat-panel__composer-clear${hasLocalDraft ? ' app-chat-panel__composer-clear--visible' : ''}`}
          aria-label="입력창 비우기"
          onClick={() => {
            setLocalDraft('');
            setHasLocalDraft(false);
            onLocalDraftChange('');
            onClear();
          }}
          disabled={!hasLocalDraft}
        >
          clear
        </Button>
      )}
    </div>
  );
});

function SharedRoomsRequestCard({
  request,
  attentionState,
  canCompletePrompt = false,
  canCompleteVerification = false,
  canReplyToResponse = false,
  isManualCompletionSaving = false,
  isReplyReferenceActive = false,
  onCompletePrompt,
  onCompleteVerification,
  onReplyToResponse,
  onSelect,
}: {
  request: ChatConversationRequest;
  attentionState?: SystemExecutionAttentionState;
  canCompletePrompt?: boolean;
  canCompleteVerification?: boolean;
  canReplyToResponse?: boolean;
  isManualCompletionSaving?: boolean;
  isReplyReferenceActive?: boolean;
  onCompletePrompt?: (() => void) | null;
  onCompleteVerification?: (() => void) | null;
  onReplyToResponse?: (() => void) | null;
  onSelect?: (() => void) | null;
}) {
  const questionText = (request.userText ?? "").trim() || "-";
  const answerText = (request.responseText ?? "").trim() || request.statusMessage?.trim() || "아직 답변이 없습니다.";
  const requestStatusLabel = formatRequestStatusLabel(request, attentionState);

  return (
    <section
      className={`app-chat-message-group${onSelect ? ' app-chat-message-group--interactive' : ''}`}
      role={onSelect ? 'button' : undefined}
      tabIndex={onSelect ? 0 : undefined}
      onClick={
        onSelect
          ? () => {
              onSelect();
            }
          : undefined
      }
      onKeyDown={
        onSelect
          ? (event) => {
              if (event.key !== 'Enter' && event.key !== ' ') {
                return;
              }

              event.preventDefault();
              onSelect();
            }
          : undefined
      }
    >
      <header className="app-chat-message-group__header">
        <div className="app-chat-message-group__header-meta">
          {requestStatusLabel ? (
            <span className="app-chat-message-group__detail">{requestStatusLabel}</span>
          ) : null}
        </div>
        <strong className="app-chat-message-group__title">{summarizeQueuedText(questionText, 88)}</strong>
      </header>
      <div className="app-chat-message-group__body">
        <div className="app-chat-bubble app-chat-bubble--user">
          <div className="app-chat-bubble__content">{questionText}</div>
        </div>
        <div className="app-chat-bubble app-chat-bubble--assistant">
          <div className="app-chat-bubble__content">{answerText}</div>
        </div>
      </div>
      {canCompletePrompt || canCompleteVerification || canReplyToResponse ? (
        <div className="app-chat-message__response-actions">
          {canCompletePrompt ? (
            <Button
              type="text"
              size="small"
              className="app-chat-message__response-action"
              icon={<CheckOutlined />}
              loading={isManualCompletionSaving}
              onClick={(event) => {
                event.stopPropagation();
                onCompletePrompt?.();
              }}
            >
              완료 처리
            </Button>
          ) : null}
          {canCompleteVerification ? (
            <Button
              type="text"
              size="small"
              className="app-chat-message__response-action"
              icon={<CheckOutlined />}
              loading={isManualCompletionSaving}
              onClick={(event) => {
                event.stopPropagation();
                onCompleteVerification?.();
              }}
            >
              완료 처리
            </Button>
          ) : null}
          {canReplyToResponse ? (
            <Button
              type="text"
              size="small"
              className={[
                'app-chat-message__response-action',
                'app-chat-message__response-reply-button',
                isReplyReferenceActive ? 'app-chat-message__response-reply-button--active' : '',
              ].filter(Boolean).join(' ')}
              icon={<SendOutlined />}
              onClick={(event) => {
                event.stopPropagation();
                onReplyToResponse?.();
              }}
            >
              {isReplyReferenceActive ? '답변 참조 중' : '답변하기'}
            </Button>
          ) : null}
        </div>
      ) : null}
    </section>
  );
}

export function ChatConversationView({
  sessionId,
  viewportRef,
  composerRef,
  visibleMessages,
  activeSystemStatus,
  isSystemStatusPending,
  showScrollToBottom,
  copiedMessageId,
  draft,
  draftVersion,
  composerAttachments,
  requestStateMap,
  lastReadResponseMessageId,
  isConversationLoading,
  conversationLoadingLabel,
  hasOlderMessages,
  isLoadingOlderMessages,
  isPullToLoadArmed,
  pullToLoadDistance,
  selectedChatTypeId,
  selectedCodexModel,
  queuedRequests,
  composerAssistActions,
  composerAssistModalTitle,
  chatTypeOptions,
  codexModelOptions,
  sharedComposerModelTokenLimits,
  previewItems,
  isResourceStripOpen,
  showResourceStrip = true,
  showRoomsShareHeader = false,
  roomsShareUseSharedPageNav = false,
  useSharedComposerChrome = false,
  sharedComposerActionVariant = 'text',
  sharedComposerInputMode = 'autosize',
  useRoomsShareBubbleFlow = false,
  roomsShareHeaderTitle,
  isLiveConnected = false,
  roomsHeaderMenuItems,
  onRoomsHeaderMenuAction,
  onRoomsHeaderMinimize = null,
  onRoomsHeaderClose = null,
  isComposerDisabled,
  enableExecutionReviewUi = false,
  isMobileViewport,
  isIpadLikeViewport,
  isChatTypeSelectionLocked,
  isComposerAttachmentUploading,
  isSendWithoutContextEnabled,
  isImmediateSendPinned,
  onViewportScroll,
  onViewportTouchEnd,
  onViewportTouchMove,
  onViewportTouchStart,
  onDraftChange,
  onPickComposerFiles,
  onRemoveComposerAttachment,
  onSelectChatType,
  onSelectCodexModel,
  onSend,
  onSendReplyToResponse,
  onSendImmediate,
  onToggleImmediateSendPinned,
  onToggleSendWithoutContext,
  onClearDraft,
  onScrollToBottom,
  onToggleResourceStrip,
  onLoadOlderMessages,
  onOpenPreview,
  onCopyMessage,
  onRetryMessage,
  onRetryFailedRequest,
  onCancelMessage,
  onDeleteRequest,
  onCompleteManualRequestBadge,
  onRemoveQueuedRequest,
  onPromoteQueuedRequest,
  onShareRequestBundle,
  onShareInquiryMessage,
  onSharePromptTarget,
  onSubmitPrompt,
  onSubmitChildRequest,
}: ChatConversationViewProps) {
  const [modalApi, modalContextHolder] = Modal.useModal();
  const useSharedComposerIconActions = useSharedComposerChrome && sharedComposerActionVariant === 'icon';
  const enableSharedComposerAutoSize = useSharedComposerChrome && sharedComposerInputMode !== 'fixed-scroll';
  const [pendingPromptSelections, setPendingPromptSelections] = useState<Record<string, PendingPromptSelection>>(() => {
    const storedSelections = readStoredPromptSelections(sessionId);
    return Object.fromEntries(
      Object.entries(storedSelections).map(([key, selection]) => [
        key,
        {
          ...selection,
          target: {
            type: 'prompt',
            title: selection.promptTitle || 'prompt',
            options: [],
          },
        } satisfies PendingPromptSelection,
      ]),
    );
  });
  const [openedPreviewUrls, setOpenedPreviewUrls] = useState<string[]>([]);
  const [openedPreviewArtifactRequestIds, setOpenedPreviewArtifactRequestIds] = useState<string[]>([]);
  const [expandedResponseMessageIds, setExpandedResponseMessageIds] = useState<number[]>([]);
  const [expandedMessageIds, setExpandedMessageIds] = useState<number[]>([]);
  const [expandedGroupIds, setExpandedGroupIds] = useState<string[]>([]);
  const [openedChildComposerGroupIds, setOpenedChildComposerGroupIds] = useState<string[]>([]);
  const [childComposerDraftsByGroupId, setChildComposerDraftsByGroupId] = useState<Record<string, string>>({});
  const [submittingChildComposerGroupIds, setSubmittingChildComposerGroupIds] = useState<string[]>([]);
  const [expandedPreviewKey, setExpandedPreviewKey] = useState<string | null>(null);
  const [fullscreenPreviewKey, setFullscreenPreviewKey] = useState<string | null>(null);
  const [collapsibleMessageIds, setCollapsibleMessageIds] = useState<number[]>([]);
  const [showBusyOverlay, setShowBusyOverlay] = useState(false);
  const [showLatestResourceOnly, setShowLatestResourceOnly] = useState(true);
  const [pendingComposerUploads, setPendingComposerUploads] = useState<PendingComposerUpload[]>([]);
  const [dismissedSystemExecutionRequestIds, setDismissedSystemExecutionRequestIds] = useState<string[]>([]);
  const [pendingManualCompletionActionKeys, setPendingManualCompletionActionKeys] = useState<string[]>([]);
  const [expandedSystemExecutionActivityRequestId, setExpandedSystemExecutionActivityRequestId] = useState<string | null>(null);
  const [systemExecutionDisplayMode, setSystemExecutionDisplayMode] = useState<SystemExecutionDisplayMode>('collapsed');
  const [systemExecutionFilter, setSystemExecutionFilter] = useState<SystemExecutionFilter>('active-attention');
  const [systemExecutionSort, setSystemExecutionSort] = useState<SystemExecutionSort>('latest');
  const [roomShareExpandMode, setRoomShareExpandMode] = useState<RoomShareExpandMode>('pending');
  const [selectedRoomShareGroupId, setSelectedRoomShareGroupId] = useState<string | null>(null);
  const [replyReferenceRequestId, setReplyReferenceRequestId] = useState('');
  const [composerForceDraftSyncVersion, setComposerForceDraftSyncVersion] = useState(0);
  const fileInputRef = useRef<HTMLInputElement | null>(null);
  const childComposerRefs = useRef(new Map<string, TextAreaRef | null>());
  const messageAnchorRefs = useRef(new Map<number, HTMLDivElement>());
  const messageBodyRefs = useRef(new Map<number, HTMLDivElement>());
  const promptCardAnchorRefs = useRef(new Map<number, HTMLElement>());
  const systemExecutionBodyRef = useRef<HTMLDivElement | null>(null);
  const systemExecutionJumpFrameRef = useRef<number | null>(null);
  const systemExecutionOlderLoadRequestedRef = useRef(false);
  const systemExecutionOlderLoadScrollSnapshotRef = useRef<{ scrollHeight: number; scrollTop: number } | null>(null);
  const previousSystemExecutionDisplayModeRef = useRef<SystemExecutionDisplayMode>('collapsed');
  const previousSessionIdRef = useRef(sessionId);
  const shouldFollowLatestRoomShareGroupRef = useRef(false);
  const pendingRoomShareJumpRef = useRef<
    | {
        groupId: string;
        requestId?: string;
        fallbackMessageId?: number;
      }
    | null
  >(null);
  const immediateSendHoldTimerRef = useRef<number | null>(null);
  const suppressImmediateSendClickRef = useRef(false);
  const composerDraftValueRef = useRef(draft);
  const lastSyncedComposerDraftRef = useRef(draft);

  const setComposerDraftValue = (nextValue: string) => {
    composerDraftValueRef.current = nextValue;
  };

  const syncComposerDraft = (nextValue: string) => {
    if (lastSyncedComposerDraftRef.current === nextValue) {
      return;
    }

    lastSyncedComposerDraftRef.current = nextValue;
    onDraftChange(nextValue);
  };

  const forceComposerDraftSync = () => {
    setComposerForceDraftSyncVersion((current) => current + 1);
  };

  useEffect(() => {
    lastSyncedComposerDraftRef.current = draft;

    if (composerDraftValueRef.current === draft) {
      return;
    }

    if (!draft) {
      composerDraftValueRef.current = '';
      return;
    }

    composerDraftValueRef.current = draft;
  }, [draft, draftVersion]);

  useEffect(() => {
    if (previousSessionIdRef.current === sessionId) {
      return;
    }

    previousSessionIdRef.current = sessionId;
    const storedSelections = readStoredPromptSelections(sessionId);
    setPendingPromptSelections(
      Object.fromEntries(
        Object.entries(storedSelections).map(([key, selection]) => [
          key,
          {
            ...selection,
            target: {
              type: 'prompt',
              title: selection.promptTitle || 'prompt',
              options: [],
            },
          } satisfies PendingPromptSelection,
        ]),
      ),
    );
    setExpandedResponseMessageIds([]);
    setExpandedMessageIds([]);
    setExpandedPreviewKey(null);
    setFullscreenPreviewKey(null);
    setCollapsibleMessageIds([]);
    setOpenedChildComposerGroupIds([]);
    setChildComposerDraftsByGroupId({});
    setReplyReferenceRequestId('');
    setSubmittingChildComposerGroupIds([]);
    setShowBusyOverlay(false);
    setPendingComposerUploads([]);
    setDismissedSystemExecutionRequestIds([]);
    setPendingManualCompletionActionKeys([]);
    setExpandedSystemExecutionActivityRequestId(null);
    setSystemExecutionDisplayMode('collapsed');
    setSystemExecutionFilter('active-attention');
    setSystemExecutionSort('latest');
    systemExecutionOlderLoadScrollSnapshotRef.current = null;
    previousSystemExecutionDisplayModeRef.current = 'collapsed';
  }, [sessionId]);

  useEffect(
    () => () => {
      if (immediateSendHoldTimerRef.current !== null) {
        window.clearTimeout(immediateSendHoldTimerRef.current);
      }

      if (systemExecutionJumpFrameRef.current !== null) {
        window.cancelAnimationFrame(systemExecutionJumpFrameRef.current);
      }
    },
    [],
  );

  useEffect(() => {
    if (!hasOlderMessages || !isLoadingOlderMessages) {
      systemExecutionOlderLoadRequestedRef.current = false;
    }
  }, [hasOlderMessages, isLoadingOlderMessages]);

  const clearImmediateSendHoldTimer = () => {
    if (immediateSendHoldTimerRef.current === null) {
      return;
    }

    window.clearTimeout(immediateSendHoldTimerRef.current);
    immediateSendHoldTimerRef.current = null;
  };

  const startImmediateSendHoldTimer = () => {
    if (isComposerDisabled || isComposerAttachmentUploading) {
      return;
    }

    clearImmediateSendHoldTimer();
    immediateSendHoldTimerRef.current = window.setTimeout(() => {
      immediateSendHoldTimerRef.current = null;
      suppressImmediateSendClickRef.current = true;
      onToggleImmediateSendPinned();
    }, IMMEDIATE_SEND_TOGGLE_HOLD_MS);
  };

  const handleImmediateSendButtonClick = () => {
    if (suppressImmediateSendClickRef.current) {
      suppressImmediateSendClickRef.current = false;
      return;
    }

    const nextDraft = composerDraftValueRef.current;
    if (onSendImmediate(nextDraft) !== 'sent') {
      return;
    }

    lastSyncedComposerDraftRef.current = '';
    composerDraftValueRef.current = '';
    onDraftChange('');
    forceComposerDraftSync();
  };

  const shouldShowConversationLoadingOverlay = isConversationLoading && visibleMessages.length === 0;

  const orderedMessages = useMemo(() => {
    const latestActivityByRequestId = new Map<string, ChatMessage>();
    const orphanActivityMessages: ChatMessage[] = [];
    const baseMessages = visibleMessages.filter((message) => {
      if (!isActivityLogMessage(message)) {
        return true;
      }

      const activityKey = message.clientRequestId?.trim();

      if (!activityKey) {
        orphanActivityMessages.push(message);
        return false;
      }

      const requestState = requestStateMap.get(activityKey);
      if (requestState?.status !== 'removed') {
        latestActivityByRequestId.set(activityKey, message);
      }
      return false;
    });
    const insertedActivityRequestIds = new Set<string>();
    const ordered: ChatMessage[] = [];

    baseMessages.forEach((message) => {
      ordered.push(message);

      if (message.author !== 'user') {
        return;
      }

      const requestId = message.clientRequestId?.trim();

      if (!requestId) {
        return;
      }

      const activityMessage = latestActivityByRequestId.get(requestId);

      if (!activityMessage) {
        return;
      }

      ordered.push(activityMessage);
      insertedActivityRequestIds.add(requestId);
    });

    latestActivityByRequestId.forEach((message, requestId) => {
      if (!insertedActivityRequestIds.has(requestId)) {
        orphanActivityMessages.push(message);
      }
    });

    return [...ordered, ...orphanActivityMessages];
  }, [requestStateMap, visibleMessages]);
  useEffect(() => {
    setRoomShareExpandMode('pending');
  }, [useRoomsShareBubbleFlow]);

  const useSharedRoomsSimplifiedView = showRoomsShareHeader && useSharedComposerChrome && !roomsShareUseSharedPageNav;
  const useSharedRoomsBubbleFlow = showRoomsShareHeader && roomsShareUseSharedPageNav && useRoomsShareBubbleFlow;
  const shouldHideSystemStatusPanel = useSharedRoomsBubbleFlow;
  const messageEntries = useMemo<ConversationMessageEntry[]>(() => {
    if (useSharedRoomsSimplifiedView) {
      return orderedMessages
        .filter((message) => !isActivityLogMessage(message))
        .map(
          (message): ConversationMessageEntry => ({
            kind: 'single',
            key: `message:${message.id}`,
            message,
          }),
        );
    }

    const entries: ConversationMessageEntry[] = [];
    const groupedEntries = new Map<string, Extract<ConversationMessageEntry, { kind: 'group' }>>();

    orderedMessages.forEach((message) => {
      const requestId = message.clientRequestId?.trim() || '';

      if (!requestId) {
        entries.push({
          kind: 'single',
          key: `message:${message.id}`,
          message,
        });
        return;
      }

      const groupRequestId = resolveConversationMessageGroupRequestId(requestId, requestStateMap) || requestId;
      const existingEntry = groupedEntries.get(groupRequestId);

      if (existingEntry) {
        existingEntry.messages.push(message);
        if (!existingEntry.requestIds.includes(requestId)) {
          existingEntry.requestIds.push(requestId);
        }

        if (!existingEntry.request) {
          existingEntry.request = requestStateMap.get(groupRequestId) ?? requestStateMap.get(requestId);
        }

        return;
      }

      const nextEntry: Extract<ConversationMessageEntry, { kind: 'group' }> = {
        kind: 'group',
        key: `request-group:${groupRequestId}`,
        groupId: groupRequestId,
        request: requestStateMap.get(groupRequestId) ?? requestStateMap.get(requestId),
        requestIds: [requestId],
        messages: [message],
      };

      groupedEntries.set(groupRequestId, nextEntry);
      entries.push(nextEntry);
    });

    return entries.filter((entry) =>
      entry.kind === 'single'
        ? !isActivityLogMessage(entry.message)
        : entry.messages.some((message) => !isActivityLogMessage(message)),
    );
  }, [orderedMessages, requestStateMap, useSharedRoomsSimplifiedView]);
  const hasConversationMessages = messageEntries.length > 0;
  const systemExecutionActivityOverviewByRequestId = useMemo(() => {
    const nextMap = new Map<string, SystemExecutionActivityOverview>();

    visibleMessages.forEach((message) => {
      if (!isActivityLogMessage(message)) {
        return;
      }

      const requestId = message.clientRequestId?.trim() || '';

      if (!requestId) {
        return;
      }

      const overview = extractSystemExecutionActivityOverview(
        message.text
          .slice(`${CHAT_ACTIVITY_MESSAGE_PREFIX}\n`.length)
          .split('\n\n')
          .map((line) => line.trim())
          .filter(Boolean),
      );

      if (overview.lines.length === 0 && overview.executors.length === 0) {
        return;
      }

      nextMap.set(requestId, overview);
    });

    return nextMap;
  }, [visibleMessages]);
  useEffect(() => {
    if (systemExecutionDisplayMode === 'expanded') {
      return;
    }

    setExpandedSystemExecutionActivityRequestId(null);
  }, [systemExecutionDisplayMode]);
  const messageRenderPayloadById = useMemo(
    () =>
      new Map(
        orderedMessages.map((message) => [message.id, extractMessageRenderPayload(message)] as const),
      ),
    [orderedMessages],
  );

  useEffect(() => {
    setOpenedPreviewUrls([]);
    setExpandedGroupIds([]);
  }, [sessionId]);

  useEffect(() => {
    const activePromptKeys = new Set<string>();

    orderedMessages.forEach((message) => {
      const { promptTargets } = messageRenderPayloadById.get(message.id) ?? extractMessageRenderPayload(message);

      promptTargets.forEach((target, index) => {
        activePromptKeys.add(buildPendingPromptSelectionKey(message.id, index, target.title, target));
      });
    });

    setPendingPromptSelections((current) => {
      const nextEntries = Object.entries(current).filter(([key]) => activePromptKeys.has(key));

      if (nextEntries.length === Object.keys(current).length) {
        return current;
      }

      return Object.fromEntries(nextEntries);
    });
  }, [messageRenderPayloadById, orderedMessages]);

  useEffect(() => {
    writeStoredPromptSelections(sessionId, pendingPromptSelections);
  }, [pendingPromptSelections, sessionId]);

  const previewItemsByUrl = useMemo(() => new Map(previewItems.map((item) => [item.url, item])), [previewItems]);
  const isChatTypeReadonly = isChatTypeSelectionLocked;
  const selectedChatTypeOption = chatTypeOptions.find((option) => option.value === selectedChatTypeId) ?? null;
  const sharedComposerChatTypeLabel = selectedChatTypeOption?.label?.trim() || '컨텍스트 없음';
  const normalizedSelectedCodexModel = normalizeCodexModel(selectedCodexModel);
  const selectedCodexModelTokenLimit =
    sharedComposerModelTokenLimits?.[normalizedSelectedCodexModel] ??
    sharedComposerModelTokenLimits?.[selectedCodexModel] ??
    null;
  const normalizedSelectedCodexModelTokenLimit = Number.isFinite(selectedCodexModelTokenLimit)
    ? Math.max(0, Math.round(Number(selectedCodexModelTokenLimit)))
    : null;
  const sharedComposerModelUsedTokens = useMemo(() => {
    if (!useSharedComposerChrome) {
      return 0;
    }

    let total = 0;

    requestStateMap.forEach((request) => {
      if (resolveRequestCodexModel(request) !== normalizedSelectedCodexModel) {
        return;
      }

      total += resolveRequestModelTokenUsage(request);
    });

    return total;
  }, [requestStateMap, normalizedSelectedCodexModel, useSharedComposerChrome]);
  const sharedComposerModelRemainingTokensText = useMemo(() => {
    if (normalizedSelectedCodexModelTokenLimit === null) {
      return `이번 세션 사용량 ${sharedComposerModelUsedTokens.toLocaleString('ko-KR')} 토큰`;
    }

    const remainingTokens = Math.max(0, normalizedSelectedCodexModelTokenLimit - sharedComposerModelUsedTokens);

    return `잔여 ${remainingTokens.toLocaleString('ko-KR')} / ${normalizedSelectedCodexModelTokenLimit.toLocaleString(
      'ko-KR',
    )} 토큰`;
  }, [normalizedSelectedCodexModelTokenLimit, sharedComposerModelUsedTokens]);
  const sharedComposerModelTokenLimitLabel =
    sharedComposerModelRemainingTokensText && sharedComposerModelRemainingTokensText.length > 0
      ? sharedComposerModelRemainingTokensText
      : '토큰 정보 없음';
  const pendingManualCompletionActionKeySet = useMemo(
    () => new Set(pendingManualCompletionActionKeys),
    [pendingManualCompletionActionKeys],
  );
  const collapsibleMessageIdSet = useMemo(() => new Set(collapsibleMessageIds), [collapsibleMessageIds]);
  const activityLinesByRequestId = useMemo(() => {
    const nextMap = new Map<string, string[]>();

    orderedMessages.forEach((message) => {
      if (!isActivityLogMessage(message)) {
        return;
      }

      const requestId = message.clientRequestId?.trim();

      if (!requestId) {
        return;
      }

      nextMap.set(
        requestId,
        message.text
          .slice(`${CHAT_ACTIVITY_MESSAGE_PREFIX}\n`.length)
          .split('\n\n')
          .map((line) => line.trim())
          .filter(Boolean),
      );
    });

    return nextMap;
  }, [orderedMessages]);
  const promptTargetsByRequestId = useMemo(() => {
    const nextMap = new Map<string, Extract<ChatMessagePart, { type: 'prompt' }>[]>();

    orderedMessages.forEach((message) => {
      const requestId = message.clientRequestId?.trim();

      if (!requestId || (message.author !== 'codex' && message.author !== 'system')) {
        return;
      }

      const { promptTargets } = messageRenderPayloadById.get(message.id) ?? extractMessageRenderPayload(message);

      if (promptTargets.length === 0) {
        return;
      }

      nextMap.set(requestId, promptTargets);
    });

    return nextMap;
  }, [messageRenderPayloadById, orderedMessages]);
  const firstPromptMessageIdByRequestId = useMemo(() => {
    const nextMap = new Map<string, number>();

    orderedMessages.forEach((message) => {
      const requestId = message.clientRequestId?.trim();

      if (!requestId || nextMap.has(requestId) || (message.author !== 'codex' && message.author !== 'system')) {
        return;
      }

      const { promptTargets } = messageRenderPayloadById.get(message.id) ?? extractMessageRenderPayload(message);

      if (promptTargets.length === 0) {
        return;
      }

      nextMap.set(requestId, message.id);
    });

    return nextMap;
  }, [messageRenderPayloadById, orderedMessages]);
  const promptFollowupCountByParentRequestId = useMemo(() => {
    const nextMap = new Map<string, number>();

    Array.from(requestStateMap.values()).forEach((request) => {
      if (request.requestOrigin !== 'prompt') {
        return;
      }

      const parentRequestId = request.parentRequestId?.trim();

      if (!parentRequestId) {
        return;
      }

      nextMap.set(parentRequestId, (nextMap.get(parentRequestId) ?? 0) + 1);
    });

    return nextMap;
  }, [requestStateMap]);
  const localSubmittedPromptCountByRequestId = useMemo(() => {
    const nextMap = new Map<string, number>();

    Object.values(pendingPromptSelections).forEach((selection) => {
      const requestId = selection.requestId?.trim();

      if (!requestId || selection.status !== 'submitted') {
        return;
      }

      nextMap.set(requestId, (nextMap.get(requestId) ?? 0) + 1);
    });

    return nextMap;
  }, [pendingPromptSelections]);
  const previewResourceRequestIdsByUrl = useMemo(() => {
    const nextMap = new Map<string, Set<string>>();

    orderedMessages.forEach((message) => {
      const requestId = message.clientRequestId?.trim();

      if (!requestId || (message.author !== 'codex' && message.author !== 'system')) {
        return;
      }

      const { previewSourceText, promptTargets } =
        messageRenderPayloadById.get(message.id) ?? extractMessageRenderPayload(message);
      const inlinePreviewTargets = extractInlinePreviewTargets(previewSourceText);
      const promptPreviewUrls = extractPromptPreviewUrls(promptTargets);

      inlinePreviewTargets.forEach((target) => {
        const normalizedUrl = normalizeChatResourceUrl(target.url);
        const currentRequestIds = nextMap.get(normalizedUrl) ?? new Set<string>();
        currentRequestIds.add(requestId);
        nextMap.set(normalizedUrl, currentRequestIds);
      });

      promptPreviewUrls.forEach((url) => {
        const normalizedUrl = normalizeChatResourceUrl(url);
        const currentRequestIds = nextMap.get(normalizedUrl) ?? new Set<string>();
        currentRequestIds.add(requestId);
        nextMap.set(normalizedUrl, currentRequestIds);
      });
    });

    return nextMap;
  }, [messageRenderPayloadById, orderedMessages]);
  const requestIdByMessageId = useMemo(() => {
    const nextMap = new Map<number, string>();

    orderedMessages.forEach((message) => {
      const requestId = message.clientRequestId?.trim();

      if (requestId) {
        nextMap.set(message.id, requestId);
      }
    });

    return nextMap;
  }, [orderedMessages]);
  const openedPreviewRequestIds = useMemo(() => {
    const nextSet = new Set<string>();

    openedPreviewUrls.forEach((url) => {
      const requestIds = previewResourceRequestIdsByUrl.get(normalizeChatResourceUrl(url));

      if (!requestIds) {
        return;
      }

      requestIds.forEach((requestId) => nextSet.add(requestId));
    });

    return nextSet;
  }, [openedPreviewUrls, previewResourceRequestIdsByUrl]);
  const expandedResponseRequestIds = useMemo(() => {
    const nextSet = new Set<string>();

    expandedResponseMessageIds.forEach((messageId) => {
      const requestId = requestIdByMessageId.get(messageId);

      if (requestId) {
        nextSet.add(requestId);
      }
    });

    return nextSet;
  }, [expandedResponseMessageIds, requestIdByMessageId]);
  const requestIdsWithPreviewArtifacts = useMemo(() => {
    const nextSet = new Set<string>();

    orderedMessages.forEach((message) => {
      const requestId = message.clientRequestId?.trim();

      if (!requestId || (message.author !== 'codex' && message.author !== 'system')) {
        return;
      }

      const { previewSourceText, diffBlocks, rankedLinkTargets, linkCardTargets, promptTargets } =
        messageRenderPayloadById.get(message.id) ?? extractMessageRenderPayload(message);
      const inlinePreviewTargets = extractInlinePreviewTargets(previewSourceText);

      if (
        diffBlocks.length > 0 ||
        inlinePreviewTargets.length > 0 ||
        rankedLinkTargets.length > 0 ||
        linkCardTargets.length > 0 ||
        hasPromptPreviewArtifact(promptTargets)
      ) {
        nextSet.add(requestId);
      }
    });

    return nextSet;
  }, [messageRenderPayloadById, orderedMessages]);
  const openedPreviewArtifactRequestIdSet = useMemo(
    () => new Set(openedPreviewArtifactRequestIds),
    [openedPreviewArtifactRequestIds],
  );
  const systemExecutionRequests = useMemo(
    () =>
      Array.from(requestStateMap.values())
        .filter((request) => request.status !== 'removed')
        .sort((left, right) => left.createdAt.localeCompare(right.createdAt)),
    [requestStateMap],
  );
  const visibleSystemExecutionRequests = useMemo(
    () =>
      systemExecutionRequests.filter(
        (request) => !dismissedSystemExecutionRequestIds.includes(request.requestId),
      ),
    [dismissedSystemExecutionRequestIds, systemExecutionRequests],
  );
  const childRequestIdsByParentRequestId = useMemo(() => {
    const nextMap = new Map<string, string[]>();

    Array.from(requestStateMap.values()).forEach((request) => {
      const parentRequestId = request.parentRequestId?.trim() || '';
      const requestId = request.requestId.trim();

      if (!parentRequestId || !requestId || parentRequestId === requestId) {
        return;
      }

      const currentChildRequestIds = nextMap.get(parentRequestId) ?? [];

      if (!currentChildRequestIds.includes(requestId)) {
        currentChildRequestIds.push(requestId);
      }

      nextMap.set(parentRequestId, currentChildRequestIds);
    });

    return nextMap;
  }, [requestStateMap]);
  const activeSystemExecutionRequests = useMemo(
    () =>
      visibleSystemExecutionRequests.filter(
        (request) =>
          !isPromptFollowupRequest(request)
          && (request.status === 'accepted' || request.status === 'queued' || request.status === 'started'),
      ),
    [visibleSystemExecutionRequests],
  );
  const systemExecutionSortOptions = useMemo(
    () => [
      { value: 'latest', label: '최신순' },
      { value: 'answered', label: '답변순' },
      { value: 'status', label: '상태 우선' },
    ] satisfies { value: SystemExecutionSort; label: string }[],
    [],
  );
  const systemExecutionFilterOptions = useMemo(
    () => [
      { value: 'all', label: '전체', compactLabel: '전체', ariaLabel: '전체 실행 기록 보기' },
      { value: 'active', label: '처리중', compactLabel: '처리', ariaLabel: '처리중인 실행 기록만 보기' },
      {
        value: 'active-attention',
        label: '처리+확인',
        compactLabel: '처리·확인',
        ariaLabel: '처리중이거나 확인이 필요한 실행 기록 보기',
      },
      {
        value: 'attention',
        label: '확인필요',
        compactLabel: '확인',
        ariaLabel: '확인 필요한 실행 기록만 보기',
      },
    ] satisfies SystemExecutionFilterOption[],
    [],
  );
  const currentSystemExecutionFilterOption =
    systemExecutionFilterOptions.find((option) => option.value === systemExecutionFilter) ?? systemExecutionFilterOptions[0];
  const mobileSystemExecutionNextFilterOption =
    systemExecutionFilterOptions[
      (systemExecutionFilterOptions.findIndex((option) => option.value === systemExecutionFilter) + 1) %
        systemExecutionFilterOptions.length
    ] ?? systemExecutionFilterOptions[0];
  const useCompactSystemExecutionControls = isIpadLikeViewport;
  const useIconSystemExecutionFilterToggle = isMobileViewport || useCompactSystemExecutionControls;
  const canLoadOlderSystemExecutionRequests = systemExecutionFilter === 'all' && Boolean(onLoadOlderMessages);
  const requestIdsByRootRequestId = useMemo(() => {
    const nextMap = new Map<string, string[]>();

    Array.from(requestStateMap.values()).forEach((request) => {
      const rootRequestId = resolveConversationRootRequestId(request.requestId, requestStateMap) || request.requestId;
      const currentRequestIds = nextMap.get(rootRequestId) ?? [];
      currentRequestIds.push(request.requestId);
      nextMap.set(rootRequestId, currentRequestIds);
    });

    return nextMap;
  }, [requestStateMap]);
  const systemExecutionAttentionStateByRequestId = useMemo(() => {
    const nextMap = new Map<string, SystemExecutionAttentionState>();

    visibleSystemExecutionRequests.forEach((request) => {
      if (isPromptFollowupRequest(request)) {
        nextMap.set(request.requestId, {
          activityLines: [],
          promptTargets: [],
          promptSubmittedCount: 0,
          isPromptManuallyCompleted: true,
          hasVerificationTarget: false,
          hasConfirmedVerificationTarget: true,
          isVerificationManuallyCompleted: true,
          hasPendingPromptBadge: false,
          hasPendingVerificationBadge: false,
          hasOwnAttentionState: false,
        });
        return;
      }

      const activityLines = activityLinesByRequestId.get(request.requestId) ?? [];
      const promptTargets = promptTargetsByRequestId.get(request.requestId) ?? [];
      const promptSubmittedCount =
        Math.max(
          promptFollowupCountByParentRequestId.get(request.requestId) ?? 0,
          localSubmittedPromptCountByRequestId.get(request.requestId) ?? 0,
        );
      const isPromptManuallyCompleted = Boolean(request.manualPromptCompletedAt);
      const hasVerificationTarget =
        (request.status === 'completed' && !request.hasResponse) ||
        request.hasResponse ||
        requestIdsWithPreviewArtifacts.has(request.requestId) ||
        (typeof request.responseMessageId === 'number' && collapsibleMessageIdSet.has(request.responseMessageId));
      const hasConfirmedVerificationTarget =
        expandedResponseRequestIds.has(request.requestId) ||
        openedPreviewRequestIds.has(request.requestId) ||
        openedPreviewArtifactRequestIdSet.has(request.requestId);
      const isVerificationManuallyCompleted = Boolean(request.manualVerificationCompletedAt);
      const hasPendingPromptBadge =
        !isPromptManuallyCompleted &&
        hasPendingPromptState({
          promptTargets,
          submittedCount: promptSubmittedCount,
        });
      const hasPendingVerificationBadge =
        !isVerificationManuallyCompleted &&
        hasPendingVerificationState({
          request,
          activityLines,
          promptTargets,
          hasVerificationTarget,
          hasConfirmedVerificationTarget,
          isPromptManuallyCompleted,
        });
      const hasOwnAttentionState =
        hasPendingPromptBadge || hasPendingVerificationBadge || isDisconnectedRequestNeedingAttention(request);

      nextMap.set(request.requestId, {
        activityLines,
        promptTargets,
        promptSubmittedCount,
        isPromptManuallyCompleted,
        hasVerificationTarget,
        hasConfirmedVerificationTarget,
        isVerificationManuallyCompleted,
        hasPendingPromptBadge,
        hasPendingVerificationBadge,
        hasOwnAttentionState,
      });
    });

    return nextMap;
  }, [
    activityLinesByRequestId,
    collapsibleMessageIdSet,
    expandedResponseRequestIds,
    lastReadResponseMessageId,
    openedPreviewArtifactRequestIdSet,
    openedPreviewRequestIds,
    childRequestIdsByParentRequestId,
    localSubmittedPromptCountByRequestId,
    promptFollowupCountByParentRequestId,
    promptTargetsByRequestId,
    requestIdsWithPreviewArtifacts,
    visibleSystemExecutionRequests,
  ]);
  const filteredSystemExecutionRequests = useMemo(
    () => {
      if (systemExecutionFilter === 'all') {
        return visibleSystemExecutionRequests;
      }

      if (systemExecutionFilter === 'active') {
        return visibleSystemExecutionRequests.filter(
          (request) =>
            !isPromptFollowupRequest(request)
            && (request.status === 'accepted' || request.status === 'queued' || request.status === 'started'),
        );
      }

      if (systemExecutionFilter === 'active-attention') {
        return visibleSystemExecutionRequests.filter((request) => {
          if (isPromptFollowupRequest(request)) {
            return false;
          }

          if (request.status === 'accepted' || request.status === 'queued' || request.status === 'started') {
            return true;
          }

          return systemExecutionAttentionStateByRequestId.get(request.requestId)?.hasOwnAttentionState === true;
        });
      }

      return visibleSystemExecutionRequests.filter(
        (request) =>
          !isPromptFollowupRequest(request)
          && systemExecutionAttentionStateByRequestId.get(request.requestId)?.hasOwnAttentionState === true,
      );
    },
    [systemExecutionAttentionStateByRequestId, visibleSystemExecutionRequests, systemExecutionFilter],
  );
  const filteredSystemExecutionRequestIdSet = useMemo(
    () => new Set(filteredSystemExecutionRequests.map((request) => request.requestId)),
    [filteredSystemExecutionRequests],
  );
  const useFlatSystemExecutionFilterView = systemExecutionFilter !== 'all';
  const orderedFilteredSystemExecutionRequests = useMemo(
    () =>
      [...filteredSystemExecutionRequests].sort((left, right) =>
        compareSystemExecutionRequests(left, right, systemExecutionSort),
      ),
    [filteredSystemExecutionRequests, systemExecutionSort],
  );
  const orderedSystemExecutionRootRequests = useMemo(() => {
    const rootRequestsById = new Map<string, ChatConversationRequest>();

    filteredSystemExecutionRequests.forEach((request) => {
      const rootRequestId = resolveConversationRootRequestId(request.requestId, requestStateMap) || request.requestId;
      const rootRequest = requestStateMap.get(rootRequestId) ?? request;
      const currentRootRequest = rootRequestsById.get(rootRequest.requestId);

      if (!currentRootRequest) {
        rootRequestsById.set(rootRequest.requestId, rootRequest);
        return;
      }

      if (compareSystemExecutionRequests(rootRequest, currentRootRequest, systemExecutionSort) < 0) {
        rootRequestsById.set(rootRequest.requestId, rootRequest);
      }
    });

    const orderedRootRequests = Array.from(rootRequestsById.values()).sort((left, right) =>
      compareSystemExecutionRequests(left, right, systemExecutionSort),
    );
    return orderedRootRequests;
  }, [
    filteredSystemExecutionRequests,
    requestStateMap,
    systemExecutionSort,
  ]);
  useEffect(() => {
    if (!expandedSystemExecutionActivityRequestId) {
      return;
    }

    const expandedRequestStillVisible = orderedSystemExecutionRootRequests.some((request) => {
      const groupedRequestIds = requestIdsByRootRequestId.get(request.requestId) ?? [request.requestId];
      return groupedRequestIds.includes(expandedSystemExecutionActivityRequestId);
    });

    if (!expandedRequestStillVisible) {
      setExpandedSystemExecutionActivityRequestId(null);
    }
  }, [
    expandedSystemExecutionActivityRequestId,
    orderedSystemExecutionRootRequests,
    requestIdsByRootRequestId,
  ]);
  const collapsedVisibleSystemExecutionRequest =
    useFlatSystemExecutionFilterView
      ? orderedFilteredSystemExecutionRequests[orderedFilteredSystemExecutionRequests.length - 1] ?? null
      : orderedSystemExecutionRootRequests[orderedSystemExecutionRootRequests.length - 1] ?? null;
  const activeSystemStatusText = activeSystemStatus?.trim() || null;
  const hiddenSystemExecutionCountText = useMemo(() => {
    const actualExecutionCount = activeSystemExecutionRequests.length;

    if (actualExecutionCount <= 0) {
      return null;
    }

    return `${actualExecutionCount}건 실행 중`;
  }, [activeSystemExecutionRequests.length]);
  const hiddenSystemStatusSummaryText = hiddenSystemExecutionCountText ?? activeSystemStatusText;
  const roomShareRequestGroups = useMemo(
    () => {
      if (useSharedRoomsSimplifiedView) {
        return Array.from(requestStateMap.values())
          .sort((left, right) => left.createdAt.localeCompare(right.createdAt))
          .map((request) => {
            const groupedRequests = [request];
            const statusSummary = resolveAggregatedRequestStatusSummary(groupedRequests, systemExecutionAttentionStateByRequestId);
            const hasAttention =
              isRequestRunningStatus(request.status) ||
              isRequestQueueStatus(request.status) ||
              systemExecutionAttentionStateByRequestId.get(request.requestId)?.hasOwnAttentionState === true;
            const messages = orderedMessages.filter((message) => {
              if (isActivityLogMessage(message)) {
                return false;
              }

              const requestId = message.clientRequestId?.trim() || '';

              if (!requestId) {
                return false;
              }

              return (resolveConversationMessageGroupRequestId(requestId, requestStateMap) || requestId) === request.requestId;
            });

            return {
              groupId: request.requestId,
              groupedRequests,
              representativeRequest: request,
              statusSummary,
              hasAttention,
              messages,
            };
          });
      }

      const groupRequestIdsByRootId = new Map<string, string[]>();

      requestIdsByRootRequestId.forEach((requestIds, rootRequestId) => {
        const dedupedRequestIds = Array.from(new Set(requestIds.map((requestId) => requestId.trim()).filter(Boolean)));

        if (dedupedRequestIds.length > 0) {
          groupRequestIdsByRootId.set(rootRequestId, dedupedRequestIds);
        }
      });

      const groupedEntries = messageEntries
        .filter((entry): entry is Extract<ConversationMessageEntry, { kind: 'group' }> => entry.kind === 'group')
        .map((entry) => {
          const groupedRequestIds = groupRequestIdsByRootId.get(entry.groupId) ?? entry.requestIds;
          const groupedRequests = Array.from(
            new Map(
              groupedRequestIds
                .map((requestId) => requestStateMap.get(requestId))
                .filter((item): item is ChatConversationRequest => item != null)
                .map((request) => [request.requestId, request] as const),
            ).values(),
          );
          const representativeRequest =
            entry.request && groupedRequests.some((request) => request.requestId === entry.request?.requestId)
              ? entry.request
              : groupedRequests[groupedRequests.length - 1] ?? entry.request;
          const statusSummary = resolveAggregatedRequestStatusSummary(groupedRequests, systemExecutionAttentionStateByRequestId);
          const hasAttention = groupedRequests.some((request) => {
            if (isRequestRunningStatus(request.status) || isRequestQueueStatus(request.status)) {
              return true;
            }

            return systemExecutionAttentionStateByRequestId.get(request.requestId)?.hasOwnAttentionState === true;
          });

          return {
            groupId: entry.groupId,
            groupedRequests,
            representativeRequest,
            statusSummary,
            hasAttention,
          };
        });
      const existingGroupIdSet = new Set(groupedEntries.map((entry) => entry.groupId));
      const pendingOnlyGroups = Array.from(groupRequestIdsByRootId.entries())
        .filter(([groupId]) => !existingGroupIdSet.has(groupId))
        .map(([groupId, groupedRequestIds]) => {
          const groupedRequests = Array.from(
            new Map(
              groupedRequestIds
                .map((requestId) => requestStateMap.get(requestId))
                .filter((item): item is ChatConversationRequest => item != null)
                .map((request) => [request.requestId, request] as const),
            ).values(),
          );

          if (groupedRequests.length === 0) {
            return null;
          }

          const hasAttention = groupedRequests.some((request) => {
            if (isRequestRunningStatus(request.status) || isRequestQueueStatus(request.status)) {
              return true;
            }

            return systemExecutionAttentionStateByRequestId.get(request.requestId)?.hasOwnAttentionState === true;
          });

          if (!hasAttention) {
            return null;
          }

          const representativeRequest = groupedRequests[groupedRequests.length - 1] ?? null;

          return {
            groupId,
            groupedRequests,
            representativeRequest,
            statusSummary: resolveAggregatedRequestStatusSummary(groupedRequests, systemExecutionAttentionStateByRequestId),
            hasAttention,
          };
        })
        .filter((entry): entry is NonNullable<typeof entry> => entry != null);

      return [...groupedEntries, ...pendingOnlyGroups]
        .filter((entry) => entry.representativeRequest != null)
        .sort((left, right) => left.representativeRequest.createdAt.localeCompare(right.representativeRequest.createdAt));
    },
     [
       messageEntries,
       orderedMessages,
       requestIdsByRootRequestId,
       requestStateMap,
       systemExecutionAttentionStateByRequestId,
       useSharedRoomsSimplifiedView,
     ],
  );
  const roomShareNavigableGroups = useMemo(() => {
    if (roomShareExpandMode === 'pending') {
      return roomShareRequestGroups.filter((entry) => entry.hasAttention);
    }

    return roomShareRequestGroups;
  }, [roomShareExpandMode, roomShareRequestGroups]);
  const roomShareGroupIdByRequestId = useMemo(() => {
    const nextMap = new Map<string, string>();

    roomShareRequestGroups.forEach((entry) => {
      entry.groupedRequests.forEach((groupedRequest) => {
        const normalizedRequestId = groupedRequest.requestId.trim();

        if (normalizedRequestId) {
          nextMap.set(normalizedRequestId, entry.groupId);
        }
      });
    });

    return nextMap;
  }, [roomShareRequestGroups]);
  useEffect(() => {
    if (!showRoomsShareHeader) {
      return;
    }

    if (roomShareRequestGroups.length === 0) {
      if (selectedRoomShareGroupId !== null) {
        setSelectedRoomShareGroupId(null);
      }
      shouldFollowLatestRoomShareGroupRef.current = false;
      return;
    }

    if (roomShareExpandMode === 'latest' && shouldFollowLatestRoomShareGroupRef.current) {
      const nextLatestGroupId = roomShareRequestGroups[roomShareRequestGroups.length - 1]?.groupId ?? null;
      shouldFollowLatestRoomShareGroupRef.current = false;

      if (nextLatestGroupId && nextLatestGroupId !== selectedRoomShareGroupId) {
        pendingRoomShareJumpRef.current = {
          groupId: nextLatestGroupId,
        };
        setSelectedRoomShareGroupId(nextLatestGroupId);
      }
      return;
    }

    if (
      selectedRoomShareGroupId &&
      roomShareRequestGroups.some((entry) => entry.groupId === selectedRoomShareGroupId)
    ) {
      return;
    }

    setSelectedRoomShareGroupId(roomShareRequestGroups[roomShareRequestGroups.length - 1]?.groupId ?? null);
  }, [roomShareExpandMode, roomShareRequestGroups, selectedRoomShareGroupId, showRoomsShareHeader]);
  const selectedRoomShareGroup = useMemo(
    () => roomShareRequestGroups.find((entry) => entry.groupId === selectedRoomShareGroupId) ?? null,
    [roomShareRequestGroups, selectedRoomShareGroupId],
  );
  const selectedRoomShareGroupIndex = useMemo(
    () => roomShareRequestGroups.findIndex((entry) => entry.groupId === selectedRoomShareGroupId),
    [roomShareRequestGroups, selectedRoomShareGroupId],
  );
  const canMoveToPreviousRoomShareGroup = roomShareExpandMode === 'latest' && selectedRoomShareGroupIndex > 0;
  const canMoveToNextRoomShareGroup =
    roomShareExpandMode === 'latest' &&
    selectedRoomShareGroupIndex >= 0 &&
    selectedRoomShareGroupIndex < roomShareRequestGroups.length - 1;
  const roomShareVisibleGroupIdSet = useMemo(() => {
    if (!showRoomsShareHeader) {
      return null;
    }

    if (roomShareExpandMode === 'latest') {
      return new Set(selectedRoomShareGroup?.groupId ? [selectedRoomShareGroup.groupId] : []);
    }

    return new Set(roomShareNavigableGroups.map((entry) => entry.groupId));
  }, [roomShareExpandMode, roomShareNavigableGroups, selectedRoomShareGroup, showRoomsShareHeader]);
  const roomShareAllRequests = useMemo(
    () => roomShareRequestGroups.flatMap((entry) => entry.groupedRequests),
    [roomShareRequestGroups],
  );
  const roomShareAggregateStatusSummary = useMemo(
    () => resolveAggregatedRequestStatusSummary(roomShareAllRequests, systemExecutionAttentionStateByRequestId),
    [roomShareAllRequests, systemExecutionAttentionStateByRequestId],
  );
  const roomShareMenuTitle = useMemo(() => {
    const normalizedTitle = roomsShareHeaderTitle?.trim() || "현재 메뉴";
    const menuSegments = normalizedTitle
      .replaceAll("｜", ">")
      .replaceAll("/", ">")
      .split(">")
      .map((item) => item.trim())
      .filter(Boolean);
    return menuSegments.at(-1) || normalizedTitle;
  }, [roomsShareHeaderTitle]);
  const roomShareHeaderSummaryLabel = useMemo(() => {
    const nowMs = Date.now();
    const processingRequests = roomShareAllRequests.filter(
      (request) => isRequestRunningStatus(request.status) || isRequestQueueStatus(request.status),
    ).filter((request) => !isPromptFollowupRequest(request));
    const processingTarget = processingRequests[processingRequests.length - 1] ?? null;
    const elapsedLabel = processingTarget ? formatOngoingElapsedLabel(processingTarget.createdAt, nowMs) : '';
    const processingCount = processingRequests.length;
    const attentionCount = roomShareRequestGroups.filter((entry) => entry.hasAttention).length;

    return `처리 시간 ${elapsedLabel || '-'} · 처리 건수 ${processingCount}건 · 미확인 ${attentionCount}건`;
  }, [roomShareAllRequests, roomShareRequestGroups]);
  const roomShareCurrentGroup = selectedRoomShareGroup ?? roomShareRequestGroups[roomShareRequestGroups.length - 1] ?? null;
  const roomShareHiddenBeforeCount =
    roomShareExpandMode === 'latest' && selectedRoomShareGroupIndex >= 0 ? selectedRoomShareGroupIndex : 0;
  const roomShareHiddenAfterCount =
    roomShareExpandMode === 'latest' && selectedRoomShareGroupIndex >= 0
      ? Math.max(0, roomShareRequestGroups.length - selectedRoomShareGroupIndex - 1)
      : 0;
  const roomShareProgressLabel =
    roomShareRequestGroups.length === 0
      ? ''
      : roomShareExpandMode === 'all'
        ? `전체 ${roomShareRequestGroups.length}건`
        : roomShareExpandMode === 'pending'
          ? `처리중·미확인 ${roomShareNavigableGroups.length}건`
          : `${selectedRoomShareGroupIndex + 1} / ${roomShareRequestGroups.length}`;
  const roomShareCollapsedActivitySummary = useMemo(() => {
    const representativeRequest = roomShareCurrentGroup?.representativeRequest ?? null;

    if (!representativeRequest || roomShareExpandMode !== 'latest' || !(isRequestRunningStatus(representativeRequest.status) || isRequestQueueStatus(representativeRequest.status))) {
      return [] as string[];
    }

    return summarizeRoomShareActivityLines(
      systemExecutionAttentionStateByRequestId.get(representativeRequest.requestId)?.activityLines ?? [],
    );
  }, [roomShareCurrentGroup, roomShareExpandMode, systemExecutionAttentionStateByRequestId]);
  const roomShareExpandModeMenuItems = useMemo<MenuProps['items']>(
    () => [
      { key: 'latest', label: ROOM_SHARE_EXPAND_MODE_LABELS.latest },
      { key: 'pending', label: ROOM_SHARE_EXPAND_MODE_LABELS.pending },
      { key: 'all', label: ROOM_SHARE_EXPAND_MODE_LABELS.all },
    ],
    [],
  );
  const expandedSystemExecutionActivityRequest = useMemo(() => {
    const requestId = expandedSystemExecutionActivityRequestId?.trim() || '';

    if (!requestId) {
      return null;
    }

    return requestStateMap.get(requestId) ?? null;
  }, [expandedSystemExecutionActivityRequestId, requestStateMap]);
  const expandedSystemExecutionActivityPanels = useMemo(() => {
    if (!expandedSystemExecutionActivityRequest) {
      return [];
    }

    const expandedRootRequest =
      orderedSystemExecutionRootRequests.find((rootRequest) => {
        const groupedRequestIds = requestIdsByRootRequestId.get(rootRequest.requestId) ?? [rootRequest.requestId];
        return groupedRequestIds.includes(expandedSystemExecutionActivityRequest.requestId);
      }) ?? expandedSystemExecutionActivityRequest;
    const groupedRequestIds = requestIdsByRootRequestId.get(expandedRootRequest.requestId) ?? [
      expandedSystemExecutionActivityRequest.requestId,
    ];
    const groupedRequests = groupedRequestIds
      .map((requestId) => requestStateMap.get(requestId))
      .filter((request): request is ChatConversationRequest => request != null);
    const visibleRequests = resolveVisibleActivityOverviewRequests(
      groupedRequests,
      systemExecutionActivityOverviewByRequestId,
      systemExecutionAttentionStateByRequestId,
    ).sort((left, right) => compareRepresentativeRequests(left, right, systemExecutionAttentionStateByRequestId));

    return visibleRequests
      .map((request) => {
        const overview = systemExecutionActivityOverviewByRequestId.get(request.requestId) ?? null;

        if (!overview) {
          return null;
        }

        const attentionState = systemExecutionAttentionStateByRequestId.get(request.requestId) ?? null;

        return {
          request,
          overview,
          attentionState,
          summaryText: summarizeSystemExecutionRequestText(request, {
            preferResponse: false,
            maxLength: resolveSystemExecutionSummaryMaxLength(),
          }),
        };
      })
      .filter(
        (
          panel,
        ): panel is {
          request: ChatConversationRequest;
          overview: SystemExecutionActivityOverview;
          attentionState: SystemExecutionAttentionState | null;
          summaryText: string;
        } => panel != null,
      );
  }, [
    expandedSystemExecutionActivityRequest,
    orderedSystemExecutionRootRequests,
    requestIdsByRootRequestId,
    requestStateMap,
    systemExecutionActivityOverviewByRequestId,
    systemExecutionAttentionStateByRequestId,
  ]);
  const hasExpandedSystemExecutionActivityContent = expandedSystemExecutionActivityPanels.length > 0;
  const systemExecutionMeta = useMemo(() => {
    const actualExecutionCount = activeSystemExecutionRequests.length;

    if (actualExecutionCount > 0) {
      return `실제 실행 ${actualExecutionCount}건`;
    }

    return activeSystemStatusText;
  }, [activeSystemExecutionRequests.length, activeSystemStatusText]);
  const visiblePreviewItems = useMemo(() => {
    if (!showLatestResourceOnly) {
      return previewItems;
    }

    const seenFileNames = new Set<string>();
    return previewItems.filter((item) => {
      const fileName = buildPreviewFileName(item);

      if (seenFileNames.has(fileName)) {
        return false;
      }

      seenFileNames.add(fileName);
      return true;
    });
  }, [previewItems, showLatestResourceOnly]);

  useEffect(() => {
    if (expandedSystemExecutionActivityRequestId && !hasExpandedSystemExecutionActivityContent) {
      setExpandedSystemExecutionActivityRequestId(null);
    }
  }, [
    expandedSystemExecutionActivityRequestId,
    hasExpandedSystemExecutionActivityContent,
  ]);

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

    const { body, documentElement } = document;
    const previousBodyOverflow = body.style.overflow;
    const previousHtmlOverflow = documentElement.style.overflow;

    if (fullscreenPreviewKey) {
      body.style.overflow = 'hidden';
      documentElement.style.overflow = 'hidden';
    }

    return () => {
      body.style.overflow = previousBodyOverflow;
      documentElement.style.overflow = previousHtmlOverflow;
    };
  }, [fullscreenPreviewKey]);

  const setMessageAnchorRef = (messageId: number, element: HTMLDivElement | null) => {
    if (element) {
      messageAnchorRefs.current.set(messageId, element);
      return;
    }

    messageAnchorRefs.current.delete(messageId);
  };

  const setMessageBodyRef = (messageId: number, element: HTMLDivElement | null) => {
    if (element) {
      messageBodyRefs.current.set(messageId, element);
      return;
    }

    messageBodyRefs.current.delete(messageId);
  };
  const setPromptCardAnchorRef = (messageId: number, element: HTMLElement | null) => {
    if (!Number.isFinite(messageId)) {
      return;
    }

    if (element) {
      promptCardAnchorRefs.current.set(messageId, element);
      return;
    }

    promptCardAnchorRefs.current.delete(messageId);
  };

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

    let frameId = 0;
    let resizeObserver: ResizeObserver | null = null;

    const measureCollapsibleMessages = () => {
      const nextIds: number[] = [];

      orderedMessages.forEach((message) => {
        if (message.author !== 'user' && message.author !== 'codex') {
          return;
        }

        if (isLikelyCollapsibleMessage(message.text)) {
          nextIds.push(message.id);
          return;
        }

        const element = messageBodyRefs.current.get(message.id);

        if (!element) {
          return;
        }

        const computedStyle = window.getComputedStyle(element);
        const rawLineHeight = Number.parseFloat(computedStyle.lineHeight);
        const lineHeight = Number.isFinite(rawLineHeight) ? rawLineHeight : 12 * 1.45;
        const collapsedMaxHeight = lineHeight * COLLAPSIBLE_MESSAGE_LINE_COUNT;
        const fullHeight = element.scrollHeight;

        if (fullHeight > collapsedMaxHeight + 4) {
          nextIds.push(message.id);
        }
      });

      setCollapsibleMessageIds((current) => {
        if (current.length === nextIds.length && current.every((id, index) => id === nextIds[index])) {
          return current;
        }

        return nextIds;
      });
    };

    const scheduleMeasure = () => {
      window.cancelAnimationFrame(frameId);
      frameId = window.requestAnimationFrame(measureCollapsibleMessages);
    };

    scheduleMeasure();
    window.addEventListener('resize', scheduleMeasure);

    if (typeof ResizeObserver !== 'undefined') {
      resizeObserver = new ResizeObserver(() => {
        scheduleMeasure();
      });

      orderedMessages.forEach((message) => {
        const element = messageBodyRefs.current.get(message.id);

        if (element) {
          resizeObserver?.observe(element);
        }
      });
    }

    return () => {
      window.cancelAnimationFrame(frameId);
      window.removeEventListener('resize', scheduleMeasure);
      resizeObserver?.disconnect();
    };
  }, [orderedMessages, expandedMessageIds]);

  useEffect(() => {
    if (isConversationLoading) {
      setShowBusyOverlay(false);
      return;
    }

    if (!isComposerAttachmentUploading) {
      setShowBusyOverlay(false);
      return;
    }

    const timeoutId = window.setTimeout(() => {
      setShowBusyOverlay(true);
    }, 350);

    return () => {
      window.clearTimeout(timeoutId);
    };
  }, [isComposerAttachmentUploading, isConversationLoading]);

  useEffect(() => {
    const previousDisplayMode = previousSystemExecutionDisplayModeRef.current;
    previousSystemExecutionDisplayModeRef.current = systemExecutionDisplayMode;

    if (systemExecutionDisplayMode !== 'expanded') {
      if (previousDisplayMode === 'expanded') {
        systemExecutionOlderLoadScrollSnapshotRef.current = null;
      }
      return;
    }

    if (systemExecutionOlderLoadScrollSnapshotRef.current && !isLoadingOlderMessages) {
      const animationFrameId = window.requestAnimationFrame(() => {
        const systemExecutionBodyElement = systemExecutionBodyRef.current;
        const snapshot = systemExecutionOlderLoadScrollSnapshotRef.current;

        if (!systemExecutionBodyElement || !snapshot) {
          systemExecutionOlderLoadScrollSnapshotRef.current = null;
          return;
        }

        const heightDelta = systemExecutionBodyElement.scrollHeight - snapshot.scrollHeight;
        systemExecutionBodyElement.scrollTop = Math.max(0, snapshot.scrollTop + heightDelta);
        systemExecutionOlderLoadScrollSnapshotRef.current = null;
      });

      return () => {
        window.cancelAnimationFrame(animationFrameId);
      };
    }

    if (previousDisplayMode === 'expanded') {
      return;
    }

    const scrollToBottom = () => {
      const viewportElement = viewportRef.current;
      const systemExecutionBodyElement = systemExecutionBodyRef.current;

      if (systemExecutionBodyElement) {
        systemExecutionBodyElement.scrollTop = systemExecutionBodyElement.scrollHeight;
      }

      if (viewportElement) {
        viewportElement.scrollTo({
          top: viewportElement.scrollHeight,
          behavior: 'auto',
        });
      }
    };

    const animationFrameId = window.requestAnimationFrame(() => {
      window.requestAnimationFrame(scrollToBottom);
    });

    return () => {
      window.cancelAnimationFrame(animationFrameId);
    };
  }, [filteredSystemExecutionRequests.length, isLoadingOlderMessages, systemExecutionDisplayMode, viewportRef]);

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

    const syncViewportAfterLayoutChange = () => {
      const viewportElement = viewportRef.current;

      if (!viewportElement) {
        return;
      }

      const maxScrollTop = Math.max(0, viewportElement.scrollHeight - viewportElement.clientHeight);
      const remainingDistance = maxScrollTop - viewportElement.scrollTop;
      const isNearBottom = remainingDistance <= 8;

      if (maxScrollTop === 0) {
        if (viewportElement.scrollTop !== 0) {
          viewportElement.scrollTop = 0;
        }
        onViewportScroll();
        return;
      }

      if (viewportElement.scrollTop > maxScrollTop) {
        viewportElement.scrollTop = maxScrollTop;
        onViewportScroll();
        return;
      }

      if (isNearBottom) {
        viewportElement.scrollTop = maxScrollTop;
        onViewportScroll();
      }
    };

    const frameId = window.requestAnimationFrame(() => {
      window.requestAnimationFrame(syncViewportAfterLayoutChange);
    });

    return () => {
      window.cancelAnimationFrame(frameId);
    };
  }, [
    activeSystemStatus,
    filteredSystemExecutionRequests.length,
    hasConversationMessages,
    isLoadingOlderMessages,
    isSystemStatusPending,
    onViewportScroll,
    pullToLoadDistance,
    requestStateMap,
    systemExecutionDisplayMode,
    viewportRef,
    visibleMessages,
    visibleSystemExecutionRequests.length,
  ]);

  const scrollToAnchorElement = (anchorElement: HTMLElement | null | undefined, behavior: ScrollBehavior) => {
    if (!anchorElement) {
      return false;
    }

    const viewportElement = viewportRef.current;
    const scrollContainer = resolveScrollableAnchorContainer(anchorElement, viewportElement);

    if (!scrollContainer) {
      anchorElement.scrollIntoView({
        behavior,
        block: 'start',
      });
      return true;
    }

    const nextTop = Math.max(
      0,
      getElementOffsetWithinContainer(anchorElement, scrollContainer) - getContainerScrollPaddingTop(scrollContainer),
    );

    scrollContainer.scrollTo({
      top: nextTop,
      behavior,
    });

    return true;
  };
  const scrollToMessageAnchor = (messageId: number, behavior: ScrollBehavior) =>
    scrollToAnchorElement(messageAnchorRefs.current.get(messageId), behavior);
  const scrollToPromptCardAnchor = (messageId: number | null | undefined, behavior: ScrollBehavior) =>
    typeof messageId === 'number' && Number.isFinite(messageId)
      ? scrollToAnchorElement(promptCardAnchorRefs.current.get(messageId), behavior)
      : false;

  const triggerOlderSystemExecutionLoad = () => {
    if (
      !canLoadOlderSystemExecutionRequests ||
      !hasOlderMessages ||
      isLoadingOlderMessages ||
      systemExecutionOlderLoadRequestedRef.current ||
      !onLoadOlderMessages
    ) {
      return;
    }

    const systemExecutionBodyElement = systemExecutionBodyRef.current;

    if (systemExecutionDisplayMode === 'expanded' && systemExecutionBodyElement) {
      systemExecutionOlderLoadScrollSnapshotRef.current = {
        scrollHeight: systemExecutionBodyElement.scrollHeight,
        scrollTop: systemExecutionBodyElement.scrollTop,
      };
    }

    systemExecutionOlderLoadRequestedRef.current = true;

    Promise.resolve(onLoadOlderMessages()).catch(() => {
      systemExecutionOlderLoadRequestedRef.current = false;
      systemExecutionOlderLoadScrollSnapshotRef.current = null;
    });
  };

  const handleSystemExecutionBodyScroll = (event: UIEvent<HTMLDivElement>) => {
    if (!canLoadOlderSystemExecutionRequests) {
      return;
    }

    if (event.currentTarget.scrollTop > 24) {
      return;
    }

    triggerOlderSystemExecutionLoad();
  };

  const scheduleSystemExecutionJump = (scroll: (behavior: ScrollBehavior) => boolean, attempt = 0) => {
    if (systemExecutionJumpFrameRef.current !== null) {
      window.cancelAnimationFrame(systemExecutionJumpFrameRef.current);
    }

    systemExecutionJumpFrameRef.current = window.requestAnimationFrame(() => {
      systemExecutionJumpFrameRef.current = null;
      const didScroll = scroll(attempt === 0 ? 'smooth' : 'auto');

      if (attempt < SYSTEM_EXECUTION_JUMP_MAX_RETRIES) {
        scheduleSystemExecutionJump(scroll, attempt + 1);
      }

      if (!didScroll && attempt >= SYSTEM_EXECUTION_JUMP_MAX_RETRIES) {
        return;
      }
    });
  };

  const resolveSystemExecutionJumpTarget = (request: ChatConversationRequest | undefined) => {
    if (!request) {
      return null;
    }

    const normalizedRequestId = request.requestId.trim();

    if (!normalizedRequestId) {
      return null;
    }

    const firstPromptMessageId = firstPromptMessageIdByRequestId.get(normalizedRequestId) ?? null;
    const hasPromptTarget = firstPromptMessageId != null || (promptTargetsByRequestId.get(normalizedRequestId)?.length ?? 0) > 0;

    if (hasPromptTarget) {
      const anchorMessageId =
        [firstPromptMessageId, request.responseMessageId].find(
          (messageId): messageId is number => typeof messageId === 'number' && Number.isFinite(messageId),
        ) ?? null;

      return {
        kind: 'prompt' as const,
        requestId: normalizedRequestId,
        promptMessageId: firstPromptMessageId,
        anchorMessageId,
        buttonLabel: 'prompt 위치로 이동',
      };
    }

    if (typeof request.responseMessageId === 'number' && Number.isFinite(request.responseMessageId)) {
      return {
        kind: 'response' as const,
        requestId: normalizedRequestId,
        anchorMessageId: request.responseMessageId,
        buttonLabel: '답변 위치로 이동',
      };
    }

    if (typeof request.userMessageId === 'number' && Number.isFinite(request.userMessageId)) {
      return {
        kind: 'request' as const,
        requestId: normalizedRequestId,
        anchorMessageId: request.userMessageId,
        buttonLabel: '요청 위치로 이동',
      };
    }

    return null;
  };

  const scrollToSystemExecutionRequest = (
    requestId: string,
    options?: {
      fallbackMessageId?: number;
      jumpTarget?: ReturnType<typeof resolveSystemExecutionJumpTarget>;
    },
  ) => {
    const request = requestStateMap.get(requestId);
    const normalizedRequestId = requestId.trim();
    const rootRequestId =
      (normalizedRequestId ? resolveConversationRootRequestId(normalizedRequestId, requestStateMap) : '') ||
      normalizedRequestId;
    const resolvedJumpTarget = options?.jumpTarget ?? resolveSystemExecutionJumpTarget(request);
    const anchorMessageId =
      resolvedJumpTarget?.anchorMessageId ??
      [request?.responseMessageId, request?.userMessageId, options?.fallbackMessageId].find(
        (messageId): messageId is number => typeof messageId === 'number' && Number.isFinite(messageId),
      ) ??
      null;

    if (!anchorMessageId) {
      return false;
    }

    if (rootRequestId) {
      setExpandedGroupIds((current) => (current.includes(rootRequestId) ? current : [...current, rootRequestId]));
    }
    setExpandedMessageIds((current) => (current.includes(anchorMessageId) ? current : [...current, anchorMessageId]));
    scheduleSystemExecutionJump((behavior) => {
      if (resolvedJumpTarget?.kind === 'prompt') {
        return (
          scrollToPromptCardAnchor(resolvedJumpTarget.promptMessageId, behavior) ||
          scrollToMessageAnchor(anchorMessageId, behavior)
        );
      }

      return scrollToMessageAnchor(anchorMessageId, behavior);
    });
    return true;
  };

  const scrollToRoomShareGroup = (groupId: string) => {
    const targetGroup = roomShareRequestGroups.find((entry) => entry.groupId === groupId);
    const representativeRequest = targetGroup?.representativeRequest;

    if (!representativeRequest) {
      return;
    }

    scrollToSystemExecutionRequest(
      representativeRequest.requestId,
      {
        fallbackMessageId: representativeRequest.responseMessageId ?? representativeRequest.userMessageId ?? undefined,
        jumpTarget: resolveSystemExecutionJumpTarget(representativeRequest),
      },
    );
  };

  const moveRoomShareGroupSelection = (direction: 'previous' | 'next') => {
    if (roomShareExpandMode !== 'latest') {
      return;
    }

    const nextIndex = direction === 'previous' ? selectedRoomShareGroupIndex - 1 : selectedRoomShareGroupIndex + 1;
    const nextGroup = roomShareRequestGroups[nextIndex];

    if (!nextGroup) {
      return;
    }

    setSelectedRoomShareGroupId(nextGroup.groupId);
    scrollToRoomShareGroup(nextGroup.groupId);
  };

  useEffect(() => {
    const pendingJump = pendingRoomShareJumpRef.current;

    if (!pendingJump || pendingJump.groupId !== selectedRoomShareGroupId) {
      return;
    }

    pendingRoomShareJumpRef.current = null;

    const frameId = window.requestAnimationFrame(() => {
      if (pendingJump.requestId) {
        const pendingRequest = requestStateMap.get(pendingJump.requestId);
        const pendingJumpTarget = resolveSystemExecutionJumpTarget(pendingRequest);
        const didScroll = scrollToSystemExecutionRequest(pendingJump.requestId, {
          fallbackMessageId: pendingJump.fallbackMessageId,
          jumpTarget: pendingJumpTarget,
        });

        if (
          didScroll &&
          (pendingJumpTarget?.kind === 'prompt' || pendingJumpTarget?.kind === 'response') &&
          isPhoneLikeViewport() &&
          systemExecutionDisplayMode === 'expanded'
        ) {
          setSystemExecutionDisplayMode('hidden');
        }
        return;
      }

      scrollToRoomShareGroup(pendingJump.groupId);
    });

    return () => {
      window.cancelAnimationFrame(frameId);
    };
  }, [requestStateMap, scrollToRoomShareGroup, selectedRoomShareGroupId, systemExecutionDisplayMode]);

  const handleSystemExecutionJump = (request: ChatConversationRequest) => {
    const jumpTarget = resolveSystemExecutionJumpTarget(request);
    const normalizedRequestId = request.requestId.trim();
    const targetGroupId = normalizedRequestId ? roomShareGroupIdByRequestId.get(normalizedRequestId) ?? null : null;
    const fallbackMessageId = request.responseMessageId ?? request.userMessageId ?? undefined;

    if (
      showRoomsShareHeader &&
      roomShareExpandMode === 'latest' &&
      targetGroupId &&
      targetGroupId !== selectedRoomShareGroupId
    ) {
      pendingRoomShareJumpRef.current = {
        groupId: targetGroupId,
        requestId: normalizedRequestId,
        fallbackMessageId,
      };
      setSelectedRoomShareGroupId(targetGroupId);
      return;
    }

    const didScroll = scrollToSystemExecutionRequest(request.requestId, {
      fallbackMessageId,
      jumpTarget,
    });

    if (
      didScroll &&
      (jumpTarget?.kind === 'prompt' || jumpTarget?.kind === 'response') &&
      isPhoneLikeViewport() &&
      systemExecutionDisplayMode === 'expanded'
    ) {
      setSystemExecutionDisplayMode('hidden');
    }
  };

  const markPreviewResourceOpened = (url: string) => {
    const normalizedUrl = normalizeChatResourceUrl(url);

    setOpenedPreviewUrls((current) => (current.includes(normalizedUrl) ? current : [...current, normalizedUrl]));
  };
  const markPreviewArtifactOpened = (requestId?: string | null) => {
    const normalizedRequestId = requestId?.trim();

    if (!normalizedRequestId) {
      return;
    }

    setOpenedPreviewArtifactRequestIds((current) =>
      current.includes(normalizedRequestId) ? current : [...current, normalizedRequestId],
    );
  };
  const handleCompleteManualBadges = async (requestId: string, types: Array<'prompt' | 'verification'>) => {
    const normalizedRequestId = requestId.trim();
    const normalizedTypes = Array.from(new Set(types)).slice(0, 1);
    const actionKeys = normalizedTypes.map((type) => `${type}:${normalizedRequestId}`);

    if (
      !normalizedRequestId ||
      normalizedTypes.length === 0 ||
      actionKeys.some((actionKey) => pendingManualCompletionActionKeySet.has(actionKey))
    ) {
      return;
    }

    setPendingManualCompletionActionKeys((current) =>
      Array.from(new Set([...current, ...actionKeys])),
    );

    try {
      for (const type of normalizedTypes) {
        await onCompleteManualRequestBadge(normalizedRequestId, type);
      }
    } catch (error) {
      message.error(error instanceof Error ? error.message : '완료 상태를 저장하지 못했습니다.');
    } finally {
      setPendingManualCompletionActionKeys((current) =>
        current.filter((item) => !actionKeys.includes(item)),
      );
    }
  };
  const completeManualBadge = async (options: {
    requestId: string;
    type: 'prompt' | 'verification';
  }) => {
    const normalizedRequestId = options.requestId.trim();

    if (!normalizedRequestId) {
      return;
    }

    const confirmed = await new Promise<boolean>((resolve) => {
      modalApi.confirm({
        title: options.type === 'prompt' ? 'prompt 완료 처리' : '응답 확인 처리',
        content:
          options.type === 'prompt'
            ? '이 요청의 prompt 선택대기를 수동 완료 처리할까요?'
            : '이 요청의 일반 답변을 응답 확인 완료로 처리할까요?',
        okText: '완료 처리',
        cancelText: '취소',
        centered: true,
        onOk: async () => {
          resolve(true);
        },
        onCancel: () => {
          resolve(false);
        },
      });
    });

    if (!confirmed) {
      return;
    }

    await handleCompleteManualBadges(normalizedRequestId, [options.type]);
  };

  const renderSystemExecutionRequestSummary = (
    request: ChatConversationRequest,
    options?: { isCollapsed?: boolean; depth?: number },
  ) => {
    const isCollapsed = options?.isCollapsed === true;
    const depth = Math.max(0, options?.depth ?? 0);
    const summaryMaxLength = resolveSystemExecutionSummaryMaxLength();
    const groupedRequests =
      depth === 0
        ? (requestIdsByRootRequestId.get(request.requestId) ?? [])
            .map((requestId) => requestStateMap.get(requestId))
            .filter((item): item is ChatConversationRequest => item != null)
        : [request];
    const representativeRequest =
      depth === 0
        ? resolveRepresentativeSystemExecutionRequest(groupedRequests, systemExecutionAttentionStateByRequestId) ?? request
        : request;
    const rootRelationshipBadge = depth === 0 ? buildRootRelationshipBadge(request, groupedRequests) : null;
    const attentionState = systemExecutionAttentionStateByRequestId.get(representativeRequest.requestId);
    const activityLines = attentionState?.activityLines ?? [];
    const promptTargets = attentionState?.promptTargets ?? [];
    const aggregatedStatusSummary = resolveAggregatedRequestStatusSummary(
      groupedRequests.length > 0 ? groupedRequests : [request],
      systemExecutionAttentionStateByRequestId,
    );
    const requestPrimaryStatusBadge = buildPrimaryStatusBadge(representativeRequest, {
      activityLines,
      promptTargets,
      hasPendingPromptBadge: attentionState?.hasPendingPromptBadge === true,
      hasPendingVerificationBadge: attentionState?.hasPendingVerificationBadge === true,
    });
    const primaryStatusBadge =
      depth === 0 && aggregatedStatusSummary.label
        ? {
            label: aggregatedStatusSummary.label,
            shortLabel: aggregatedStatusSummary.label,
            tone: aggregatedStatusSummary.tone,
          }
        : requestPrimaryStatusBadge;
    const hierarchyBadge = buildHierarchyBadge(representativeRequest, depth);
    const detailBadge = getRequestDetailBadge(representativeRequest);
    const promptSubmittedCount = attentionState?.promptSubmittedCount ?? 0;
    const isPromptManuallyCompleted = attentionState?.isPromptManuallyCompleted === true;
    const promptStateBadge = buildPromptStateBadge({
      promptTargets,
      submittedCount: promptSubmittedCount,
      isManuallyCompleted: isPromptManuallyCompleted,
    });
    const hasPendingPromptBadge = attentionState?.hasPendingPromptBadge === true;
    const hasVerificationTarget = attentionState?.hasVerificationTarget === true;
    const hasConfirmedVerificationTarget = attentionState?.hasConfirmedVerificationTarget === true;
    const isVerificationManuallyCompleted = attentionState?.isVerificationManuallyCompleted === true;
    const verificationStateBadge = buildVerificationStateBadge({
      request: representativeRequest,
      activityLines,
      promptTargets,
      hasVerificationTarget,
      hasConfirmedVerificationTarget,
      isManuallyCompleted: isVerificationManuallyCompleted,
      isPromptManuallyCompleted,
    });
    const hasPendingVerificationBadge = attentionState?.hasPendingVerificationBadge === true;
    const manualCompletionTypes = buildManualCompletionTypes({
      hasPendingPromptBadge,
      hasPendingVerificationBadge,
    });
    const checklistBadge = buildChecklistStageBadge(activityLines, representativeRequest);
    const readStateBadge =
      verificationStateBadge ? null : buildReadStateBadge(representativeRequest, lastReadResponseMessageId);
    const retryBadge = getRequestRetryBadge(representativeRequest);
    const secondaryBadges = [
      hierarchyBadge,
      rootRelationshipBadge,
      retryBadge,
      detailBadge,
      promptStateBadge,
      verificationStateBadge,
      checklistBadge,
      readStateBadge,
    ].filter((badge): badge is SystemExecutionBadge => badge != null);
    const primaryManualCompletionType = resolvePrimaryManualCompletionType({
      secondaryBadges,
      promptStateBadge,
      verificationStateBadge,
      hasPendingPromptBadge,
      hasPendingVerificationBadge,
    });
    const canCompleteManualBadge = primaryManualCompletionType != null;
    const isManualCompletionSaving = manualCompletionTypes.some((type) =>
      pendingManualCompletionActionKeySet.has(`${type}:${representativeRequest.requestId}`),
    );
    const mobileSecondaryBadge = buildMobileSecondaryBadge(representativeRequest, secondaryBadges);
    const displayBadges: SystemExecutionBadgeDisplay[] = mobileSecondaryBadge
      ? [...secondaryBadges.map((badge) => ({ ...badge, hideOnMobile: true })), mobileSecondaryBadge]
      : secondaryBadges;
    const summaryRequest = depth === 0 ? request : representativeRequest;
    const summaryText = summarizeSystemExecutionRequestText(summaryRequest, {
      preferResponse: false,
      maxLength: summaryMaxLength,
    });
    const detailText = getRequestDetailText(summaryRequest);
    const responseSummary = String(summaryRequest.responseText ?? '').replace(/\s+/g, ' ').trim();
    const representativeSummaryText =
      representativeRequest.requestId !== summaryRequest.requestId
        ? summarizeSystemExecutionRequestText(representativeRequest, {
            preferResponse: false,
            maxLength: summaryMaxLength,
          })
        : '';
    const supplementalDetail = representativeSummaryText
      ? `현재 확인 대상: ${representativeSummaryText}`
      : responseSummary && summarizeQueuedText(responseSummary, summaryMaxLength) !== summaryText
        ? `답변: ${summarizeQueuedText(responseSummary, summaryMaxLength)}`
        : detailText && detailText !== summaryText
          ? detailText
          : '';
    const activityOverviewTargetRequest = resolveVisibleActivityOverviewTargetRequest(
      depth === 0 ? groupedRequests : [request],
      systemExecutionActivityOverviewByRequestId,
      systemExecutionAttentionStateByRequestId,
    );
    const activityActionTargetRequest = resolveActiveSystemExecutionActionTargetRequest(
      depth === 0 ? groupedRequests : [request],
      systemExecutionAttentionStateByRequestId,
    );
    const activityOverview = activityOverviewTargetRequest
      ? systemExecutionActivityOverviewByRequestId.get(activityOverviewTargetRequest.requestId) ?? null
      : null;
    const canToggleActivityOverview = activityActionTargetRequest != null;
    const timestampLabel = resolveSystemExecutionRequestTimestamp(representativeRequest);
    const elapsedLabel = formatSystemExecutionElapsedLabel(representativeRequest);
    const summaryJumpTarget = resolveSystemExecutionJumpTarget(summaryRequest);
    const representativeJumpTarget = resolveSystemExecutionJumpTarget(representativeRequest);
    const jumpTargetRequest =
      getSystemExecutionJumpTargetPriority(representativeJumpTarget) >
        getSystemExecutionJumpTargetPriority(summaryJumpTarget)
        ? representativeRequest
        : summaryRequest;
    const jumpButtonLabel =
      (jumpTargetRequest.requestId === representativeRequest.requestId
        ? representativeJumpTarget
        : summaryJumpTarget)?.buttonLabel ?? '위치로 이동';

    return (
      <div
        key={request.requestId}
        className={`app-chat-panel__system-execution-record${
          isCollapsed ? ' app-chat-panel__system-execution-record--collapsed' : ''
        }${depth > 0 ? ' app-chat-panel__system-execution-record--child' : ''}`}
        style={
          depth > 0
            ? {
                '--system-execution-indent-level': String(depth),
              } as CSSProperties
            : undefined
        }
      >
        <button
          type="button"
          className="app-chat-panel__system-execution-record-main"
          aria-label={jumpButtonLabel}
          onClick={() => {
            handleSystemExecutionJump(jumpTargetRequest);
          }}
        >
          {depth > 0 ? (
            <span className="app-chat-panel__system-execution-record-hierarchy">
              부모 요청에 연결된 자식 실행
            </span>
          ) : rootRelationshipBadge ? (
            <span className="app-chat-panel__system-execution-record-hierarchy app-chat-panel__system-execution-record-hierarchy--root">
              {rootRelationshipBadge.label}
            </span>
          ) : null}
          <span className="app-chat-panel__system-execution-record-row">
            <span className="app-chat-panel__system-execution-record-badges">
              <span
                className={`app-chat-panel__system-execution-record-status app-chat-panel__system-execution-record-status--${primaryStatusBadge.tone}`}
                aria-label={primaryStatusBadge.label}
              >
                <span className="app-chat-panel__system-execution-record-status-text app-chat-panel__system-execution-record-status-text--full">
                  {primaryStatusBadge.label}
                </span>
                <span className="app-chat-panel__system-execution-record-status-text app-chat-panel__system-execution-record-status-text--compact">
                  {primaryStatusBadge.shortLabel}
                </span>
              </span>
              {displayBadges.map((badge) => (
                <span
                  key={`${request.requestId}:${badge.label}:${badge.tone}:${badge.shortLabel}:${mobileSecondaryBadge === badge ? 'mobile' : 'full'}`}
                  className={`app-chat-panel__system-execution-record-status app-chat-panel__system-execution-record-status--${badge.tone}${
                    badge.hideOnMobile ? ' app-chat-panel__system-execution-record-status--desktop-only' : ''
                  }${mobileSecondaryBadge === badge ? ' app-chat-panel__system-execution-record-status--mobile-summary' : ''}`}
                  aria-label={badge.label}
                >
                  <span className="app-chat-panel__system-execution-record-status-text app-chat-panel__system-execution-record-status-text--full">
                    {badge.label}
                  </span>
                  <span className="app-chat-panel__system-execution-record-status-text app-chat-panel__system-execution-record-status-text--compact">
                    {badge.shortLabel}
                  </span>
                </span>
              ))}
            </span>
            {timestampLabel || elapsedLabel ? (
              <span className="app-chat-panel__system-execution-record-time">
                {[timestampLabel, elapsedLabel].filter(Boolean).join(' · ')}
              </span>
            ) : null}
          </span>
          <span className="app-chat-panel__system-execution-record-text">{summaryText}</span>
          {supplementalDetail && supplementalDetail !== summaryText ? (
            <span className="app-chat-panel__system-execution-record-detail">{supplementalDetail}</span>
          ) : null}
        </button>
        {canCompleteManualBadge || canToggleActivityOverview || Boolean(onShareRequestBundle) ? (
          <div className="app-chat-panel__system-execution-record-actions">
            {onShareRequestBundle ? (
              <Button
                type="text"
                size="small"
                className="app-chat-panel__system-execution-record-action"
                icon={<ShareAltOutlined />}
                aria-label="요청묶음 공유"
                onClick={() => {
                  onShareRequestBundle(representativeRequest.requestId);
                }}
              />
            ) : null}
            {canToggleActivityOverview ? (
              <Button
                type="text"
                size="small"
                className="app-chat-panel__system-execution-record-action app-chat-panel__system-execution-record-action--activity"
                icon={<ProfileOutlined />}
                aria-label="Plan 체크리스트와 실행기 보기"
                onClick={() => {
                  setSystemExecutionDisplayMode('expanded');
                  setExpandedSystemExecutionActivityRequestId(
                    activityOverviewTargetRequest?.requestId ?? activityActionTargetRequest?.requestId ?? null,
                  );
                }}
              />
            ) : null}
            {canCompleteManualBadge ? (
              <Button
                type="text"
                size="small"
                loading={isManualCompletionSaving}
                className="app-chat-panel__system-execution-record-action app-chat-panel__system-execution-record-action--complete"
                onClick={() => {
                  if (!primaryManualCompletionType) {
                    return;
                  }

                  void completeManualBadge({
                    requestId: representativeRequest.requestId,
                    type: primaryManualCompletionType,
                  });
                }}
              >
                완료
              </Button>
            ) : null}
          </div>
        ) : null}
      </div>
    );
  };
  const renderSystemExecutionRequestTree = (
    request: ChatConversationRequest,
    options?: {
      isCollapsed?: boolean;
      depth?: number;
      scopeRequestIdSet?: Set<string>;
      excludedRequestIdSet?: Set<string>;
      filterToVisibleSet?: boolean;
      visitedRequestIdSet?: Set<string>;
    },
  ): ReactNode => {
    const depth = Math.max(0, options?.depth ?? 0);
    const isCollapsed = options?.isCollapsed === true;
    const scopeRequestIdSet = options?.scopeRequestIdSet ?? null;
    const excludedRequestIdSet = options?.excludedRequestIdSet ?? null;
    const filterToVisibleSet = options?.filterToVisibleSet !== false;
    const visitedRequestIdSet = options?.visitedRequestIdSet ?? new Set<string>();

    if (visitedRequestIdSet.has(request.requestId)) {
      return renderSystemExecutionRequestSummary(request, { isCollapsed: true, depth });
    }

    const nextVisitedRequestIdSet = new Set(visitedRequestIdSet);
    nextVisitedRequestIdSet.add(request.requestId);
    const childRequests = isCollapsed
      ? []
      : (childRequestIdsByParentRequestId.get(request.requestId) ?? [])
          .filter((requestId) => {
            if (scopeRequestIdSet && !scopeRequestIdSet.has(requestId)) {
              return false;
            }

            if (excludedRequestIdSet?.has(requestId)) {
              return false;
            }

            return !filterToVisibleSet || filteredSystemExecutionRequestIdSet.has(requestId);
          })
          .map((requestId) => requestStateMap.get(requestId))
          .filter((item): item is ChatConversationRequest => item != null)
          .sort((left, right) => compareSystemExecutionHierarchyRequests(left, right, systemExecutionSort));

    return (
      <div
        key={`${request.requestId}:${depth}:${isCollapsed ? 'collapsed' : 'expanded'}`}
        className={`app-chat-panel__system-execution-record-tree${
          depth > 0 ? ' app-chat-panel__system-execution-record-tree--child' : ''
        }`}
      >
        {renderSystemExecutionRequestSummary(request, { isCollapsed, depth })}
        {childRequests.length > 0 ? (
          <div className="app-chat-panel__system-execution-record-children">
            {childRequests.map((childRequest) =>
            renderSystemExecutionRequestTree(childRequest, {
              depth: depth + 1,
              scopeRequestIdSet,
              excludedRequestIdSet,
              filterToVisibleSet,
              visitedRequestIdSet: nextVisitedRequestIdSet,
            }),
          )}
        </div>
      ) : null}
      </div>
    );
  };

  const resolveVisibleSystemExecutionEntryRequests = (
    rootRequest: ChatConversationRequest,
    options?: {
      filterToVisibleSet?: boolean;
    },
  ) => {
    const filterToVisibleSet = options?.filterToVisibleSet !== false;

    if (!filterToVisibleSet || filteredSystemExecutionRequestIdSet.has(rootRequest.requestId)) {
      return [rootRequest];
    }

    const entryRequests: ChatConversationRequest[] = [];
    const seenEntryRequestIds = new Set<string>();
    const visitedParentRequestIds = new Set<string>();
    const pendingParentRequestIds = [rootRequest.requestId];

    while (pendingParentRequestIds.length > 0) {
      const currentParentRequestId = pendingParentRequestIds.shift();

      if (!currentParentRequestId || visitedParentRequestIds.has(currentParentRequestId)) {
        continue;
      }

      visitedParentRequestIds.add(currentParentRequestId);
      const childRequestIds = childRequestIdsByParentRequestId.get(currentParentRequestId) ?? [];

      childRequestIds.forEach((childRequestId) => {
        if (filteredSystemExecutionRequestIdSet.has(childRequestId)) {
          if (seenEntryRequestIds.has(childRequestId)) {
            return;
          }

          const childRequest = requestStateMap.get(childRequestId);

          if (!childRequest) {
            return;
          }

          entryRequests.push(childRequest);
          seenEntryRequestIds.add(childRequestId);
          return;
        }

        pendingParentRequestIds.push(childRequestId);
      });
    }

    return entryRequests.length > 0
      ? entryRequests.sort((left, right) => compareSystemExecutionHierarchyRequests(left, right, systemExecutionSort))
      : [rootRequest];
  };

  const renderSystemExecutionRootEntries = (
    rootRequest: ChatConversationRequest,
    options?: {
      isCollapsed?: boolean;
      filterToVisibleSet?: boolean;
    },
  ) =>
    resolveVisibleSystemExecutionEntryRequests(rootRequest, {
      filterToVisibleSet: options?.filterToVisibleSet,
    }).map((entryRequest) =>
      renderSystemExecutionRequestTree(entryRequest, {
        depth: getConversationRequestDepth(entryRequest, requestStateMap),
        isCollapsed: options?.isCollapsed,
        filterToVisibleSet: options?.filterToVisibleSet,
      }),
    );

  const renderEmbeddedChildRequestTree = (
    parentRequest: ChatConversationRequest | null | undefined,
    options?: {
      title?: string;
      className?: string;
      scopeRequestIdSet?: Set<string>;
      excludedRequestIdSet?: Set<string>;
      filterToVisibleSet?: boolean;
      flattenDescendants?: boolean;
    },
  ): ReactNode => {
    if (!parentRequest) {
      return null;
    }

    const scopeRequestIdSet = options?.scopeRequestIdSet ?? null;
    const excludedRequestIdSet = options?.excludedRequestIdSet ?? null;
    const filterToVisibleSet = options?.filterToVisibleSet !== false;
    const flattenDescendants = options?.flattenDescendants === true;
    const visibleChildRequestIds = new Set<string>();
    const visitedRequestIds = new Set<string>();
    const pendingParentRequestIds = [parentRequest.requestId];

    while (pendingParentRequestIds.length > 0) {
      const currentParentRequestId = pendingParentRequestIds.shift();

      if (!currentParentRequestId || visitedRequestIds.has(currentParentRequestId)) {
        continue;
      }

      visitedRequestIds.add(currentParentRequestId);
      const currentChildRequestIds = childRequestIdsByParentRequestId.get(currentParentRequestId) ?? [];

      currentChildRequestIds.forEach((requestId) => {
        if (scopeRequestIdSet && !scopeRequestIdSet.has(requestId)) {
          return;
        }

        if (excludedRequestIdSet?.has(requestId)) {
          return;
        }

        if (filterToVisibleSet && !filteredSystemExecutionRequestIdSet.has(requestId)) {
          return;
        }

        if (visibleChildRequestIds.has(requestId)) {
          return;
        }

        visibleChildRequestIds.add(requestId);

        if (flattenDescendants) {
          pendingParentRequestIds.push(requestId);
        }
      });

      if (!flattenDescendants) {
        break;
      }
    }

    const childRequests = Array.from(visibleChildRequestIds)
      .map((requestId) => requestStateMap.get(requestId))
      .filter((item): item is ChatConversationRequest => item != null)
      .sort((left, right) => compareSystemExecutionHierarchyRequests(left, right, systemExecutionSort));

    if (childRequests.length === 0) {
      return null;
    }

    return (
      <section className={['app-chat-embedded-request-tree', options?.className].filter(Boolean).join(' ')}>
        <div className="app-chat-embedded-request-tree__title">{options?.title ?? '자식 요청'}</div>
        <div className="app-chat-embedded-request-tree__body">
          {childRequests.map((childRequest) =>
            flattenDescendants
              ? renderSystemExecutionRequestSummary(childRequest, {
                  isCollapsed: true,
                  depth: 1,
                })
              : renderSystemExecutionRequestTree(childRequest, {
                  depth: getConversationRequestDepth(childRequest, requestStateMap),
                  scopeRequestIdSet,
                  excludedRequestIdSet,
                  filterToVisibleSet,
                }),
          )}
        </div>
      </section>
    );
  };

  const busyOverlayLabel = '첨부 파일을 처리하는 중입니다.';

  const setChildComposerRef = (groupId: string, instance: TextAreaRef | null) => {
    if (!groupId.trim()) {
      return;
    }

    if (instance) {
      childComposerRefs.current.set(groupId, instance);
      return;
    }

    childComposerRefs.current.delete(groupId);
  };

  const openChildComposer = (groupId: string) => {
    const normalizedGroupId = groupId.trim();

    if (!normalizedGroupId) {
      return;
    }

    setOpenedChildComposerGroupIds((current) =>
      current.includes(normalizedGroupId) ? current : [...current, normalizedGroupId],
    );

    window.setTimeout(() => {
      childComposerRefs.current.get(normalizedGroupId)?.focus({ cursor: 'end' });
    }, 0);
  };

  const activateReplyReference = (
    requestId: string | null | undefined,
    options?: {
      focusComposer?: boolean;
    },
  ) => {
    const normalizedRequestId = requestId?.trim() || '';

    if (!normalizedRequestId) {
      return;
    }

    setReplyReferenceRequestId(normalizedRequestId);

    if (options?.focusComposer !== false) {
      composerRef.current?.focus({ cursor: 'end' });
    }
  };

  const closeChildComposer = (groupId: string) => {
    const normalizedGroupId = groupId.trim();

    if (!normalizedGroupId) {
      return;
    }

    setOpenedChildComposerGroupIds((current) => current.filter((item) => item !== normalizedGroupId));
    setSubmittingChildComposerGroupIds((current) => current.filter((item) => item !== normalizedGroupId));
  };

  const submitChildComposer = async (groupId: string, parentRequestId: string) => {
    const normalizedGroupId = groupId.trim();
    const normalizedParentRequestId = parentRequestId.trim();
    const nextText = childComposerDraftsByGroupId[normalizedGroupId]?.trim() ?? '';

    if (!normalizedGroupId || !normalizedParentRequestId || !nextText) {
      return;
    }

    if (submittingChildComposerGroupIds.includes(normalizedGroupId)) {
      return;
    }

    setSubmittingChildComposerGroupIds((current) =>
      current.includes(normalizedGroupId) ? current : [...current, normalizedGroupId],
    );

    try {
      const submitted = await onSubmitChildRequest({
        text: nextText,
        parentRequestId: normalizedParentRequestId,
      });

      if (!submitted) {
        return;
      }

      setChildComposerDraftsByGroupId((current) => {
        if (!(normalizedGroupId in current)) {
          return current;
        }

        const next = { ...current };
        delete next[normalizedGroupId];
        return next;
      });
      setOpenedChildComposerGroupIds((current) => current.filter((item) => item !== normalizedGroupId));
    } finally {
      setSubmittingChildComposerGroupIds((current) => current.filter((item) => item !== normalizedGroupId));
    }
  };

  const renderConversationMessage = (
    message: ChatMessage,
    _options?: {
      defaultCollapsedPrompt?: boolean;
    },
  ) => {
    const canCollapseMessage = collapsibleMessageIdSet.has(message.id);
    // Prompt cards should open by default even when the surrounding message group is collapsed.
    const defaultCollapsedPrompt = false;
    const isExpandedMessage = expandedMessageIds.includes(message.id);
    const shouldTruncateMessage = canCollapseMessage && !isExpandedMessage;
    const isRecoveredMissingRequest = isMissingRequestMessage(message);
    const isRecoveredExecutionFailure = isExecutionFailureMessage(message);
    const baseMessageBodyClassName = `app-chat-message__body${shouldTruncateMessage ? ' app-chat-message__body--collapsed' : ''}${
      isRecoveredMissingRequest || isRecoveredExecutionFailure ? ' app-chat-message__body--system-status' : ''
    }`;
    const { previewSourceText, visibleText, diffBlocks, rankedLinkTargets, linkCardTargets, previewCardTargets, promptTargets } =
      messageRenderPayloadById.get(message.id) ?? extractMessageRenderPayload(message);
    const renderedText = isRecoveredMissingRequest
      ? getMissingRequestMessageText(message)
      : isRecoveredExecutionFailure
        ? getExecutionFailureMessageText(message)
        : visibleText;
    const isActivityMessage = isActivityLogMessage(message);
    const activityOverview = isActivityMessage
      ? extractSystemExecutionActivityOverview(
          message.text
            .slice(`${CHAT_ACTIVITY_MESSAGE_PREFIX}\n`.length)
            .split('\n\n')
            .map((line) => line.trim())
            .filter(Boolean),
        )
      : null;

    const inlinePreviewTargets = extractInlinePreviewTargets(previewSourceText);
    const hasPreviewCards =
      diffBlocks.length > 0 ||
      inlinePreviewTargets.length > 0 ||
      rankedLinkTargets.length > 0 ||
      linkCardTargets.length > 0 ||
      previewCardTargets.length > 0 ||
      promptTargets.length > 0;
    const shouldRenderStandalonePreview =
      hasPreviewCards && !visibleText && (message.author === 'codex' || message.author === 'system');
    const stackClassName = [
        `app-chat-message-stack app-chat-message-stack--${message.author}`,
        useSharedRoomsBubbleFlow ? 'app-chat-message-stack--rooms-share-bubble' : '',
        shouldRenderStandalonePreview ? 'app-chat-message-stack--artifact-only' : '',
      ]
      .filter(Boolean)
      .join(' ');
    const messageClassName = [
      'app-chat-message',
      isRecoveredMissingRequest || isRecoveredExecutionFailure
        ? 'app-chat-message--system-inline'
        : isActivityMessage
          ? 'app-chat-message--activity'
          : `app-chat-message--${message.author}`,
      useSharedRoomsBubbleFlow ? 'app-chat-message--rooms-share-bubble' : '',
    ]
      .filter(Boolean)
      .join(' ');
    const previewContainerClassName = [
      'app-chat-message-stack__previews',
      useSharedRoomsBubbleFlow ? 'app-chat-message-stack__previews--rooms-share-bubble' : '',
    ]
      .filter(Boolean)
      .join(' ');
    const requestState = message.clientRequestId ? requestStateMap.get(message.clientRequestId) : undefined;
    const attentionState = message.clientRequestId
      ? systemExecutionAttentionStateByRequestId.get(message.clientRequestId)
      : undefined;
    const requestStatusLabel = formatRequestStatusLabel(requestState, attentionState, {
      hideFinalizedLabel: message.author === 'user',
    });
    const requestDetailText = getRequestDetailText(requestState);
    const responsePromptTargets = attentionState?.promptTargets ?? [];
    const responsePromptSubmittedCount = attentionState?.promptSubmittedCount ?? 0;
    const isResponsePromptManuallyCompleted = attentionState?.isPromptManuallyCompleted === true;
    const responsePromptStateBadge = buildPromptStateBadge({
      promptTargets: responsePromptTargets,
      submittedCount: responsePromptSubmittedCount,
      isManuallyCompleted: isResponsePromptManuallyCompleted,
    });
    const responseHasPendingPromptBadge = attentionState?.hasPendingPromptBadge === true;
    const responseHasVerificationTarget = attentionState?.hasVerificationTarget === true;
    const responseHasConfirmedVerificationTarget = attentionState?.hasConfirmedVerificationTarget === true;
    const isResponseVerificationManuallyCompleted = attentionState?.isVerificationManuallyCompleted === true;
    const responseVerificationStateBadge = buildVerificationStateBadge({
      request: requestState,
      activityLines: attentionState?.activityLines ?? [],
      promptTargets: responsePromptTargets,
      hasVerificationTarget: responseHasVerificationTarget,
      hasConfirmedVerificationTarget: responseHasConfirmedVerificationTarget,
      isManuallyCompleted: isResponseVerificationManuallyCompleted,
      isPromptManuallyCompleted: isResponsePromptManuallyCompleted,
    });
    const responseHasPendingVerificationBadge = attentionState?.hasPendingVerificationBadge === true;
    const responseSecondaryBadges = [responsePromptStateBadge, responseVerificationStateBadge].filter(
      (badge): badge is SystemExecutionBadge => badge != null,
    );
    const responseRetryBadge = getRequestRetryBadge(requestState);
    const responseDisplayBadges = [
      ...(responseRetryBadge ? [responseRetryBadge] : []),
      ...responseSecondaryBadges,
    ];
    const responsePrimaryManualCompletionType = resolvePrimaryManualCompletionType({
      secondaryBadges: responseDisplayBadges,
      promptStateBadge: responsePromptStateBadge,
      verificationStateBadge: responseVerificationStateBadge,
      hasPendingPromptBadge: responseHasPendingPromptBadge,
      hasPendingVerificationBadge: responseHasPendingVerificationBadge,
    });
    const responseManualCompletionTypes = buildManualCompletionTypes({
      hasPendingPromptBadge: responseHasPendingPromptBadge,
      hasPendingVerificationBadge: responseHasPendingVerificationBadge,
    });
    const isResponseManualCompletionSaving =
      Boolean(message.clientRequestId) &&
      responseManualCompletionTypes.some((type) =>
        pendingManualCompletionActionKeySet.has(`${type}:${message.clientRequestId}`),
      );
    const promptParentRequestId = resolvePromptSubmissionParentRequestId(message.clientRequestId ?? null, requestStateMap);
    const bubblePromptShareTarget =
      message.author === 'codex' && promptParentRequestId && promptTargets.length > 0 && onSharePromptTarget
        ? {
            requestId: promptParentRequestId,
            sourceMessageId: message.id,
            promptIndex: 0,
            promptTitle: promptTargets[0]?.title || 'prompt',
            promptSignature: buildPromptTargetSignature(promptTargets[0]!),
          }
        : null;
    const showResponseManualCompleteAction =
      enableExecutionReviewUi &&
      message.author === 'codex' &&
      Boolean(message.clientRequestId) &&
      Boolean(responsePrimaryManualCompletionType);
    const canCompletePromptFromResponse =
      showRoomsShareHeader &&
      message.author === 'codex' &&
      Boolean(message.clientRequestId) &&
      responsePromptTargets.length > 0 &&
      !isResponsePromptManuallyCompleted &&
      !isRequestInFlightStatus(requestState?.status);
    const canCompleteVerificationFromResponse =
      showRoomsShareHeader &&
      message.author === 'codex' &&
      Boolean(message.clientRequestId) &&
      responsePromptTargets.length === 0 &&
      responsePrimaryManualCompletionType === 'verification' &&
      !isRequestInFlightStatus(requestState?.status);
    const canReplyToResponse =
      showRoomsShareHeader &&
      message.author === 'codex' &&
      Boolean(message.clientRequestId) &&
      responsePromptTargets.length === 0 &&
      !isRequestInFlightStatus(requestState?.status);
    const isReplyReferenceActive =
      canReplyToResponse &&
      replyReferenceRequestId.trim() === (message.clientRequestId?.trim() ?? '');

    return (
      <div
        key={message.id}
        ref={(element) => {
          setMessageAnchorRef(message.id, element);
        }}
        className={stackClassName}
      >
        {shouldRenderStandalonePreview ? null : (
          <article className={messageClassName}>
            <div className="app-chat-message__header">
              <div className="app-chat-message__header-meta">
                <strong>
                  {isRecoveredMissingRequest
                    ? '원문 누락'
                    : isRecoveredExecutionFailure
                      ? '실행 실패'
                      : isActivityMessage
                        ? '진행 현황'
                      : message.author === 'codex'
                        ? 'Codex'
                        : message.author === 'user'
                          ? 'You'
                          : 'System'}
                </strong>
                <span>{formatChatTimestamp(message.timestamp)}</span>
                {message.author === 'user' && requestStatusLabel ? (
                  <span className="app-chat-message__status" aria-label={`요청 상태 ${requestStatusLabel}`}>
                    <span>{requestStatusLabel}</span>
                  </span>
                ) : null}
                {message.author === 'user' &&
                requestState?.status === 'queued' &&
                message.clientRequestId &&
                onPromoteQueuedRequest ? (
                  <Button
                    type="link"
                    size="small"
                    className="app-chat-message__retry app-chat-message__retry--icon-only app-chat-message__queue-promote"
                    icon={<ThunderboltOutlined />}
                    aria-label="즉시 접수"
                    onClick={() => {
                      onPromoteQueuedRequest(message.clientRequestId!, requestState.userText || message.text);
                    }}
                  />
                ) : null}
                {message.author === 'user' && message.deliveryStatus === 'retrying' ? (
                  <span className="app-chat-message__status app-chat-message__status--retrying" aria-label="재전송 중">
                    <SyncOutlined spin />
                    <span>{message.retryCount && message.retryCount > 0 ? `재시도 ${message.retryCount}` : '재전송 대기'}</span>
                  </span>
                ) : null}
                {message.author === 'user' && message.deliveryStatus === 'failed' ? (
                  <span className="app-chat-message__status app-chat-message__status--failed" aria-label="전송 실패">
                    <ExclamationCircleOutlined />
                    <span>전송 실패</span>
                  </span>
                ) : null}
                {message.author === 'user' &&
                (message.deliveryStatus === 'retrying' || message.deliveryStatus === 'failed') ? (
                  <Button
                    type="link"
                    size="small"
                    danger
                    className="app-chat-message__cancel"
                    icon={<CloseOutlined />}
                    onClick={() => {
                      onCancelMessage(message);
                    }}
                  >
                    취소
                  </Button>
                ) : null}
                {message.author === 'user' && message.deliveryStatus === 'failed' ? (
                  <>
                    <Button
                      type="link"
                      size="small"
                      className="app-chat-message__retry"
                      icon={<RedoOutlined />}
                      onClick={() => {
                        onRetryMessage(message);
                      }}
                    >
                      재전송
                    </Button>
                  </>
                ) : null}
                {message.author === 'user' && message.deliveryStatus !== 'failed' && requestState?.status === 'failed' ? (
                  <Button
                    type="link"
                    size="small"
                    className="app-chat-message__retry app-chat-message__retry--icon-only"
                    icon={<RedoOutlined />}
                    aria-label="실패 요청 재처리"
                    title="실패 요청 재처리"
                    onClick={() => {
                      onRetryFailedRequest(requestState);
                    }}
                  />
                ) : null}
                {message.author === 'user' &&
                requestState?.canDelete &&
                requestState.status !== 'accepted' ? (
                  <Button
                    type="link"
                    size="small"
                    className="app-chat-message__retry app-chat-message__delete"
                    icon={<DeleteOutlined />}
                    aria-label="메시지 삭제"
                    onClick={() => {
                      onDeleteRequest(message);
                    }}
                  />
                ) : null}
              </div>
              {message.author !== 'system' ? (
                <div className="app-chat-message__header-actions">
                  {bubblePromptShareTarget ? (
                    <Button
                      type="text"
                      size="small"
                      className="app-chat-message__header-action"
                      icon={<ShareAltOutlined />}
                      aria-label="prompt 공유"
                      title="prompt 공유"
                      onClick={() => {
                        onSharePromptTarget?.(bubblePromptShareTarget);
                      }}
                    />
                  ) : null}
                  {message.author === 'user' && requestState && onShareInquiryMessage ? (
                    <Button
                      type="text"
                      size="small"
                      className="app-chat-message__header-action"
                      icon={<ShareAltOutlined />}
                      aria-label="문의 공유"
                      title="문의 공유"
                      onClick={() => {
                        onShareInquiryMessage({
                          requestId: requestState.requestId,
                          messageId: message.id,
                        });
                      }}
                    />
                  ) : null}
                  {showResponseManualCompleteAction && !showRoomsShareHeader ? (
                    <Button
                      type="text"
                      size="small"
                      loading={isResponseManualCompletionSaving}
                      className="app-chat-message__header-action app-chat-message__header-action--complete"
                      icon={<CheckOutlined />}
                      aria-label="응답 완료 처리"
                      title="응답 완료 처리"
                      onClick={() => {
                        if (!message.clientRequestId || !responsePrimaryManualCompletionType) {
                          return;
                        }

                        void completeManualBadge({
                          requestId: message.clientRequestId,
                          type: responsePrimaryManualCompletionType,
                        });
                      }}
                    />
                  ) : null}
                  <Button
                    type="text"
                    size="small"
                    className="app-chat-message__header-action"
                    icon={<CopyOutlined />}
                    aria-label={copiedMessageId === message.id ? '복사됨' : message.author === 'user' ? '내 메시지 복사' : '답변 복사'}
                    onClick={() => {
                      onCopyMessage(message);
                    }}
                  />
                </div>
              ) : null}
            </div>
            {message.author === 'codex' && showRoomsShareHeader ? (
              <div className="app-chat-message__response-actions">
                {canCompletePromptFromResponse && message.clientRequestId ? (
                  <Button
                    type="text"
                    size="small"
                    className="app-chat-message__response-action"
                    icon={<CheckOutlined />}
                    loading={isResponseManualCompletionSaving}
                    onClick={() => {
                      void completeManualBadge({
                        requestId: message.clientRequestId!,
                        type: 'prompt',
                      });
                    }}
                  >
                    완료 처리
                  </Button>
                ) : null}
                {!canCompletePromptFromResponse && isResponsePromptManuallyCompleted ? <Tag color="success">prompt 완료 처리됨</Tag> : null}
                {canCompleteVerificationFromResponse && message.clientRequestId ? (
                  <Button
                    type="text"
                    size="small"
                    className="app-chat-message__response-action"
                    icon={<CheckOutlined />}
                    loading={isResponseManualCompletionSaving}
                    onClick={() => {
                      void completeManualBadge({
                        requestId: message.clientRequestId!,
                        type: 'verification',
                      });
                    }}
                  >
                    완료 처리
                  </Button>
                ) : null}
                {!canCompleteVerificationFromResponse && responsePromptTargets.length === 0 && isResponseVerificationManuallyCompleted ? <Tag color="success">응답 확인 완료</Tag> : null}
                {canReplyToResponse && message.clientRequestId ? (
                  <Button
                    type="text"
                    size="small"
                    className={['app-chat-message__response-action', 'app-chat-message__response-reply-button', isReplyReferenceActive ? 'app-chat-message__response-reply-button--active' : ''].filter(Boolean).join(' ')}
                    icon={<SendOutlined />}
                    onClick={() => {
                      activateReplyReference(message.clientRequestId, {
                        focusComposer: false,
                      });
                    }}
                  >
                    {isReplyReferenceActive ? '답변 참조 중' : '답변하기'}
                  </Button>
                ) : null}
              </div>
            ) : null}
              <div
                ref={(element) => {
                  setMessageBodyRef(message.id, element);
                }}
                className={baseMessageBodyClassName}
              >
              {isActivityMessage && activityOverview
                ? renderActivityOverviewBody(activityOverview, message.id, requestState, {
                    attentionState,
                    requestTree: renderEmbeddedChildRequestTree(requestState, {
                      title: '연결된 자식 요청',
                      className: 'app-chat-activity-checklist-stack__request-tree',
                      filterToVisibleSet: false,
                    }),
                  })
                : renderedText
                  ? renderMessageBody(renderedText)
                  : null}
            </div>
            {message.author === 'user' && requestDetailText ? (
              <div className="app-chat-message__request-detail" role="status" aria-live="polite">
                {requestDetailText}
              </div>
            ) : null}
            {canCollapseMessage ? (
              <Button
                type="text"
                size="small"
                className="app-chat-message__expand"
                icon={isExpandedMessage ? <UpOutlined /> : <DownOutlined />}
                aria-label={isExpandedMessage ? '메시지 접기' : '메시지 펼치기'}
                onClick={() => {
                  const isExpanding = !isExpandedMessage;

                  if (isExpanding && message.author !== 'user') {
                    setExpandedResponseMessageIds((current) =>
                      current.includes(message.id) ? current : [...current, message.id],
                    );
                  }

                  setExpandedMessageIds((current) =>
                    current.includes(message.id) ? current.filter((id) => id !== message.id) : [...current, message.id],
                  );
                }}
              >
                {isExpandedMessage ? '접기' : '펼치기'}
              </Button>
            ) : null}
          </article>
        )}
        {hasPreviewCards ? (
          <div className={previewContainerClassName}>
            {linkCardTargets.map((target) => (
              <ChatLinkCardPreview
                key={`${message.id}-${target.url}-${target.title}`}
                target={target}
                onOpen={() => {
                  markPreviewArtifactOpened(message.clientRequestId ?? null);
                }}
              />
            ))}
            {previewCardTargets.map((target, index) => (
              <ChatStructuredPreviewCard
                key={`${message.id}-preview-card-${index}-${target.title}`}
                target={target}
                onOpen={(previewUrl) => {
                  markPreviewArtifactOpened(message.clientRequestId ?? null);

                  if (previewUrl) {
                    markPreviewResourceOpened(previewUrl);
                  }
                }}
              />
            ))}
            {promptTargets.map((target, index) => (
              (() => {
                const selectionKey = buildPendingPromptSelectionKey(message.id, index, target.title, target);
                const draftSelection = pendingPromptSelections[selectionKey]?.status === 'draft'
                  ? pendingPromptSelections[selectionKey]
                  : null;
                const submittedSelection = pendingPromptSelections[selectionKey]?.status === 'submitted'
                  ? pendingPromptSelections[selectionKey]
                  : null;
                const promptParentQuestionText = resolvePromptParentQuestionText(
                  promptParentRequestId ? requestStateMap.get(promptParentRequestId) ?? null : null,
                );

                return (
                  <ChatPromptCard
                    key={`${message.id}-prompt-${index}-${target.title}`}
                    target={target}
                    parentQuestionText={promptParentQuestionText}
                    defaultCollapsed={defaultCollapsedPrompt}
                    draftSelection={draftSelection}
                    submittedSelection={submittedSelection}
                    onPreviewViewed={(previewUrl) => {
                      markPreviewArtifactOpened(message.clientRequestId ?? null);

                      if (previewUrl) {
                        markPreviewResourceOpened(previewUrl);
                      }
                    }}
                    onSubmit={(payload) =>
                      onSubmitPrompt({
                        ...payload,
                        parentRequestId: promptParentRequestId || null,
                        promptIndex: index,
                        sourceMessageId: message.id,
                      })
                    }
                    anchorRef={
                      index === 0 && message.clientRequestId
                        ? (element) => {
                            setPromptCardAnchorRef(message.id, element);
                          }
                        : null
                    }
                    onSharePrompt={
                      promptParentRequestId && onSharePromptTarget
                        ? () => {
                            onSharePromptTarget({
                              requestId: promptParentRequestId,
                              sourceMessageId: message.id,
                              promptIndex: index,
                              promptTitle: target.title,
                              promptSignature: buildPromptTargetSignature(target),
                            });
                          }
                        : null
                    }
                    onSubmitted={(selection) => {
                      setPendingPromptSelections((current) => {
                        const previousSelection = current[selectionKey];

                        return {
                          ...current,
                          [selectionKey]: {
                            ...selection,
                            status: 'submitted',
                            promptTitle: target.title,
                            target,
                            requestId: promptParentRequestId || (message.clientRequestId ?? null),
                            summaryText:
                              selection.summaryText ||
                              previousSelection?.summaryText ||
                              null,
                          },
                        };
                      });
                    }}
                    onSelectionChange={(selection) => {
                      setPendingPromptSelections((current) => {
                        if (!selection) {
                          if (!(selectionKey in current)) {
                            return current;
                          }

                          const next = { ...current };
                          delete next[selectionKey];
                          return next;
                        }

                        return {
                          ...current,
                          [selectionKey]: {
                            ...selection,
                            status: 'draft',
                            promptTitle: target.title,
                            target,
                            requestId: message.clientRequestId ?? null,
                          },
                        };
                      });
                    }}
                  />
                );
              })()
            ))}
            {rankedLinkTargets.map((target) => (
              <ChatRankedLinkPreview
                key={`${message.id}-${target.url}-${target.title}`}
                target={target}
                onOpen={() => {
                  markPreviewArtifactOpened(message.clientRequestId ?? null);
                }}
              />
            ))}
            {(() => {
              if (!diffBlocks.length) {
                return null;
              }

              const previewKey = `${message.id}-diff`;
              const combinedDiff = combineCodexDiffBlocks(diffBlocks);

              return (
                <DiffMessagePreview
                  key={previewKey}
                  diffText={combinedDiff.diffText}
                  fileCount={Math.max(1, combinedDiff.fileCount)}
                  isExpanded={expandedPreviewKey === previewKey}
                  isFullscreen={fullscreenPreviewKey === previewKey}
                  onToggle={() => {
                    if (expandedPreviewKey !== previewKey && fullscreenPreviewKey !== previewKey) {
                      markPreviewArtifactOpened(message.clientRequestId ?? null);
                    }
                    setExpandedPreviewKey((current) => {
                      if (fullscreenPreviewKey === previewKey) {
                        setFullscreenPreviewKey(null);
                        return null;
                      }

                      return current === previewKey ? null : previewKey;
                    });
                  }}
                  onToggleFullscreen={() => {
                    markPreviewArtifactOpened(message.clientRequestId ?? null);
                    setFullscreenPreviewKey((current) => {
                      const nextKey = current === previewKey ? null : previewKey;

                      if (nextKey) {
                        setExpandedPreviewKey(previewKey);
                      }

                      return nextKey;
                    });
                  }}
                />
              );
            })()}
            {inlinePreviewTargets.map((target) => {
              const previewKey = `${message.id}-${target.url}`;
              const matchedPreview = previewItemsByUrl.get(target.url);

              return (
                <InlineMessagePreview
                  key={previewKey}
                  target={target}
                  isExpanded={expandedPreviewKey === previewKey}
                  hasModalPreview
                  onOpenModalPreview={() => {
                    markPreviewArtifactOpened(message.clientRequestId ?? null);
                    markPreviewResourceOpened(target.url);
                    onOpenPreview(
                      matchedPreview
                        ? matchedPreview.id
                        : {
                            id: previewKey,
                            label: target.label,
                            url: target.url,
                            kind: target.kind,
                            source: 'message',
                          },
                      { fullscreen: true },
                    );
                  }}
                  onToggle={() => {
                    if (expandedPreviewKey !== previewKey) {
                      markPreviewArtifactOpened(message.clientRequestId ?? null);
                      markPreviewResourceOpened(target.url);
                    }
                    setExpandedPreviewKey((current) => (current === previewKey ? null : previewKey));
                  }}
                />
              );
            })}
          </div>
        ) : null}
      </div>
    );
  };

  const syncPendingComposerUploads = async (files: File[]) => {
    const attemptId = `composer-upload-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
    const nextPendingNames = new Set(files.map((file) => normalizeAttachmentName(file.name)).filter(Boolean));
    const nextPendingUploads = files.map((file) => ({
      attemptId,
      key: buildComposerFilePickKey(file),
      name: file.name,
      status: 'uploading' as const,
    }));
    const pendingKeys = new Set(nextPendingUploads.map((item) => item.key));

    setPendingComposerUploads((current) => [
      ...current.filter((item) => !pendingKeys.has(item.key) && !nextPendingNames.has(normalizeAttachmentName(item.name))),
      ...nextPendingUploads,
    ]);

    let result: ComposerFilePickResult = { items: [] };

    try {
      result = (await onPickComposerFiles(files)) ?? { items: [] };
    } catch {
      result = {
        items: nextPendingUploads.map((item) => ({
          key: item.key,
          fileName: item.name,
          status: 'failed',
        })),
      };
    }

    const resultByKey = new Map<string, ComposerFilePickResult['items'][number]>(
      result.items.map((item) => [item.key, item]),
    );

    setPendingComposerUploads((current) =>
      current.flatMap((item) => {
        if (item.attemptId !== attemptId || !pendingKeys.has(item.key)) {
          return [item];
        }

        const matched = resultByKey.get(item.key);

        if (!matched || matched.status === 'failed') {
          return [{ ...item, status: 'failed', reason: matched?.reason }];
        }

        return [];
      }),
    );
  };

  const handleComposerFileChange = (event: ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(event.target.files ?? []);
    event.target.value = '';

    if (files.length === 0) {
      return;
    }

    void syncPendingComposerUploads(files);
  };

  const handleComposerPaste = (event: ClipboardEvent<HTMLTextAreaElement>) => {
    const clipboardData = event.clipboardData;

    if (!clipboardData) {
      return;
    }
    const files = resolveComposerPasteFiles(clipboardData);

    if (files.length === 0) {
      return;
    }

    event.preventDefault();
    void syncPendingComposerUploads(files);
  };

  const handleComposerSendRequest = (draftText?: string) => {
    const nextDraft = typeof draftText === 'string' ? draftText : composerDraftValueRef.current;
    const normalizedReplyReferenceRequestId = replyReferenceRequestId.trim();
    const result = normalizedReplyReferenceRequestId && onSendReplyToResponse
      ? onSendReplyToResponse({ draftText: nextDraft, parentRequestId: normalizedReplyReferenceRequestId })
      : onSend(nextDraft);

    if (result !== 'sent') {
      return;
    }

    if (showRoomsShareHeader && roomShareExpandMode === 'latest') {
      shouldFollowLatestRoomShareGroupRef.current = true;
    }

    lastSyncedComposerDraftRef.current = '';
    composerDraftValueRef.current = '';
    setComposerDraftValue('');
    onDraftChange('');
    if (normalizedReplyReferenceRequestId) {
      setReplyReferenceRequestId('');
    }
    forceComposerDraftSync();
  };

  const handleComposerClear = () => {
    lastSyncedComposerDraftRef.current = '';
    composerDraftValueRef.current = '';
    onDraftChange('');
    onClearDraft();
  };

  const dismissPendingComposerUpload = (key: string) => {
    setPendingComposerUploads((current) => current.filter((item) => item.key !== key));
  };

  const composerAttachmentStrip =
    pendingComposerUploads.length > 0 || composerAttachments.length > 0 ? (
      <div className="app-chat-panel__composer-attachment-strip" aria-live="polite">
        {pendingComposerUploads.map((upload) => (
          <div
            key={`pending:${upload.key}`}
            className={`app-chat-panel__composer-attachment-chip app-chat-panel__composer-attachment-chip--pending${
              upload.status === 'failed' ? ' app-chat-panel__composer-attachment-chip--failed' : ''
            }`}
            title={upload.status === 'failed' ? upload.reason ?? '업로드 실패' : undefined}
          >
            <span className="app-chat-panel__composer-attachment-name">{upload.name}</span>
            <span className="app-chat-panel__composer-attachment-pending-label">
              {upload.status === 'failed' ? upload.reason ?? '업로드 실패' : '업로드 중'}
            </span>
            {upload.status === 'failed' ? (
              <Button
                type="text"
                size="small"
                className="app-chat-panel__composer-attachment-remove"
                icon={<CloseOutlined />}
                aria-label={`${upload.name} 업로드 실패 항목 닫기`}
                onClick={() => {
                  dismissPendingComposerUpload(upload.key);
                }}
              />
            ) : null}
          </div>
        ))}
        {composerAttachments.map((attachment) => (
          <div key={attachment.id} className="app-chat-panel__composer-attachment-chip">
            <span className="app-chat-panel__composer-attachment-name">{attachment.name}</span>
            <Button
              type="text"
              size="small"
              className="app-chat-panel__composer-attachment-remove"
              icon={<CloseOutlined />}
              aria-label={`${attachment.name} 첨부 제거`}
              onClick={() => {
                onRemoveComposerAttachment(attachment.id);
              }}
            />
          </div>
        ))}
      </div>
    ) : null;
  const replyReferenceRequest = replyReferenceRequestId.trim() ? requestStateMap.get(replyReferenceRequestId.trim()) ?? null : null;
  const replyReferenceSummary = replyReferenceRequest
    ? summarizeQueuedText(
        String(replyReferenceRequest.responseText ?? '').trim() ||
          replyReferenceRequest.statusMessage?.trim() ||
          replyReferenceRequest.userText ||
          '선택한 답변',
        96,
      )
    : '';

  useEffect(() => {
    if (!replyReferenceRequestId.trim()) {
      return;
    }

    if (requestStateMap.has(replyReferenceRequestId.trim())) {
      return;
    }

    setReplyReferenceRequestId('');
  }, [replyReferenceRequestId, requestStateMap]);

  const composerPlaceholder = isComposerDisabled
    ? '권한이 있는 컨텍스트가 없어 입력할 수 없습니다.'
    : replyReferenceRequest
      ? '선택한 답변을 바탕으로 새 대화에 이어서 보낼 내용을 입력하세요. 첨부만 추가해서 보내도 됩니다.'
      : showRoomsShareHeader
        ? isMobileViewport
          ? '공유채팅에 보낼 내용을 입력하세요.'
          : '공유채팅에 보낼 내용을 입력하세요. Ctrl+Enter로 전송하고, 답변과 preview를 이 대화에서 바로 이어서 확인할 수 있습니다.'
        : isMobileViewport
          ? '메시지를 입력하세요.'
          : '메시지를 입력하세요. Ctrl+Enter로 전송하고, 실시간 응답과 preview 링크를 이 대화에서 바로 이어서 다룰 수 있습니다.';

  return (
    <div
      ref={viewportRef}
      className="app-chat-panel__conversation-view"
      onScroll={onViewportScroll}
      onTouchEnd={onViewportTouchEnd}
      onTouchMove={onViewportTouchMove}
      onTouchStart={onViewportTouchStart}
    >
      {modalContextHolder}
      {shouldShowConversationLoadingOverlay ? (
        <div className="app-chat-panel__conversation-loading" aria-live="polite">
          <Spin size="large" />
          <strong>{conversationLoadingLabel}</strong>
          <span>재접속 중에는 채팅방 내용을 다시 맞춘 뒤 자연스럽게 표시합니다.</span>
        </div>
      ) : null}

      {showBusyOverlay ? (
        <div className="app-chat-panel__busy-overlay" aria-live="polite" aria-busy="true">
          <Spin size="large" />
          <strong>{busyOverlayLabel}</strong>
          <span>처리가 끝나면 화면이 바로 갱신됩니다.</span>
        </div>
      ) : null}

      <div
        className={`app-chat-panel__conversation-view-inner${shouldShowConversationLoadingOverlay ? ' is-loading' : ''}${
          showBusyOverlay ? ' is-busy' : ''
        }`}
      >
        {showResourceStrip ? (
          <div className="app-chat-panel__conversation-toolbar">
            <Button
              type={isResourceStripOpen ? 'default' : 'text'}
              size="small"
              className="app-chat-panel__conversation-toggle"
              icon={<PaperClipOutlined />}
              aria-label={isResourceStripOpen ? '채팅 리소스 숨기기' : '채팅 리소스 보기'}
              onClick={onToggleResourceStrip}
            />
          </div>
        ) : null}

        {showResourceStrip && isResourceStripOpen ? (
          <div className="app-chat-panel__resource-strip">
            {previewItems.length > 0 ? (
              <>
                <label className="app-chat-panel__resource-strip-filter">
                  <Checkbox
                    checked={showLatestResourceOnly}
                    onChange={(event) => {
                      setShowLatestResourceOnly(event.target.checked);
                    }}
                  >
                    같은 파일명은 최종 리소스만 보기
                  </Checkbox>
                </label>
                <div className="app-chat-panel__resource-strip-list">
                  {visiblePreviewItems.map((item) => (
                    <button
                      key={item.id}
                      type="button"
                      className="app-chat-panel__resource-chip"
                      onClick={() => {
                        markPreviewResourceOpened(item.url);
                        onOpenPreview(item.id);
                      }}
                    >
                      <span className="app-chat-panel__resource-chip-main">
                        <span className="app-chat-panel__resource-chip-icon" aria-hidden="true">
                          {resolveChatPreviewGlyph(item.kind as ChatPreviewKind)}
                        </span>
                        <span className="app-chat-panel__resource-chip-label" title={item.label}>
                          {item.label}
                        </span>
                      </span>
                      <span className="app-chat-panel__resource-chip-meta" aria-label="preview 형식">
                        {buildResourceChipMeta(item)}
                      </span>
                    </button>
                  ))}
                </div>
              </>
            ) : (
              <span className="app-chat-panel__resource-strip-empty">
                현재 대화에 바로 열 수 있는 리소스가 없습니다.
              </span>
            )}
          </div>
        ) : null}

        {showRoomsShareHeader ? (
          <div className="app-chat-panel__rooms-share-header">
            <div className="app-chat-panel__rooms-share-header-main">
              <div className="app-chat-panel__rooms-share-header-copy">
                <div className="app-chat-panel__rooms-share-header-title-wrap">
                  <strong className="app-chat-panel__rooms-share-header-title">
                    {roomShareMenuTitle}
                  </strong>
                  <span
                    className={`app-chat-panel__rooms-share-live-indicator app-chat-panel__rooms-share-live-indicator--${
                      isLiveConnected ? 'connected' : 'disconnected'
                    }`}
                    aria-label={isLiveConnected ? '웹소켓 연결 정상' : '웹소켓 연결 끊김'}
                    title={isLiveConnected ? '웹소켓 연결 정상' : '웹소켓 연결 끊김'}
                  />
                </div>
              </div>
              <div className="app-chat-panel__rooms-share-header-actions">
                <div className="app-chat-panel__rooms-share-nav" aria-label="대화 이동">
                    <Button
                      type="text"
                      size="small"
                      className={`app-chat-panel__rooms-share-action app-chat-panel__rooms-share-action--nav${roomsShareUseSharedPageNav ? ' app-chat-panel__rooms-share-action--nav-shared' : ''}`}
                      icon={roomsShareUseSharedPageNav ? <LeftOutlined /> : undefined}
                      disabled={!canMoveToPreviousRoomShareGroup}
                      onClick={() => {
                        moveRoomShareGroupSelection('previous');
                      }}
                    >
                      {roomsShareUseSharedPageNav ? '이전' : <span className="app-chat-panel__rooms-share-action-label">{'< 이전'}</span>}
                    </Button>
                    <Button
                      type="text"
                      size="small"
                      className={`app-chat-panel__rooms-share-action app-chat-panel__rooms-share-action--nav${roomsShareUseSharedPageNav ? ' app-chat-panel__rooms-share-action--nav-shared' : ''}`}
                      icon={roomsShareUseSharedPageNav ? <RightOutlined /> : undefined}
                      iconPosition={roomsShareUseSharedPageNav ? 'end' : undefined}
                      disabled={!canMoveToNextRoomShareGroup}
                      onClick={() => {
                        moveRoomShareGroupSelection('next');
                      }}
                    >
                      {roomsShareUseSharedPageNav ? '다음' : <span className="app-chat-panel__rooms-share-action-label">{'다음 >'}</span>}
                    </Button>
                  </div>
                {roomsHeaderMenuItems && onRoomsHeaderMenuAction ? (
                  <div className="app-chat-panel__rooms-share-window-actions">
                    <Dropdown
                      trigger={['click']}
                      placement="bottomRight"
                      menu={{
                        items: roomsHeaderMenuItems,
                        onClick: ({ key }) => {
                          onRoomsHeaderMenuAction(String(key));
                        },
                      }}
                    >
                      <Button
                        type="text"
                        size="small"
                        className="app-chat-panel__rooms-share-action app-chat-panel__rooms-share-action--tool"
                        icon={<SettingOutlined />}
                        aria-label="채팅방 설정"
                        title="채팅방 설정"
                      >
                        <span className="app-chat-panel__rooms-share-tool-label">설정</span>
                      </Button>
                    </Dropdown>
                    {onRoomsHeaderMinimize ? (
                      <Button
                        type="text"
                        size="small"
                        className="app-chat-panel__rooms-share-action app-chat-panel__rooms-share-action--icon"
                        icon={<MinusOutlined />}
                        aria-label="채팅 최소화"
                        title="채팅 최소화"
                        onMouseDown={(event) => {
                          event.preventDefault();
                        }}
                        onClick={() => {
                          onRoomsHeaderMinimize();
                        }}
                      />
                    ) : null}
                    {onRoomsHeaderClose ? (
                      <Button
                        type="text"
                        size="small"
                        danger
                        className="app-chat-panel__rooms-share-action app-chat-panel__rooms-share-action--icon app-chat-panel__rooms-share-action--close"
                        icon={<CloseOutlined />}
                        aria-label="채팅 닫기"
                        title="채팅 닫기"
                        onMouseDown={(event) => {
                          event.preventDefault();
                        }}
                        onClick={() => {
                          onRoomsHeaderClose();
                        }}
                      />
                    ) : null}
                  </div>
                ) : null}
              </div>
            </div>
            <div className="app-chat-panel__rooms-share-header-sub">
                <div className="app-chat-panel__rooms-share-current">
                  <span className="app-chat-panel__rooms-share-summary">
                    {roomShareHeaderSummaryLabel}
                  </span>
                </div>
                <Dropdown
                  trigger={['click']}
                  placement="bottomRight"
                  menu={{
                    items: roomShareExpandModeMenuItems,
                    selectable: true,
                    selectedKeys: [roomShareExpandMode],
                    onClick: ({ key }) => {
                      setRoomShareExpandMode(key as RoomShareExpandMode);
                    },
                  }}
                >
                  <Button
                    type="text"
                    size="small"
                    className={`app-chat-panel__rooms-share-action app-chat-panel__rooms-share-filter${
                      roomShareExpandMode !== 'latest' ? ' app-chat-panel__rooms-share-filter--active' : ''
                    }`}
                    aria-label={`공유형 필터: ${ROOM_SHARE_EXPAND_MODE_LABELS[roomShareExpandMode]} ${roomShareProgressLabel}`.trim()}
                    title={`공유형 필터: ${ROOM_SHARE_EXPAND_MODE_LABELS[roomShareExpandMode]} ${roomShareProgressLabel}`.trim()}
                    icon={<FilterOutlined />}
                  >
                    {roomShareExpandMode === 'latest' ? '필터' : ROOM_SHARE_EXPAND_MODE_LABELS[roomShareExpandMode]}
                  </Button>
                </Dropdown>
              </div>
          </div>
        ) : null}

        <div className="app-chat-panel__messages">
          {isLoadingOlderMessages || pullToLoadDistance > 0 ? (
            <div
              className={`app-chat-panel__history-loader${
                isLoadingOlderMessages ? ' is-loading' : ''
              }${isPullToLoadArmed ? ' is-armed' : ''}`}
              style={{
                maxHeight: `${Math.max(isLoadingOlderMessages ? 52 : 0, pullToLoadDistance)}px`,
                opacity: isLoadingOlderMessages || pullToLoadDistance > 0 ? 1 : 0.72,
              }}
            >
              <Spin size="small" spinning={isLoadingOlderMessages} />
              <span>
                {isLoadingOlderMessages
                  ? '이전 대화를 동기화하는 중입니다.'
                  : isPullToLoadArmed
                    ? '손을 놓으면 이전 대화를 더 불러옵니다.'
                    : hasOlderMessages
                      ? '최상단에서 아래로 끌어당겨 이전 대화를 불러오세요.'
                      : '이전 대화가 없습니다.'}
              </span>
            </div>
          ) : null}
          <div className="app-chat-panel__messages-content">
            {!hasConversationMessages ? (
              <div className="app-chat-panel__messages-empty" aria-live="polite">
                <div className="app-chat-panel__messages-empty-card">
                  <strong>아직 대화 콘텐츠가 없습니다.</strong>
                  <span>첫 요청을 보내면 이 영역이 viewport 높이에 맞춰 바로 채워집니다.</span>
                </div>
              </div>
            ) : null}
            {useSharedRoomsBubbleFlow && roomShareExpandMode === 'latest' && roomShareHiddenBeforeCount > 0 ? (
              <div className="app-chat-panel__messages-omission" aria-label={`이전 채팅 ${roomShareHiddenBeforeCount}건 숨김`}>
                <span className="app-chat-panel__messages-omission-line" aria-hidden="true" />
                <span className="app-chat-panel__messages-omission-text">생략 {roomShareHiddenBeforeCount}건</span>
                <span className="app-chat-panel__messages-omission-line" aria-hidden="true" />
              </div>
            ) : null}
            {useSharedRoomsBubbleFlow && roomShareExpandMode === 'latest' && roomShareCollapsedActivitySummary.length > 0 ? (
              <section className="app-chat-panel__rooms-share-activity" aria-label="현재 진행 상황">
                <strong className="app-chat-panel__rooms-share-activity-title">현재 진행 상황</strong>
                <div className="app-chat-panel__rooms-share-activity-list">
                  {roomShareCollapsedActivitySummary.map((item) => (
                    <span key={item} className="app-chat-panel__rooms-share-activity-item">{item}</span>
                  ))}
                </div>
              </section>
            ) : null}
            {useSharedRoomsSimplifiedView
              ? roomShareNavigableGroups.map((entry) => {
                const representativeRequest = entry.representativeRequest;
                const visibleMessages = Array.isArray(entry.messages) ? entry.messages.filter((message) => !isActivityLogMessage(message)) : [];
                const collapsedVisibleFinalMessage =
                  visibleMessages.length > 0 ? visibleMessages[visibleMessages.length - 1] : null;
                const representativeAttentionState = representativeRequest
                  ? systemExecutionAttentionStateByRequestId.get(representativeRequest.requestId)
                  : undefined;
                const representativePromptTargets = representativeAttentionState?.promptTargets ?? [];
                const isRepresentativePromptManuallyCompleted =
                  representativeAttentionState?.isPromptManuallyCompleted === true;
                const representativeHasPendingPromptBadge =
                  representativeAttentionState?.hasPendingPromptBadge === true;
                const representativeHasPendingVerificationBadge =
                  representativeAttentionState?.hasPendingVerificationBadge === true;
                const representativeManualCompletionTypes = buildManualCompletionTypes({
                  hasPendingPromptBadge: representativeHasPendingPromptBadge,
                  hasPendingVerificationBadge: representativeHasPendingVerificationBadge,
                });
                const isRepresentativeManualCompletionSaving =
                  representativeRequest != null &&
                  representativeManualCompletionTypes.some((type) =>
                    pendingManualCompletionActionKeySet.has(`${type}:${representativeRequest.requestId}`),
                  );
                const canCompletePromptFromCard =
                  representativeRequest != null &&
                  representativePromptTargets.length > 0 &&
                  !isRepresentativePromptManuallyCompleted &&
                  !isRequestInFlightStatus(representativeRequest.status);
                const canCompleteVerificationFromCard =
                  representativeRequest != null &&
                  representativePromptTargets.length === 0 &&
                  representativeHasPendingVerificationBadge &&
                  !isRequestInFlightStatus(representativeRequest.status);
                const canReplyToCardResponse =
                  representativeRequest != null &&
                  representativeRequest.hasResponse &&
                  representativePromptTargets.length === 0 &&
                  !isRequestInFlightStatus(representativeRequest.status);
                const isCardReplyReferenceActive =
                  representativeRequest != null &&
                  canReplyToCardResponse &&
                  replyReferenceRequestId.trim() === representativeRequest.requestId.trim();

                if (!representativeRequest) {
                  return null;
                }

                if (roomShareVisibleGroupIdSet && !roomShareVisibleGroupIdSet.has(entry.groupId)) {
                  return null;
                }

                if (visibleMessages.length === 0) {
                  return (
                    <SharedRoomsRequestCard
                      key={entry.groupId}
                      request={representativeRequest}
                      attentionState={representativeAttentionState}
                      canCompletePrompt={canCompletePromptFromCard}
                      canCompleteVerification={canCompleteVerificationFromCard}
                      canReplyToResponse={canReplyToCardResponse}
                      isManualCompletionSaving={isRepresentativeManualCompletionSaving}
                      isReplyReferenceActive={isCardReplyReferenceActive}
                      onCompletePrompt={() => {
                        void completeManualBadge({
                          requestId: representativeRequest.requestId,
                          type: 'prompt',
                        });
                      }}
                      onCompleteVerification={() => {
                        void completeManualBadge({
                          requestId: representativeRequest.requestId,
                          type: 'verification',
                        });
                      }}
                      onReplyToResponse={() => {
                        activateReplyReference(representativeRequest.requestId, {
                          focusComposer: false,
                        });
                      }}
                      onSelect={() => {
                        setSelectedRoomShareGroupId(entry.groupId);
                        scrollToRoomShareGroup(entry.groupId);
                      }}
                    />
                  );
                }

                return (
                  <Fragment key={entry.groupId}>
                    {visibleMessages.map((message) => (
                      <Fragment key={message.id}>
                        {renderConversationMessage(message, {
                          defaultCollapsedPrompt: message.id === collapsedVisibleFinalMessage?.id,
                        })}
                      </Fragment>
                    ))}
                  </Fragment>
                );
              })
              : messageEntries.map((entry) => {
            if (
              entry.kind === 'group' &&
              roomShareVisibleGroupIdSet &&
              !roomShareVisibleGroupIdSet.has(entry.groupId)
            ) {
              return null;
            }

            if (entry.kind === 'single') {
              return renderConversationMessage(entry.message);
            }

            const groupedRequests = Array.from(
              new Map(
                entry.requestIds
                  .map((requestId) => requestStateMap.get(requestId))
                  .filter((item): item is ChatConversationRequest => item != null)
                  .map((request) => [request.requestId, request] as const),
              ).values(),
            );
            const representativeGroupedRequest =
              entry.request && groupedRequests.some((request) => request.requestId === entry.request?.requestId)
                ? entry.request
                : groupedRequests[0] ?? entry.request;
            const collapseSummary = resolveCollapsedGroupSummary(
              entry.request,
              groupedRequests,
              entry.messages,
              systemExecutionAttentionStateByRequestId,
              {
                preferredMessageId: representativeGroupedRequest?.responseMessageId ?? null,
              },
            );
            const isGroupExpanded = expandedGroupIds.includes(entry.groupId);
            const canToggleGroup = collapseSummary.hiddenMessageCount > 0;
            const shouldCollapseGroup = canToggleGroup && collapseSummary.shouldCollapseByDefault && !isGroupExpanded;
            const representativeMessage =
              entry.messages.find((message) => message.author === 'user') ??
              entry.messages.find((message) => !isActivityLogMessage(message)) ??
              entry.messages[0];
            const renderedMessages = shouldCollapseGroup ? collapseSummary.displayedMessages : entry.messages;
            const visibleGroupMessages = renderedMessages.filter((message) => !isActivityLogMessage(message));
            const collapsedVisibleFinalMessage =
              shouldCollapseGroup && visibleGroupMessages.length > 0
                ? visibleGroupMessages[visibleGroupMessages.length - 1]
                : null;

            if (useSharedRoomsBubbleFlow) {
              return (
                <section key={entry.key} className="app-chat-message-group app-chat-message-group--rooms-share">
                  <div className="app-chat-message-group__body">
                    {visibleGroupMessages.map((message) => (
                      <Fragment key={message.id}>
                        {renderConversationMessage(message, {
                          defaultCollapsedPrompt: message.id === collapsedVisibleFinalMessage?.id,
                        })}
                      </Fragment>
                    ))}
                  </div>
                </section>
              );
            }
            const groupStatusSummary = resolveAggregatedRequestStatusSummary(
              groupedRequests.length > 0 ? groupedRequests : entry.request ? [entry.request] : [],
              systemExecutionAttentionStateByRequestId,
            );
            const rootGroupRequest = representativeGroupedRequest ?? entry.request ?? groupedRequests[0] ?? null;
            const groupActivityOverviewTarget = resolveVisibleActivityOverviewRequests(
              groupedRequests.length > 0 ? groupedRequests : rootGroupRequest ? [rootGroupRequest] : [],
              systemExecutionActivityOverviewByRequestId,
              systemExecutionAttentionStateByRequestId,
            ).sort((left, right) => compareRepresentativeRequests(left, right, systemExecutionAttentionStateByRequestId))[0] ?? null;
            const groupTitleSource =
              entry.request?.userText?.trim() ||
              representativeMessage?.text?.trim() ||
              `요청 ${entry.groupId.slice(0, 8)}`;
          const groupTitle = summarizeQueuedText(groupTitleSource, isMobileViewport ? 52 : 88);
          const groupDetailText = getRequestDetailText(entry.request);
          const parentRequestId = entry.request?.parentRequestId?.trim() || '';
          const parentRequest = parentRequestId ? requestStateMap.get(parentRequestId) : null;
          const groupRelationText = parentRequest
            ? `부모 요청: ${summarizeQueuedText(parentRequest.userText || parentRequest.responseText || parentRequestId, 52)}`
            : null;
          const childComposerGroupId = entry.groupId;
          const childComposerParentRequestId = resolveChildComposerParentRequestId(entry.request, requestStateMap);
          const childComposerDraft = childComposerDraftsByGroupId[childComposerGroupId] ?? '';
          const isChildComposerOpen = openedChildComposerGroupIds.includes(childComposerGroupId);
          const isChildComposerSubmitting = submittingChildComposerGroupIds.includes(childComposerGroupId);
          const canSubmitChildComposer =
            childComposerParentRequestId.length > 0 && childComposerDraft.trim().length > 0 && !isChildComposerSubmitting;
          const renderedRequestSectionIds = new Set<string>();
          const shouldHideGroupChrome = useSharedRoomsBubbleFlow;

          if (shouldHideGroupChrome) {
            return (
              <Fragment key={entry.key}>
                {visibleGroupMessages.map((message) => (
                  <Fragment key={message.id}>
                    {renderConversationMessage(message, {
                      defaultCollapsedPrompt: message.id === collapsedVisibleFinalMessage?.id,
                    })}
                  </Fragment>
                ))}
              </Fragment>
            );
          }

          return (
            <section key={entry.key} className="app-chat-message-group">
              <header className="app-chat-message-group__header">
                <div className="app-chat-message-group__header-row">
                  <div className="app-chat-message-group__header-meta">
                    <span className="app-chat-message-group__eyebrow">요청 묶음</span>
                    {groupStatusSummary.label ? (
                      <span
                        className={`app-chat-message-group__status app-chat-message-group__status--${groupStatusSummary.tone}`}
                        aria-label={`요청 상태 ${groupStatusSummary.label}`}
                      >
                        {groupStatusSummary.label}
                      </span>
                    ) : null}
                  </div>
                  <div className="app-chat-message-group__header-actions">
                    {groupActivityOverviewTarget ? (
                      <Button
                        type="text"
                        size="small"
                        className="app-chat-message-group__toggle"
                        icon={<ProfileOutlined />}
                        aria-label="요청묶음 요약 보기"
                        onClick={() => {
                          setExpandedSystemExecutionActivityRequestId(groupActivityOverviewTarget.requestId);
                          setSystemExecutionDisplayMode('expanded');
                        }}
                      >
                        요약
                      </Button>
                    ) : null}
                    {childComposerParentRequestId ? (
                      <Button
                        type={isChildComposerOpen ? 'default' : 'text'}
                        size="small"
                        className="app-chat-message-group__child-action"
                        icon={<PlusOutlined />}
                        aria-label={isChildComposerOpen ? '자식 요청 입력 닫기' : '자식 요청 입력 열기'}
                        onClick={() => {
                          if (isChildComposerOpen) {
                            closeChildComposer(childComposerGroupId);
                            return;
                          }

                          openChildComposer(childComposerGroupId);
                        }}
                      >
                        자식 요청
                      </Button>
                    ) : null}
                    {canToggleGroup ? (
                      <Button
                        type="text"
                        size="small"
                        className="app-chat-message-group__toggle"
                        icon={isGroupExpanded ? <UpOutlined /> : <DownOutlined />}
                        aria-label={isGroupExpanded ? '중간 대화 접기' : '중간 대화 펼치기'}
                        onClick={() => {
                          setExpandedGroupIds((current) =>
                            current.includes(entry.groupId)
                              ? current.filter((groupId) => groupId !== entry.groupId)
                              : [...current, entry.groupId],
                          );
                        }}
                      >
                        {isGroupExpanded ? '요약 보기' : `숨김 대화 ${collapseSummary.hiddenMessageCount}개 펼치기`}
                      </Button>
                    ) : null}
                  </div>
                </div>
                <strong className="app-chat-message-group__title">{groupTitle}</strong>
                {groupRelationText ? <span className="app-chat-message-group__detail">{groupRelationText}</span> : null}
                {groupDetailText ? <span className="app-chat-message-group__detail">{groupDetailText}</span> : null}
                {isChildComposerOpen ? (
                  <div className="app-chat-message-group__child-composer">
                    <Input.TextArea
                      ref={(instance) => {
                        setChildComposerRef(childComposerGroupId, instance);
                      }}
                      value={childComposerDraft}
                      autoSize={{ minRows: 2, maxRows: 5 }}
                      placeholder="이 요청 자식으로 바로 이어서 처리할 내용을 입력하세요."
                      disabled={isChildComposerSubmitting}
                      onChange={(event) => {
                        const nextValue = event.target.value;
                        setChildComposerDraftsByGroupId((current) => ({
                          ...current,
                          [childComposerGroupId]: nextValue,
                        }));
                      }}
                      onKeyDown={(event) => {
                        if (event.key !== 'Enter' || event.nativeEvent.isComposing) {
                          return;
                        }

                        const hasSubmitModifier = event.ctrlKey || event.metaKey;
                        if (!hasSubmitModifier) {
                          event.stopPropagation();
                          return;
                        }

                        event.preventDefault();
                        event.stopPropagation();

                        if (!canSubmitChildComposer) {
                          return;
                        }

                        void submitChildComposer(childComposerGroupId, childComposerParentRequestId);
                      }}
                    />
                    <div className="app-chat-message-group__child-composer-actions">
                      <span className="app-chat-message-group__child-composer-hint">Ctrl+Enter / Cmd+Enter 전송, Enter 줄바꿈</span>
                      <div className="app-chat-message-group__child-composer-buttons">
                        <Button
                          size="small"
                          onClick={() => {
                            closeChildComposer(childComposerGroupId);
                          }}
                          disabled={isChildComposerSubmitting}
                        >
                          닫기
                        </Button>
                        <Button
                          type="primary"
                          size="small"
                          icon={<ThunderboltOutlined />}
                          loading={isChildComposerSubmitting}
                          disabled={!canSubmitChildComposer}
                          onClick={() => {
                            void submitChildComposer(childComposerGroupId, childComposerParentRequestId);
                          }}
                        >
                          즉시 전송
                        </Button>
                      </div>
                    </div>
                  </div>
                ) : null}
              </header>
              <div className="app-chat-message-group__body">
                {visibleGroupMessages.map((message) => {
                  const messageRequestId = message.clientRequestId?.trim() || '';

                  if (messageRequestId) {
                    renderedRequestSectionIds.add(messageRequestId);
                  }

                  return (
                    <Fragment key={message.id}>
                      {renderConversationMessage(message, {
                        defaultCollapsedPrompt: message.id === collapsedVisibleFinalMessage?.id,
                      })}
                    </Fragment>
                  );
                })}
              </div>
            </section>
          );
            })}
            {useSharedRoomsBubbleFlow && roomShareExpandMode === 'latest' && roomShareHiddenAfterCount > 0 ? (
              <div className="app-chat-panel__messages-omission" aria-label={`이후 채팅 ${roomShareHiddenAfterCount}건 숨김`}>
                <span className="app-chat-panel__messages-omission-line" aria-hidden="true" />
                <span className="app-chat-panel__messages-omission-text">생략 {roomShareHiddenAfterCount}건</span>
                <span className="app-chat-panel__messages-omission-line" aria-hidden="true" />
              </div>
            ) : null}
            {useSharedRoomsBubbleFlow && roomShareExpandMode === 'pending' && roomShareNavigableGroups.length === 0 ? (
              <div className="app-chat-panel__messages-omission app-chat-panel__messages-omission--empty">
                <span className="app-chat-panel__messages-omission-line" aria-hidden="true" />
                <span className="app-chat-panel__messages-omission-text">현재 처리중이거나 확인이 필요한 건이 없습니다.</span>
                <span className="app-chat-panel__messages-omission-line" aria-hidden="true" />
              </div>
            ) : null}
          </div>
      </div>

      {!shouldHideSystemStatusPanel && !useSharedRoomsSimplifiedView && visibleSystemExecutionRequests.length > 0 ? systemExecutionDisplayMode === 'hidden' ? (
        <div className="app-chat-panel__system-status-slot app-chat-panel__system-status-slot--bottom">
          <div
            className={`app-chat-panel__system-status app-chat-panel__system-status--hidden-summary${
              isSystemStatusPending ? ' app-chat-panel__system-status--pending' : ''
            }`}
          >
            <span className="app-chat-panel__system-status-label">시스템</span>
            {hiddenSystemStatusSummaryText ? (
              <span className="app-chat-panel__system-status-summary-inline">{hiddenSystemStatusSummaryText}</span>
            ) : null}
            {isSystemStatusPending ? (
              <div className="app-chat-panel__system-status-dots" aria-label="처리 중">
                <span className="app-chat-panel__system-status-dot" />
                <span className="app-chat-panel__system-status-dot" />
                <span className="app-chat-panel__system-status-dot" />
              </div>
            ) : null}
            <Button
              type="text"
              size="small"
              className="app-chat-panel__system-status-records-toggle app-chat-panel__system-status-records-toggle--icon-only"
              icon={<DownOutlined />}
              aria-label="실행 기록 펼치기"
              onClick={() => {
                setSystemExecutionDisplayMode('expanded');
              }}
            />
          </div>
        </div>
      ) : (
        <div className="app-chat-panel__system-status-slot app-chat-panel__system-status-slot--bottom">
          <section
            className={`app-chat-panel__system-status app-chat-panel__system-status--records${
              isSystemStatusPending ? ' app-chat-panel__system-status--pending' : ''
            }`}
          >
            <div className="app-chat-panel__system-status-records-header">
              <div className="app-chat-panel__system-status-records-heading">
                <span className="app-chat-panel__system-status-label">시스템</span>
                {systemExecutionMeta ? (
                  <span className="app-chat-panel__system-status-records-meta">{systemExecutionMeta}</span>
                ) : null}
                {isSystemStatusPending ? (
                  <div className="app-chat-panel__system-status-dots" aria-label="처리 중">
                    <span className="app-chat-panel__system-status-dot" />
                    <span className="app-chat-panel__system-status-dot" />
                    <span className="app-chat-panel__system-status-dot" />
                  </div>
                ) : null}
              </div>
              <div className="app-chat-panel__system-status-records-actions">
                {hasExpandedSystemExecutionActivityContent ? (
                  <>
                    <Button
                      type="text"
                      size="small"
                      className="app-chat-panel__system-status-records-toggle app-chat-panel__system-status-records-toggle--icon-only"
                      icon={<ArrowLeftOutlined />}
                      aria-label="돌아가기"
                      onClick={() => {
                        setExpandedSystemExecutionActivityRequestId(null);
                      }}
                    />
                    <Button
                      type="text"
                      size="small"
                      className="app-chat-panel__system-status-records-toggle app-chat-panel__system-status-records-toggle--icon-only"
                      icon={<CloseOutlined />}
                      aria-label="시스템 숨기기"
                      onClick={() => {
                        setExpandedSystemExecutionActivityRequestId(null);
                        setSystemExecutionDisplayMode('hidden');
                      }}
                    />
                  </>
                ) : (
                  <>
                    {!showRoomsShareHeader ? (
                      <>
                        <Select
                          size="small"
                          variant="borderless"
                          popupMatchSelectWidth={false}
                          className={`app-chat-panel__system-status-records-sort-select${
                            useCompactSystemExecutionControls ? ' app-chat-panel__system-status-records-sort-select--compact' : ''
                          }`}
                          value={systemExecutionSort}
                          options={systemExecutionSortOptions}
                          aria-label="실행 기록 정렬 기준"
                          onChange={(value) => {
                            setSystemExecutionSort(value);
                          }}
                        />
                        {useIconSystemExecutionFilterToggle ? (
                          <Button
                            type="text"
                            size="small"
                            className="app-chat-panel__system-status-filter-cycle"
                            icon={renderSystemExecutionFilterIcon(currentSystemExecutionFilterOption.value)}
                            aria-label={`${currentSystemExecutionFilterOption.ariaLabel}. 누르면 ${mobileSystemExecutionNextFilterOption.label} 필터로 전환합니다.`}
                            title={`${currentSystemExecutionFilterOption.label} 필터 적용 중`}
                            onClick={() => {
                              setSystemExecutionFilter(mobileSystemExecutionNextFilterOption.value);
                            }}
                          >
                            {currentSystemExecutionFilterOption.compactLabel}
                          </Button>
                        ) : useCompactSystemExecutionControls ? (
                          <Segmented
                            size="small"
                            className="app-chat-panel__system-status-filter-segmented"
                            aria-label="실행 기록 필터"
                            options={systemExecutionFilterOptions.map((option) => ({
                              label: option.compactLabel,
                              title: option.ariaLabel,
                              value: option.value,
                            }))}
                            value={systemExecutionFilter}
                            onChange={(value) => {
                              setSystemExecutionFilter(value as SystemExecutionFilter);
                            }}
                          />
                        ) : (
                          <div className="app-chat-panel__system-status-filter-group" role="tablist" aria-label="실행 기록 필터">
                            {systemExecutionFilterOptions.map((option) => (
                              <Button
                                key={option.value}
                                type="text"
                                size="small"
                                className={`app-chat-panel__system-status-filter-toggle${
                                  systemExecutionFilter === option.value
                                    ? ' app-chat-panel__system-status-filter-toggle--active'
                                    : ''
                                }`}
                                aria-label={option.ariaLabel}
                                aria-pressed={systemExecutionFilter === option.value}
                                onClick={() => {
                                  setSystemExecutionFilter(option.value);
                                }}
                              >
                                {option.label}
                              </Button>
                            ))}
                          </div>
                        )}
                      </>
                    ) : null}
                    <Button
                      type="text"
                      size="small"
                      className="app-chat-panel__system-status-records-toggle app-chat-panel__system-status-records-toggle--icon-only"
                      icon={systemExecutionDisplayMode === 'expanded' ? <UpOutlined /> : <DownOutlined />}
                      aria-label={systemExecutionDisplayMode === 'expanded' ? '실행 기록 접기' : '실행 기록 펼치기'}
                      onClick={() => {
                        setSystemExecutionDisplayMode((current) => (current === 'expanded' ? 'collapsed' : 'expanded'));
                      }}
                    />
                    <Button
                      type="text"
                      size="small"
                      className="app-chat-panel__system-status-records-toggle app-chat-panel__system-status-records-toggle--icon-only"
                      icon={<CloseOutlined />}
                      aria-label="시스템 숨기기"
                      onClick={() => {
                        setSystemExecutionDisplayMode('hidden');
                      }}
                    />
                  </>
                )}
              </div>
            </div>
            {systemExecutionDisplayMode === 'expanded' ? (
              <div
                ref={systemExecutionBodyRef}
                className={`app-chat-panel__system-status-records-body${
                  hasExpandedSystemExecutionActivityContent
                    ? ' app-chat-panel__system-status-records-body--activity-focus'
                    : ''
                }`}
                onScroll={handleSystemExecutionBodyScroll}
              >
                {hasExpandedSystemExecutionActivityContent ? (
                  <div className="app-chat-panel__system-execution-record-activity app-chat-panel__system-execution-record-activity--full">
                    {expandedSystemExecutionActivityPanels.map((panel, index) => (
                      <Fragment key={`${panel.request.requestId}:${index}`}>
                        {panel.summaryText ? (
                          <div className="app-chat-panel__system-execution-record-activity-focus">
                            {panel.summaryText}
                          </div>
                        ) : null}
                        {renderActivityOverviewBody(panel.overview, -1 - index, panel.request, {
                          attentionState: panel.attentionState ?? undefined,
                        })}
                      </Fragment>
                    ))}
                  </div>
                ) : canLoadOlderSystemExecutionRequests && (hasOlderMessages || isLoadingOlderMessages) ? (
                  <div className="app-chat-panel__system-status-history-loader">
                    <Spin size="small" spinning={isLoadingOlderMessages} />
                    <span>
                      {isLoadingOlderMessages
                        ? '오래된 실행 내역을 가져오는 중입니다.'
                        : '상단까지 올리면 오래된 실행 내역을 더 가져옵니다.'}
                    </span>
                    <Button
                      type="text"
                      size="small"
                      className="app-chat-panel__system-status-history-loader-button"
                      disabled={!hasOlderMessages || isLoadingOlderMessages}
                      onClick={() => {
                        triggerOlderSystemExecutionLoad();
                      }}
                    >
                      더 가져오기
                    </Button>
                  </div>
                ) : null}
                {!hasExpandedSystemExecutionActivityContent
                  ? useFlatSystemExecutionFilterView
                    ? orderedFilteredSystemExecutionRequests.map((request) =>
                        renderSystemExecutionRequestSummary(request, { depth: 0 }),
                      )
                    : orderedSystemExecutionRootRequests.flatMap((request) =>
                        renderSystemExecutionRootEntries(request, { filterToVisibleSet: true }),
                      )
                  : null}
              </div>
            ) : collapsedVisibleSystemExecutionRequest ? (
              <div className="app-chat-panel__system-status-records-body">
                {useFlatSystemExecutionFilterView
                  ? renderSystemExecutionRequestSummary(collapsedVisibleSystemExecutionRequest, {
                      isCollapsed: true,
                      depth: 0,
                    })
                  : renderSystemExecutionRootEntries(collapsedVisibleSystemExecutionRequest, {
                      isCollapsed: true,
                      filterToVisibleSet: true,
                    })}
              </div>
            ) : filteredSystemExecutionRequests.length === 0 ? (
              <div className="app-chat-panel__system-status-records-empty">
                <FilterOutlined aria-hidden="true" />
                <span>표시할 실행 기록이 없습니다.</span>
              </div>
            ) : null}
          </section>
        </div>
      ) : !shouldHideSystemStatusPanel && !useSharedRoomsSimplifiedView && activeSystemStatusText ? systemExecutionDisplayMode === 'hidden' ? (
        <div className="app-chat-panel__system-status-slot app-chat-panel__system-status-slot--bottom">
          <div
            className={`app-chat-panel__system-status app-chat-panel__system-status--hidden-summary${
              isSystemStatusPending ? ' app-chat-panel__system-status--pending' : ''
            }`}
          >
            <span className="app-chat-panel__system-status-label">시스템</span>
            <span className="app-chat-panel__system-status-summary-inline">{hiddenSystemStatusSummaryText}</span>
            {isSystemStatusPending ? (
              <div className="app-chat-panel__system-status-dots" aria-label="처리 중">
                <span className="app-chat-panel__system-status-dot" />
                <span className="app-chat-panel__system-status-dot" />
                <span className="app-chat-panel__system-status-dot" />
              </div>
            ) : null}
            <Button
              type="text"
              size="small"
              className="app-chat-panel__system-status-records-toggle app-chat-panel__system-status-records-toggle--icon-only"
              icon={<DownOutlined />}
              aria-label="시스템 보기"
              onClick={() => {
                setSystemExecutionDisplayMode('collapsed');
              }}
            />
          </div>
        </div>
      ) : (
        <div className="app-chat-panel__system-status-slot app-chat-panel__system-status-slot--bottom">
          <div
            className={`app-chat-panel__system-status${
              isSystemStatusPending ? ' app-chat-panel__system-status--pending' : ''
            }`}
          >
            <span className="app-chat-panel__system-status-label">시스템</span>
            <span className="app-chat-panel__system-status-summary-inline">{activeSystemStatusText}</span>
            {isSystemStatusPending ? (
              <div className="app-chat-panel__system-status-dots" aria-label="처리 중">
                <span className="app-chat-panel__system-status-dot" />
                <span className="app-chat-panel__system-status-dot" />
                <span className="app-chat-panel__system-status-dot" />
              </div>
            ) : null}
            <div className="app-chat-panel__system-status-records-actions">
              <Button
                type="text"
                size="small"
                className="app-chat-panel__system-status-records-toggle app-chat-panel__system-status-records-toggle--icon-only"
                icon={<CloseOutlined />}
                aria-label="시스템 숨기기"
                onClick={() => {
                  setSystemExecutionDisplayMode('hidden');
                }}
              />
            </div>
          </div>
        </div>
      ) : null}

      {showScrollToBottom ? (
        <div className="app-chat-panel__scroll-jump">
          <Button type="primary" shape="circle" icon={<DownOutlined />} aria-label="최하단으로 이동" onClick={onScrollToBottom} />
        </div>
      ) : null}

      <div className="app-chat-panel__composer">
        {useSharedComposerChrome ? (
          <div className="app-chat-panel__composer-topline app-chat-panel__composer-topline--shared">
            <div className="app-chat-panel__composer-utility-buttons">
              <Button
                icon={<PlusOutlined />}
                aria-label="파일 첨부"
                onClick={() => {
                  fileInputRef.current?.click();
                }}
                disabled={isComposerDisabled}
                loading={isComposerAttachmentUploading}
              />
            </div>
            <div className="app-chat-panel__composer-type app-chat-panel__composer-type--readonly">
              <Select
                value={selectedChatTypeOption?.value ?? '__shared-readonly__'}
                options={[
                  {
                    value: selectedChatTypeOption?.value ?? '__shared-readonly__',
                    label: (
                      <div className="app-chat-panel__type-option">
                        <span>{sharedComposerChatTypeLabel}</span>
                      </div>
                    ),
                  },
                ]}
                getPopupContainer={(triggerNode) => triggerNode.closest('.app-chat-panel') ?? document.body}
                disabled
              />
            </div>
            <div className="app-chat-panel__composer-type">
              <Select
                value={selectedCodexModel}
                placeholder="Codex 모델 선택"
                options={codexModelOptions.map((option) => ({
                  value: option.value,
                  label: (
                    <div className="app-chat-panel__type-option">
                      <span>{option.label}</span>
                    </div>
                  ),
                }))}
                getPopupContainer={(triggerNode) => triggerNode.closest('.app-chat-panel') ?? document.body}
                disabled={isComposerDisabled || codexModelOptions.length === 0}
                onChange={onSelectCodexModel}
              />
            </div>
            <div className="app-chat-panel__composer-model-token" title={sharedComposerModelTokenLimitLabel}>
              <span>{sharedComposerModelTokenLimitLabel}</span>
            </div>
            <div className="app-chat-panel__composer-actions app-chat-panel__composer-actions--shared">
              <div className="app-chat-panel__composer-action-buttons">
                <Button
                  type={isSendWithoutContextEnabled ? 'primary' : 'default'}
                  className={`app-chat-panel__composer-contextless-toggle${
                    isSendWithoutContextEnabled ? ' app-chat-panel__composer-contextless-toggle--active' : ''
                  }${useSharedComposerIconActions ? ' app-chat-panel__composer-toggle--icon-only' : ''}`}
                  icon={<DisconnectOutlined />}
                  title={
                    isSendWithoutContextEnabled
                      ? '다음 1회만 문맥 없이 보냄'
                      : '다음 전송을 문맥 없이 보내기'
                  }
                  aria-label={
                    isSendWithoutContextEnabled
                      ? '다음 1회만 문맥 없이 보냄'
                      : '다음 전송을 문맥 없이 보내기'
                  }
                  onMouseDown={(event) => {
                    event.preventDefault();
                  }}
                  onClick={onToggleSendWithoutContext}
                  disabled={isComposerDisabled || isComposerAttachmentUploading}
                />
                <Button
                  icon={<ThunderboltOutlined />}
                  type={isImmediateSendPinned ? 'primary' : 'default'}
                  className={`app-chat-panel__composer-immediate-toggle${
                    isImmediateSendPinned ? ' app-chat-panel__composer-immediate-toggle--active' : ''
                  }${useSharedComposerIconActions ? ' app-chat-panel__composer-toggle--icon-only' : ''}`}
                  title={isImmediateSendPinned ? '즉시 전송 자동 실행 켜짐' : '즉시 요청'}
                  aria-label={isImmediateSendPinned ? '즉시 전송 자동 실행 켜짐' : '즉시 요청'}
                  onPointerDown={startImmediateSendHoldTimer}
                  onMouseDown={(event) => {
                    event.preventDefault();
                  }}
                  onPointerUp={clearImmediateSendHoldTimer}
                  onPointerLeave={clearImmediateSendHoldTimer}
                  onPointerCancel={clearImmediateSendHoldTimer}
                  onClick={handleImmediateSendButtonClick}
                  disabled={isComposerDisabled || isComposerAttachmentUploading}
                />
                <Button
                  type="primary"
                  icon={<SendOutlined />}
                  aria-label="답변 전송"
                  onMouseDown={(event) => {
                    event.preventDefault();
                  }}
                  onClick={() => {
                    handleComposerSendRequest();
                  }}
                  disabled={isComposerDisabled || isComposerAttachmentUploading}
                />
              </div>
            </div>
          </div>
        ) : (
          <div className="app-chat-panel__composer-topline">
            <div className="app-chat-panel__composer-utility-buttons">
              <Button
                icon={<PlusOutlined />}
                aria-label="파일 첨부"
                onClick={() => {
                  fileInputRef.current?.click();
                }}
                disabled={isComposerDisabled}
                loading={isComposerAttachmentUploading}
              />
            </div>
            <div className="app-chat-panel__composer-type">
              <Select
                value={selectedChatTypeId ?? undefined}
                placeholder="컨텍스트를 선택하세요."
                options={chatTypeOptions.map((option) => ({
                  value: option.value,
                  disabled: option.disabled,
                  label: (
                    <div className="app-chat-panel__type-option">
                      <span>{option.label}</span>
                    </div>
                  ),
                }))}
                getPopupContainer={(triggerNode) => triggerNode.closest('.app-chat-panel') ?? document.body}
                disabled={chatTypeOptions.length === 0 || isChatTypeReadonly}
                onChange={onSelectChatType}
              />
            </div>
            {codexModelOptions.length > 1 ? (
              <div className="app-chat-panel__composer-type">
                <Select
                  value={selectedCodexModel}
                  placeholder="Codex 모델 선택"
                  options={codexModelOptions.map((option) => ({
                    value: option.value,
                    label: (
                      <div className="app-chat-panel__type-option">
                        <span>{option.label}</span>
                      </div>
                    ),
                  }))}
                  getPopupContainer={(triggerNode) => triggerNode.closest('.app-chat-panel') ?? document.body}
                  disabled={codexModelOptions.length === 0}
                  onChange={onSelectCodexModel}
                />
              </div>
            ) : null}
            <div className="app-chat-panel__composer-actions">
              <div className="app-chat-panel__composer-action-buttons">
                <Button
                  type={isSendWithoutContextEnabled ? 'primary' : 'default'}
                  className={`app-chat-panel__composer-contextless-toggle${
                    isSendWithoutContextEnabled ? ' app-chat-panel__composer-contextless-toggle--active' : ''
                  } app-chat-panel__composer-toggle--icon-only`}
                  icon={<DisconnectOutlined />}
                  title={
                    isSendWithoutContextEnabled
                      ? '다음 1회만 문맥 없이 보냄'
                      : '다음 전송을 문맥 없이 보내기'
                  }
                  aria-label={
                    isSendWithoutContextEnabled
                      ? '다음 1회만 문맥 없이 보냄'
                      : '다음 전송을 문맥 없이 보내기'
                  }
                  onMouseDown={(event) => {
                    event.preventDefault();
                  }}
                  onClick={onToggleSendWithoutContext}
                  disabled={isComposerDisabled || isComposerAttachmentUploading}
                />
                <Button
                  icon={<ThunderboltOutlined />}
                  type={isImmediateSendPinned ? 'primary' : 'default'}
                  className={`app-chat-panel__composer-immediate-toggle${
                    isImmediateSendPinned ? ' app-chat-panel__composer-immediate-toggle--active' : ''
                  } app-chat-panel__composer-toggle--icon-only`}
                  title={isImmediateSendPinned ? '즉시 전송 자동 실행 켜짐' : '즉시 요청'}
                  aria-label={isImmediateSendPinned ? '즉시 전송 자동 실행 켜짐' : '즉시 요청'}
                  onPointerDown={startImmediateSendHoldTimer}
                  onMouseDown={(event) => {
                    event.preventDefault();
                  }}
                  onPointerUp={clearImmediateSendHoldTimer}
                  onPointerLeave={clearImmediateSendHoldTimer}
                  onPointerCancel={clearImmediateSendHoldTimer}
                  onClick={handleImmediateSendButtonClick}
                  disabled={isComposerDisabled || isComposerAttachmentUploading}
                />
                <Button
                  type="primary"
                  icon={<SendOutlined />}
                  title="큐로 보내기"
                  aria-label="큐로 보내기"
                  onMouseDown={(event) => {
                    event.preventDefault();
                  }}
                  onClick={() => {
                    handleComposerSendRequest();
                  }}
                  disabled={isComposerDisabled || isComposerAttachmentUploading}
                />
              </div>
            </div>
          </div>
        )}

        {chatTypeOptions.length === 0 ? (
          <Alert
            showIcon
            type="warning"
            message="사용 가능한 컨텍스트가 없습니다."
            description="관리 페이지에서 현재 사용자 권한에 맞는 컨텍스트를 등록하거나 권한을 부여하세요."
          />
        ) : null}

        {composerAttachmentStrip}

        {replyReferenceRequest ? (
          <div className="app-chat-panel__reply-reference" aria-live="polite">
            <div className="app-chat-panel__reply-reference-copy">
              <span className="app-chat-panel__reply-reference-label">답변 참조 중</span>
              <span className="app-chat-panel__reply-reference-text">{replyReferenceSummary}</span>
            </div>
            <Button
              type="text"
              size="small"
              className="app-chat-panel__reply-reference-clear"
              onClick={() => {
                setReplyReferenceRequestId('');
              }}
            >
              해제
            </Button>
          </div>
        ) : null}

        <ChatComposerInput
          composerRef={composerRef}
          draft={draft}
          draftVersion={draftVersion}
          forceDraftSyncVersion={composerForceDraftSyncVersion}
          composerPlaceholder={composerPlaceholder}
          enableAutoSize={enableSharedComposerAutoSize}
          isComposerDisabled={isComposerDisabled}
          isComposerAttachmentUploading={isComposerAttachmentUploading}
          queuedRequests={queuedRequests}
          composerAssistActions={composerAssistActions}
          composerAssistModalTitle={composerAssistModalTitle}
          hideAssistTrigger={useSharedComposerChrome}
          hideClearButton={useSharedComposerChrome}
          onPromoteQueuedRequest={onPromoteQueuedRequest}
          onRemoveQueuedRequest={onRemoveQueuedRequest}
          onPaste={handleComposerPaste}
          onCommitDraft={syncComposerDraft}
          onLocalDraftChange={setComposerDraftValue}
          onSend={handleComposerSendRequest}
          onClear={handleComposerClear}
        />

          <input
            ref={fileInputRef}
            type="file"
            multiple
            accept="image/*,.heic,.heif,.zip,application/zip,application/x-zip-compressed"
            className="app-chat-panel__composer-file-input"
            onChange={handleComposerFileChange}
          />
      </div>
      </div>
    </div>
  );
}
