import { createHmac, randomUUID } from 'node:crypto';
import { createReadStream } from 'node:fs';
import { access, mkdir, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
import type { FastifyInstance, FastifyReply } from 'fastify';
import { z } from 'zod';
import { env } from '../config/env.js';
import { hasErrorLogViewAccessToken } from '../services/error-log-service.js';
import {
  getSharedResourceTokenDetailBySharePath,
  recordSharedResourceTokenUsage,
  resolveSharedResourceTokenEffectiveExpiresAt,
  type SharedResourceTokenSettingSnapshot,
  upsertSharedResourceToken,
  validateSharedResourceAccessPinBySharePath,
} from '../services/shared-resource-token-service.js';
import {
  ensureChatSessionResourceDirectories,
  getActiveChatService,
  getChatRuntimeController,
} from '../services/chat-service.js';
import { rollbackChatRuntimeRequest } from '../services/chat-runtime-rollback-service.js';
import {
  assignSharedResourceTokenToRequests,
  appendChatConversationActivityLine,
  appendChatConversationMessage,
  buildChatPromptTargetSignature,
  CHAT_CONTEXT_DESCRIPTION_MAX_LENGTH,
  cancelUnansweredChatConversationRequest,
  clearChatConversationData,
  createChatConversation,
  deleteUnansweredChatConversationRequest,
  deleteChatConversation,
  ensureChatConversationTables,
  getChatConversation,
  getChatConversationRequest,
  MANAGED_CHAT_SHARE_SESSION_PREFIX,
  listChatConversationActivityLogsByRequestIds,
  listChatConversationMessages,
  listChatConversationRequests,
  listChatSourceChangeSnapshots,
  listChatConversationDetailPage,
  listChatConversations,
  markChatConversationRequestManualCompletion,
  ChatConversationManualCompletionBlockedError,
  markChatConversationResponsesRead,
  persistChatConversationPromptSelection,
  upsertChatConversationRequest,
  updateChatConversationContext,
  hasPendingAttentionVerificationRequest,
} from '../services/chat-room-service.js';
import {
  archiveChatShareTokenRoomMap,
  ensureDefaultChatShareTokenRoomMap,
  getChatShareTokenRoomMap,
  resolveChatShareTokenRoomSessionIds,
  upsertChatShareTokenRoomMap,
  type ChatShareRoomLinkContext,
  type ChatShareTokenRoomMapItem,
} from '../services/chat-share-room-map-service.js';
import { chatRuntimeService } from '../services/chat-runtime-service.js';
import { resolveMainProjectRoot } from '../services/main-project-root-service.js';
import { openResourceManagerPreviewStream } from '../services/resource-manager-service.js';
import { getTokenSettingById, type TokenSettingRecord } from '../services/token-setting-config-service.js';

const CHAT_ATTACHMENT_FILE_SIZE_LIMIT = 300 * 1024 * 1024;
const CHAT_ATTACHMENT_ROUTE_BODY_LIMIT = 450 * 1024 * 1024;
const CHAT_PUBLIC_ROUTE_PREFIX = '/.codex_chat/';
const CHAT_LEGACY_PUBLIC_ROUTE_PREFIX = '/public/.codex_chat/';
const CHAT_API_RESOURCE_ROUTE_PREFIX = '/api/chat/resources';
const CHAT_SHARE_ROUTE_PREFIX = '/api/chat/shares';
const CHAT_SHARE_TOKEN_VERSION = 1;
const RESOURCE_MANAGER_ROOT_PREFIX = 'resource/';

type ChatShareKind = 'request-bundle' | 'inquiry-message' | 'prompt';

type ChatShareTokenPayload = {
  version: number;
  kind: ChatShareKind;
  sessionId: string;
  requestId: string;
  tokenSettingId: string;
  tokenSettingName: string;
  tokenSettingDefaultExpiresInMinutes: number;
  tokenSettingAllowedAppIds: string[];
  tokenSettingMaxTokensPer30Days: number;
  tokenSettingMaxTokensPer7Days: number;
  tokenSettingMaxTokensPer5Hours: number;
  tokenSettingOneTimeTokenLimit: number;
  managedResourceTokenId?: string;
  sourceMessageId?: number;
  promptIndex?: number;
  promptSignature?: string;
};

const chatPromptContextRefSchema = z
  .object({
    key: z.literal('prompt_parent_question'),
    promptTitle: z.string().trim().min(1).max(500),
    promptDescription: z.string().trim().max(1000).optional().nullable(),
    parentQuestionText: z.string().trim().max(1000).optional().nullable(),
  })
  .optional()
  .nullable();

const chatComposerAttachmentSchema = z.object({
  id: z.string().trim().min(1).max(240),
  name: z.string().trim().min(1).max(500),
  path: z.string().trim().min(1).max(4000),
  publicUrl: z.string().trim().min(1).max(4000),
  size: z.number().finite().min(0).max(CHAT_ATTACHMENT_FILE_SIZE_LIMIT),
  mimeType: z.string().trim().min(1).max(255),
});

async function findExistingActivePromptFollowupRequest(
  sessionId: string,
  parentRequestId: string,
  followupText: string,
) {
  const normalizedSessionId = sessionId.trim();
  const normalizedParentRequestId = parentRequestId.trim();
  const normalizedFollowupText = followupText.trim();

  if (!normalizedSessionId || !normalizedParentRequestId || !normalizedFollowupText) {
    return null;
  }

  const requests = await listChatConversationRequests(normalizedSessionId, 1000);
  return requests.find((request) =>
    request.requestOrigin === 'prompt'
    && (request.parentRequestId?.trim() || '') === normalizedParentRequestId
    && request.userText.trim() === normalizedFollowupText
    && (request.status === 'queued' || request.status === 'accepted' || request.status === 'started')
  ) ?? null;
}

export function resolvePromptFollowupMode(mode?: 'queue' | 'direct' | null) {
  return mode === 'direct' ? 'direct' : 'queue';
}

function encodeBase64Url(value: string) {
  return Buffer.from(value, 'utf-8').toString('base64url');
}

function decodeBase64Url(value: string) {
  return Buffer.from(value, 'base64url').toString('utf-8');
}

function signChatSharePayload(encodedPayload: string) {
  return createHmac('sha256', env.SERVER_COMMAND_ACCESS_TOKEN).update(encodedPayload).digest('base64url');
}

function createChatShareToken(payload: ChatShareTokenPayload) {
  const encodedPayload = encodeBase64Url(JSON.stringify(payload));
  const signature = signChatSharePayload(encodedPayload);
  return `${encodedPayload}.${signature}`;
}

function parseChatShareToken(token: string) {
  const normalizedToken = token.trim();

  if (!normalizedToken) {
    return null;
  }

  const delimiterIndex = normalizedToken.lastIndexOf('.');

  if (delimiterIndex <= 0 || delimiterIndex >= normalizedToken.length - 1) {
    return null;
  }

  const encodedPayload = normalizedToken.slice(0, delimiterIndex);
  const signature = normalizedToken.slice(delimiterIndex + 1);
  const expectedSignature = signChatSharePayload(encodedPayload);

  if (signature !== expectedSignature) {
    return null;
  }

  try {
    const parsed = JSON.parse(decodeBase64Url(encodedPayload)) as Partial<ChatShareTokenPayload>;

    if (
      parsed.version !== CHAT_SHARE_TOKEN_VERSION ||
      (parsed.kind !== 'request-bundle' && parsed.kind !== 'inquiry-message' && parsed.kind !== 'prompt') ||
      typeof parsed.sessionId !== 'string' ||
      typeof parsed.requestId !== 'string' ||
      typeof parsed.tokenSettingId !== 'string' ||
      typeof parsed.tokenSettingName !== 'string'
    ) {
      return null;
    }

    return {
      version: CHAT_SHARE_TOKEN_VERSION,
      kind: parsed.kind,
      sessionId: parsed.sessionId.trim(),
      requestId: parsed.requestId.trim(),
      tokenSettingId: parsed.tokenSettingId.trim(),
      tokenSettingName: parsed.tokenSettingName.trim(),
      tokenSettingDefaultExpiresInMinutes:
        typeof parsed.tokenSettingDefaultExpiresInMinutes === 'number' ? parsed.tokenSettingDefaultExpiresInMinutes : 0,
      tokenSettingAllowedAppIds: Array.isArray(parsed.tokenSettingAllowedAppIds)
        ? parsed.tokenSettingAllowedAppIds.filter((item): item is string => typeof item === 'string').map((item) => item.trim()).filter(Boolean)
        : [],
      tokenSettingMaxTokensPer30Days:
        typeof parsed.tokenSettingMaxTokensPer30Days === 'number' ? parsed.tokenSettingMaxTokensPer30Days : 0,
      tokenSettingMaxTokensPer7Days:
        typeof parsed.tokenSettingMaxTokensPer7Days === 'number' ? parsed.tokenSettingMaxTokensPer7Days : 0,
      tokenSettingMaxTokensPer5Hours:
        typeof parsed.tokenSettingMaxTokensPer5Hours === 'number' ? parsed.tokenSettingMaxTokensPer5Hours : 0,
      tokenSettingOneTimeTokenLimit:
        typeof parsed.tokenSettingOneTimeTokenLimit === 'number' ? parsed.tokenSettingOneTimeTokenLimit : 0,
      managedResourceTokenId: typeof parsed.managedResourceTokenId === 'string' ? parsed.managedResourceTokenId.trim() : undefined,
      sourceMessageId: typeof parsed.sourceMessageId === 'number' ? parsed.sourceMessageId : undefined,
      promptIndex: typeof parsed.promptIndex === 'number' ? parsed.promptIndex : undefined,
      promptSignature: typeof parsed.promptSignature === 'string' ? parsed.promptSignature.trim() : undefined,
    } satisfies ChatShareTokenPayload;
  } catch {
    return null;
  }
}

function resolveChatSharePath(token: string) {
  return `/chat/share/${encodeURIComponent(token)}`;
}

function resolveChatShareTokenSettingSnapshot(tokenPayload: ChatShareTokenPayload): SharedResourceTokenSettingSnapshot {
  return {
    id: tokenPayload.tokenSettingId,
    name: tokenPayload.tokenSettingName,
    defaultExpiresInMinutes: tokenPayload.tokenSettingDefaultExpiresInMinutes,
    maxTokensPer30Days: tokenPayload.tokenSettingMaxTokensPer30Days,
    maxTokensPer7Days: tokenPayload.tokenSettingMaxTokensPer7Days,
    maxTokensPer5Hours: tokenPayload.tokenSettingMaxTokensPer5Hours,
    oneTimeTokenLimit: tokenPayload.tokenSettingOneTimeTokenLimit,
    allowedAppIds: tokenPayload.tokenSettingAllowedAppIds,
  };
}

type ChatShareResolvedRoom = {
  sessionId: string;
  requestId: string;
  isDefault: boolean;
  sortOrder: number;
  title: string;
  requestBadgeLabel: string | null;
  chatTypeId: string | null;
  lastChatTypeId: string | null;
  contextLabel: string | null;
  contextDescription: string | null;
  notifyOffline: boolean;
  linkContext: ChatShareRoomLinkContext | null;
  createdAt: string | null;
  updatedAt: string | null;
};

function mapResolvedShareRoomItem(room: ChatShareTokenRoomMapItem): ChatShareResolvedRoom {
  return {
    sessionId: room.sessionId,
    requestId: room.rootRequestId,
    isDefault: room.isDefault,
    sortOrder: room.sortOrder,
    title: room.title,
    requestBadgeLabel: room.requestBadgeLabel,
    chatTypeId: room.chatTypeId,
    lastChatTypeId: room.lastChatTypeId,
    contextLabel: room.contextLabel,
    contextDescription: room.contextDescription,
    notifyOffline: room.notifyOffline,
    linkContext: room.linkContext,
    createdAt: room.createdAt,
    updatedAt: room.conversationUpdatedAt ?? room.updatedAt,
  };
}

async function resolveManagedShareRooms(args: {
  managedResource:
    | Awaited<ReturnType<typeof resolveManagedChatShareContext>>['managedResource']
    | null;
  tokenPayload: ChatShareTokenPayload;
}) {
  const managedResourceTokenId = args.managedResource?.token.id?.trim() || '';

  if (!managedResourceTokenId || args.tokenPayload.kind !== 'request-bundle') {
    return [
      {
        sessionId: args.tokenPayload.sessionId,
        requestId: args.tokenPayload.requestId,
        isDefault: true,
        sortOrder: 0,
        title: '공유 채팅방',
        requestBadgeLabel: null,
        chatTypeId: null,
        lastChatTypeId: null,
        contextLabel: null,
        contextDescription: null,
        notifyOffline: false,
        linkContext: null,
        createdAt: null,
        updatedAt: null,
      } satisfies ChatShareResolvedRoom,
    ];
  }

  const ensuredRooms = await ensureDefaultChatShareTokenRoomMap({
    tokenId: managedResourceTokenId,
    sessionId: args.tokenPayload.sessionId,
    rootRequestId: args.tokenPayload.requestId,
    createdByClientId: null,
  });

  if (ensuredRooms.length === 0) {
    return [
      {
        sessionId: args.tokenPayload.sessionId,
        requestId: args.tokenPayload.requestId,
        isDefault: true,
        sortOrder: 0,
        title: '공유 채팅방',
        requestBadgeLabel: null,
        chatTypeId: null,
        lastChatTypeId: null,
        contextLabel: null,
        contextDescription: null,
        notifyOffline: false,
        linkContext: null,
        createdAt: null,
        updatedAt: null,
      } satisfies ChatShareResolvedRoom,
    ];
  }

  return ensuredRooms.map((room) => mapResolvedShareRoomItem(room));
}

async function resolveActiveManagedShareRoom(args: {
  managedResource:
    | Awaited<ReturnType<typeof resolveManagedChatShareContext>>['managedResource']
    | null;
  tokenPayload: ChatShareTokenPayload;
  requestedSessionId?: string | null;
}) {
  const rooms = await resolveManagedShareRooms({
    managedResource: args.managedResource,
    tokenPayload: args.tokenPayload,
  });
  const requestedSessionId = args.requestedSessionId?.trim() || '';
  const requestedRoom = requestedSessionId ? rooms.find((room) => room.sessionId === requestedSessionId) ?? null : null;
  const defaultRoom = rooms.find((room) => room.isDefault) ?? rooms[0] ?? null;
  const activeRoom = requestedRoom ?? defaultRoom;

  return {
    rooms,
    activeRoom,
    requestedRoomMatched: !requestedSessionId || Boolean(requestedRoom),
  };
}

function createManagedChatShareTokenId() {
  return `chat_share_${randomUUID().replace(/-/g, '').slice(0, 20)}`;
}

function createManagedChatShareSessionId() {
  return `chat-share-room-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
}

function createManagedChatShareRequestId() {
  return `share-room-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
}

function createManagedChatShareMessageIds() {
  const baseId = Date.now() * 1000 + Math.floor(Math.random() * 100);
  return {
    userMessageId: baseId,
  };
}

function normalizeShareRoomLinkContext(input: {
  linkedSessionId?: string | null;
  linkedRequestId?: string | null;
  linkedTitle?: string | null;
  linkedRequestPreview?: string | null;
  linkedChatTypeLabel?: string | null;
}): ChatShareRoomLinkContext | null {
  const sourceSessionId = input.linkedSessionId?.trim() || '';
  const sourceRequestId = input.linkedRequestId?.trim() || '';

  if (!sourceSessionId || !sourceRequestId) {
    return null;
  }

  return {
    kind: 'linked-session',
    sourceSessionId,
    sourceRequestId,
    sourceTitle: input.linkedTitle?.trim() || null,
    sourceRequestPreview: input.linkedRequestPreview?.trim() || null,
    sourceChatTypeLabel: input.linkedChatTypeLabel?.trim() || null,
    linkedAt: new Date().toISOString(),
  };
}

function sortShareMessages(messages: ListedChatConversationMessage[]) {
  return [...messages].sort((left, right) => {
    if (left.id !== right.id) {
      return left.id - right.id;
    }

    return left.timestamp.localeCompare(right.timestamp);
  });
}

function sortShareRequests(requests: ListedChatConversationRequest[]) {
  return [...requests].sort((left, right) => {
    const createdAtDiff = left.createdAt.localeCompare(right.createdAt);

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

    return left.requestId.localeCompare(right.requestId);
  });
}

async function materializeManagedShareConversation(args: {
  shareSnapshot: NonNullable<Awaited<ReturnType<typeof buildChatShareSnapshot>>>;
  managedResourceTokenId: string;
  ownerClientId: string | null;
  shareTitle: string;
}) {
  const { shareSnapshot, managedResourceTokenId, ownerClientId, shareTitle } = args;
  const sessionId = createManagedChatShareSessionId();
  const requestIdSet = new Set(shareSnapshot.requests.map((item) => item.requestId.trim()).filter(Boolean));
  const sortedRequests = sortShareRequests(shareSnapshot.requests);
  const sortedMessages = sortShareMessages(shareSnapshot.messages);
  const sourceConversation = shareSnapshot.conversation;

  await createChatConversation({
    sessionId,
    clientId: ownerClientId,
    title: shareTitle,
    draftText: '',
    requestBadgeLabel: sourceConversation?.requestBadgeLabel ?? null,
    codexModel: sourceConversation?.codexModel ?? null,
    chatTypeId: sourceConversation?.chatTypeId ?? sourceConversation?.lastChatTypeId ?? null,
    lastChatTypeId: sourceConversation?.lastChatTypeId ?? sourceConversation?.chatTypeId ?? null,
    generalSectionName: sourceConversation?.generalSectionName ?? null,
    contextLabel: sourceConversation?.contextLabel ?? null,
    contextDescription: sourceConversation?.contextDescription ?? null,
    notifyOffline: true,
  });

  for (const message of sortedMessages) {
    await appendChatConversationMessage(
      {
        sessionId,
        clientId: ownerClientId,
        title: shareTitle,
        requestBadgeLabel: sourceConversation?.requestBadgeLabel ?? null,
        codexModel: sourceConversation?.codexModel ?? null,
        chatTypeId: sourceConversation?.chatTypeId ?? sourceConversation?.lastChatTypeId ?? null,
        lastChatTypeId: sourceConversation?.lastChatTypeId ?? sourceConversation?.chatTypeId ?? null,
        generalSectionName: sourceConversation?.generalSectionName ?? null,
        contextLabel: sourceConversation?.contextLabel ?? null,
        contextDescription: sourceConversation?.contextDescription ?? null,
        notifyOffline: true,
      },
      {
        sessionId,
        messageId: message.id,
        author: message.author,
        text: message.text,
        timestamp: message.timestamp,
        clientRequestId: message.clientRequestId?.trim() || null,
        parts: message.parts ?? [],
      },
    );
  }

  for (const request of sortedRequests) {
    const normalizedParentRequestId = request.parentRequestId?.trim() || '';

    await upsertChatConversationRequest(sessionId, {
      requestId: request.requestId,
      requesterClientId: request.requesterClientId ?? ownerClientId,
      chatTypeId: request.chatTypeId ?? sourceConversation?.chatTypeId ?? sourceConversation?.lastChatTypeId ?? null,
      chatTypeLabel: request.chatTypeLabel ?? sourceConversation?.contextLabel ?? null,
      requestOrigin: request.requestOrigin,
      sharedResourceTokenId: managedResourceTokenId,
      parentRequestId: normalizedParentRequestId && requestIdSet.has(normalizedParentRequestId) ? normalizedParentRequestId : null,
      status: request.status,
      statusMessage: request.statusMessage,
      userMessageId: request.userMessageId,
      userText: request.userText,
      responseMessageId: request.responseMessageId,
      responseText: request.responseText,
      usageSnapshot: request.usageSnapshot,
      totalTokens: request.totalTokens,
    });
  }

  for (const activityLog of shareSnapshot.activityLogs) {
    let lineNo = 1;
    for (const line of activityLog.lines) {
      await appendChatConversationActivityLine(sessionId, activityLog.requestId, line, lineNo);
      lineNo += 1;
    }
  }

  await assignSharedResourceTokenToRequests(
    sessionId,
    sortedRequests.map((item) => item.requestId),
    managedResourceTokenId,
  );

  return {
    sessionId,
    requestId:
      sortedRequests.find((item) => item.requestId.trim() === shareSnapshot.targetRequest.requestId.trim())?.requestId
      ?? shareSnapshot.targetRequest.requestId,
  };
}

export function resolveStaticContentType(filePath: string) {
  const extension = path.extname(filePath).toLowerCase();

  switch (extension) {
    case '.ts':
    case '.tsx':
    case '.js':
    case '.jsx':
    case '.mjs':
    case '.cjs':
    case '.json':
    case '.css':
    case '.txt':
    case '.diff':
      return 'text/plain; charset=utf-8';
    case '.html':
    case '.htm':
      return 'text/html; charset=utf-8';
    case '.md':
    case '.markdown':
      return 'text/markdown; charset=utf-8';
    case '.svg':
      return 'image/svg+xml';
    case '.png':
      return 'image/png';
    case '.jpg':
    case '.jpeg':
      return 'image/jpeg';
    case '.gif':
      return 'image/gif';
    case '.webp':
      return 'image/webp';
    case '.ico':
      return 'image/x-icon';
    case '.pdf':
      return 'application/pdf';
    default:
      return 'application/octet-stream';
  }
}

function buildChatResourcePublicUrl(relativePath: string) {
  const normalizedRelativePath = relativePath.replace(/^public\//, '').replace(/^\/+/, '');
  return `${CHAT_API_RESOURCE_ROUTE_PREFIX}/${normalizedRelativePath
    .split('/')
    .filter(Boolean)
    .map((segment) => encodeURIComponent(segment))
    .join('/')}`;
}

function normalizeChatResourceWildcard(wildcard: string) {
  const cleaned = wildcard.trim().replace(/^\/+/, '').replace(/^public\//, '');

  if (!cleaned) {
    return '';
  }

  if (cleaned.startsWith('.codex_chat/')) {
    return cleaned;
  }

  if (cleaned === RESOURCE_MANAGER_ROOT_PREFIX.slice(0, -1) || cleaned.startsWith(RESOURCE_MANAGER_ROOT_PREFIX)) {
    return cleaned;
  }

  return path.posix.join('.codex_chat', cleaned);
}

async function serveChatPublicResource(
  repoPath: string,
  wildcard: string,
  reply: FastifyReply,
) {
  const requestedRelativePath = normalizeChatResourceWildcard(wildcard);

  if (!requestedRelativePath) {
    return reply.code(404).send({
      message: '채팅 리소스를 찾을 수 없습니다.',
    });
  }

  const publicRoot = path.join(repoPath, 'public');
  const absolutePath = path.resolve(publicRoot, requestedRelativePath);

  if (!absolutePath.startsWith(`${publicRoot}${path.sep}`)) {
    return reply.code(403).send({
      message: '허용되지 않은 경로입니다.',
    });
  }

  try {
    await access(absolutePath);
    const fileStat = await stat(absolutePath);

    if (!fileStat.isFile()) {
      return reply.code(404).send({
        message: '채팅 리소스를 찾을 수 없습니다.',
      });
    }
  } catch {
    return reply.code(404).send({
      message: '채팅 리소스를 찾을 수 없습니다.',
    });
  }

  reply.header('Cache-Control', 'no-store');
  reply.type(resolveStaticContentType(absolutePath));
  return reply.send(createReadStream(absolutePath));
}

async function serveChatSharedResource(
  repoPath: string,
  wildcard: string,
  reply: FastifyReply,
) {
  const requestedRelativePath = normalizeChatResourceWildcard(wildcard);
  const resourceManagerPrefix = `${RESOURCE_MANAGER_ROOT_PREFIX}`;

  if (requestedRelativePath.startsWith(resourceManagerPrefix)) {
    const resourceManagerRelativePath = requestedRelativePath.slice(resourceManagerPrefix.length).replace(/^\/+/, '');

    if (!resourceManagerRelativePath) {
      return reply.code(404).send({
        message: '공유 리소스를 찾을 수 없습니다.',
      });
    }

    try {
      const preview = await openResourceManagerPreviewStream(repoPath, resourceManagerRelativePath);
      reply.header('Cache-Control', 'no-store');
      reply.header('Content-Length', String(preview.size));
      reply.type(preview.contentType);
      return reply.send(preview.createStream());
    } catch (error) {
      const statusCode =
        typeof error === 'object' && error && 'statusCode' in error && typeof error.statusCode === 'number'
          ? error.statusCode
          : 500;
      const message =
        error instanceof Error && error.message.trim()
          ? error.message
          : '공유 리소스를 불러오지 못했습니다.';
      return reply.code(statusCode).send({
        message,
      });
    }
  }

  return serveChatPublicResource(repoPath, requestedRelativePath, reply);
}

function sanitizeChatAttachmentFileName(fileName: string) {
  const normalized = fileName.trim().replace(/[\\/:"*?<>|]+/g, '-').replace(/\s+/g, ' ');
  const compact = normalized || 'attachment';
  return compact.length > 120 ? compact.slice(-120) : compact;
}

function resolveAttachmentFallbackExtension(mimeType: string | undefined) {
  const normalizedMimeType = String(mimeType ?? '').trim().toLowerCase();

  switch (normalizedMimeType) {
    case 'application/pdf':
      return 'pdf';
    case 'application/zip':
      return 'zip';
    case 'image/bmp':
      return 'bmp';
    case 'image/gif':
      return 'gif';
    case 'image/heic':
      return 'heic';
    case 'image/heif':
      return 'heif';
    case 'image/jpeg':
      return 'jpg';
    case 'image/png':
      return 'png';
    case 'image/tiff':
      return 'tiff';
    case 'image/webp':
      return 'webp';
    default:
      return 'bin';
  }
}

function resolveChatAttachmentDisplayFileName(fileName: string | undefined, mimeType: string | undefined) {
  const normalizedFileName = String(fileName ?? '').trim();

  if (normalizedFileName) {
    return normalizedFileName;
  }

  const extension = resolveAttachmentFallbackExtension(mimeType);
  const baseName = String(mimeType ?? '').trim().toLowerCase().startsWith('image/') ? 'pasted-image' : 'attachment';
  return `${baseName}-${Date.now().toString(36)}.${extension}`;
}

function resolveChatAttachmentRepoPath() {
  return resolveMainProjectRoot();
}

async function saveChatAttachmentFile(args: {
  sessionId: string;
  fileName?: string;
  mimeType?: string;
  contentBase64: string;
}) {
  const buffer = Buffer.from(args.contentBase64, 'base64');
  return saveChatAttachmentBuffer({
    sessionId: args.sessionId,
    fileName: args.fileName,
    mimeType: args.mimeType,
    buffer,
  });
}

async function saveChatAttachmentBuffer(args: {
  sessionId: string;
  fileName?: string;
  mimeType?: string;
  buffer: Buffer;
}) {
  const buffer = args.buffer;

  if (buffer.byteLength === 0) {
    return {
      ok: false as const,
      statusCode: 400,
      message: '업로드할 파일 내용을 찾지 못했습니다.',
    };
  }

  if (buffer.byteLength > CHAT_ATTACHMENT_FILE_SIZE_LIMIT) {
    return {
      ok: false as const,
      statusCode: 413,
      message: '첨부 파일은 300MB 이하만 업로드할 수 있습니다.',
    };
  }

  const displayFileName = resolveChatAttachmentDisplayFileName(args.fileName, args.mimeType);
  const safeFileName = sanitizeChatAttachmentFileName(displayFileName);
  const fileToken = `${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
  const relativePath = path.posix.join(
    'public',
    '.codex_chat',
    args.sessionId,
    'resource',
    'uploads',
    `${fileToken}-${safeFileName}`,
  );
  const absolutePath = path.join(resolveChatAttachmentRepoPath(), ...relativePath.split('/'));

  await ensureChatSessionResourceDirectories(resolveChatAttachmentRepoPath(), args.sessionId);
  await writeFile(absolutePath, buffer);

  return {
    ok: true as const,
    item: {
      id: randomUUID(),
      name: displayFileName,
      path: relativePath,
      publicUrl: buildChatResourcePublicUrl(relativePath),
      size: buffer.byteLength,
      mimeType: args.mimeType?.trim() || 'application/octet-stream',
    },
  };
}

const chatAttachmentJsonBodySchema = z.object({
  sessionId: z.string().trim().min(1).max(120).regex(/^[A-Za-z0-9._:-]+$/),
  fileName: z.string().trim().max(255).optional(),
  mimeType: z.string().trim().max(200).optional(),
  contentBase64: z.string().trim().min(1).max(CHAT_ATTACHMENT_ROUTE_BODY_LIMIT * 2),
});

const chatAttachmentShareJsonBodySchema = z.object({
  sessionId: z.string().trim().min(1).max(120).optional().nullable(),
  fileName: z.string().trim().max(255).optional(),
  mimeType: z.string().trim().max(200).optional(),
  contentBase64: z.string().trim().min(1).max(CHAT_ATTACHMENT_ROUTE_BODY_LIMIT * 2),
});

function decodeChatAttachmentHeaderValue(value: unknown) {
  const normalized = String(Array.isArray(value) ? value[0] ?? '' : value ?? '').trim();

  if (!normalized) {
    return '';
  }

  try {
    return decodeURIComponent(normalized);
  } catch {
    return normalized;
  }
}

function parseChatAttachmentBinaryHeaders(headers: Record<string, unknown>, options?: { allowOptionalSessionId?: boolean }) {
  const sessionId = decodeChatAttachmentHeaderValue(headers['x-chat-attachment-session-id']);
  const fileName = decodeChatAttachmentHeaderValue(headers['x-chat-attachment-file-name']);
  const mimeType = decodeChatAttachmentHeaderValue(headers['x-chat-attachment-mime-type']);
  const schema = options?.allowOptionalSessionId
    ? z.object({
        sessionId: z.string().trim().max(120).regex(/^[A-Za-z0-9._:-]+$/).optional().nullable(),
        fileName: z.string().trim().max(255).optional(),
        mimeType: z.string().trim().max(200).optional(),
      })
    : z.object({
        sessionId: z.string().trim().min(1).max(120).regex(/^[A-Za-z0-9._:-]+$/),
        fileName: z.string().trim().max(255).optional(),
        mimeType: z.string().trim().max(200).optional(),
      });

  return schema.parse({
    sessionId: sessionId || (options?.allowOptionalSessionId ? null : ''),
    fileName: fileName || undefined,
    mimeType: mimeType || undefined,
  });
}

function isOctetStreamRequest(contentTypeHeader: unknown) {
  const contentType = String(Array.isArray(contentTypeHeader) ? contentTypeHeader[0] ?? '' : contentTypeHeader ?? '').toLowerCase();
  return contentType.startsWith('application/octet-stream');
}

function getClientIdHeader(request: { headers: Record<string, unknown> }) {
  const raw = request.headers['x-client-id'];
  return Array.isArray(raw) ? String(raw[0] ?? '').trim() : String(raw ?? '').trim();
}

function canViewAllConversations(request: { headers: Record<string, unknown> }) {
  return hasErrorLogViewAccessToken(request.headers['x-access-token'] as string | string[] | undefined);
}

function applyChatApiNoStoreHeaders(reply: FastifyReply) {
  reply.header('Cache-Control', 'no-store, no-cache, max-age=0, must-revalidate');
  reply.header('Pragma', 'no-cache');
  reply.header('Expires', '0');
  reply.header('Surrogate-Control', 'no-store');
}

type ListedChatConversationRequest = Awaited<ReturnType<typeof listChatConversationRequests>>[number];
type ListedChatConversationMessage = Awaited<ReturnType<typeof listChatConversationMessages>>[number];
type ListedChatConversationActivityLog = Awaited<ReturnType<typeof listChatConversationActivityLogsByRequestIds>>[number];

function resolveShareRootRequestId(
  requestId: string,
  requestMap: Map<string, ListedChatConversationRequest>,
  kind: ChatShareKind,
) {
  const normalizedRequestId = requestId.trim();

  if (!normalizedRequestId || kind !== 'request-bundle') {
    return normalizedRequestId;
  }

  const visitedRequestIds = new Set<string>();
  let currentRequestId = normalizedRequestId;

  while (currentRequestId && !visitedRequestIds.has(currentRequestId)) {
    visitedRequestIds.add(currentRequestId);
    const currentRequest = requestMap.get(currentRequestId);
    const parentRequestId = currentRequest?.parentRequestId?.trim() || '';

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

    currentRequestId = parentRequestId;
  }

  return currentRequestId;
}

function buildChildRequestIdsByParentRequestId(requests: ListedChatConversationRequest[]) {
  const nextMap = new Map<string, string[]>();

  requests.forEach((request) => {
    const parentRequestId = request.parentRequestId?.trim() || '';
    const requestId = request.requestId.trim();

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

    const currentChildRequestIds = nextMap.get(parentRequestId) ?? [];
    currentChildRequestIds.push(requestId);
    nextMap.set(parentRequestId, currentChildRequestIds);
  });

  return nextMap;
}

function collectShareScopeRequestIds(
  rootRequestId: string,
  childRequestIdsByParentRequestId: Map<string, string[]>,
) {
  const normalizedRootRequestId = rootRequestId.trim();

  if (!normalizedRootRequestId) {
    return [];
  }

  const orderedRequestIds: string[] = [];
  const pendingRequestIds = [normalizedRootRequestId];
  const visitedRequestIds = new Set<string>();

  while (pendingRequestIds.length > 0) {
    const currentRequestId = pendingRequestIds.shift()?.trim() || '';

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

    visitedRequestIds.add(currentRequestId);
    orderedRequestIds.push(currentRequestId);
    (childRequestIdsByParentRequestId.get(currentRequestId) ?? []).forEach((childRequestId) => {
      if (!visitedRequestIds.has(childRequestId)) {
        pendingRequestIds.push(childRequestId);
      }
    });
  }

  return orderedRequestIds;
}

function resolvePromptTarget(
  messages: ListedChatConversationMessage[],
  tokenPayload: ChatShareTokenPayload,
) {
  const preferredMessage = tokenPayload.sourceMessageId
    ? messages.find((message) => message.id === tokenPayload.sourceMessageId)
    : null;
  const candidateMessages = preferredMessage
    ? [preferredMessage, ...messages.filter((message) => message.id !== preferredMessage.id)]
    : messages;

  for (const message of candidateMessages) {
    const promptParts: Array<{
      index: number;
      part: Extract<NonNullable<ListedChatConversationMessage['parts']>[number], { type: 'prompt' }>;
    }> = (message.parts ?? []).flatMap((part: NonNullable<ListedChatConversationMessage['parts']>[number], index: number) =>
      part.type === 'prompt' ? [{ index, part }] : [],
    );
    const directMatch = tokenPayload.promptIndex != null ? promptParts[tokenPayload.promptIndex] ?? null : null;

    if (
      directMatch &&
      tokenPayload.promptSignature &&
      buildChatPromptTargetSignature(directMatch.part) === tokenPayload.promptSignature
    ) {
      return {
        sourceMessageId: message.id,
        promptIndex: directMatch.index,
        prompt: directMatch.part,
      };
    }

    const matchedPrompt = promptParts.find(({ part }) =>
      tokenPayload.promptSignature ? buildChatPromptTargetSignature(part) === tokenPayload.promptSignature : false,
    );

    if (matchedPrompt) {
      return {
        sourceMessageId: message.id,
        promptIndex: matchedPrompt.index,
        prompt: matchedPrompt.part,
      };
    }
  }

  return null;
}

function isPromptPartResolved(
  part: Extract<NonNullable<ListedChatConversationMessage['parts']>[number], { type: 'prompt' }>,
) {
  return part.readOnly === true || part.resolvedBy != null;
}

function hasUnresolvedPromptPart(message: ListedChatConversationMessage) {
  const promptParts = (message.parts ?? []).filter(
    (
      part: NonNullable<ListedChatConversationMessage['parts']>[number],
    ): part is Extract<NonNullable<ListedChatConversationMessage['parts']>[number], { type: 'prompt' }> => part.type === 'prompt',
  );

  return promptParts.some(
    (part: Extract<NonNullable<ListedChatConversationMessage['parts']>[number], { type: 'prompt' }>) =>
      !isPromptPartResolved(part),
  );
}

function hasPendingPromptRequest(
  request: ListedChatConversationRequest,
  relatedMessages: ListedChatConversationMessage[],
  promptSubmittedCount = 0,
) {
  if (request.manualPromptCompletedAt) {
    return false;
  }

  const unresolvedPromptCount = relatedMessages.reduce((count, message) => {
    if (message.author !== 'codex' && message.author !== 'system') {
      return count;
    }

    const promptParts = (message.parts ?? []).filter(
      (
        part: NonNullable<ListedChatConversationMessage['parts']>[number],
      ): part is Extract<NonNullable<ListedChatConversationMessage['parts']>[number], { type: 'prompt' }> => part.type === 'prompt',
    );

    return count + promptParts.filter(
      (part: Extract<NonNullable<ListedChatConversationMessage['parts']>[number], { type: 'prompt' }>) => !isPromptPartResolved(part),
    ).length;
  }, 0);

  if (unresolvedPromptCount === 0) {
    return false;
  }

  return unresolvedPromptCount > Math.max(0, promptSubmittedCount);
}

function hasVerificationTargetRequest(
  request: ListedChatConversationRequest,
  relatedMessages: ListedChatConversationMessage[],
) {
  return hasPendingAttentionVerificationRequest(request, relatedMessages);
}

function buildChildRequestCountMap(requests: ListedChatConversationRequest[]) {
  const nextMap = new Map<string, number>();

  requests.forEach((request) => {
    const parentRequestId = request.parentRequestId?.trim() || '';

    if (!parentRequestId) {
      return;
    }

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

  return nextMap;
}

function buildPromptFollowupCountMap(requests: ListedChatConversationRequest[]) {
  const nextMap = new Map<string, number>();

  requests.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;
}

function isPromptFollowupRoomRequest(request: ListedChatConversationRequest) {
  return request.requestOrigin === 'prompt';
}

function isRequestInFlight(status: ListedChatConversationRequest['status']) {
  return status === 'accepted' || status === 'queued' || status === 'started';
}

function isPendingCompletionRoomRequest(
  request: ListedChatConversationRequest,
  relatedMessages: ListedChatConversationMessage[],
  childRequestCountByParentId?: Map<string, number>,
  promptFollowupCountByParentId?: Map<string, number>,
) {
  if (isPromptFollowupRoomRequest(request)) {
    return false;
  }

  if (isRequestInFlight(request.status)) {
    return true;
  }

  if (hasPendingPromptRequest(request, relatedMessages, promptFollowupCountByParentId?.get(request.requestId.trim()) ?? 0)) {
    return true;
  }

  if (request.manualPromptCompletedAt) {
    return false;
  }

  if ((childRequestCountByParentId?.get(request.requestId.trim()) ?? 0) > 0) {
    return false;
  }

  if (!hasVerificationTargetRequest(request, relatedMessages)) {
    return false;
  }

  return !request.manualVerificationCompletedAt;
}

function buildRoomRequestCounts(
  requests: ListedChatConversationRequest[],
  messages: ListedChatConversationMessage[],
) {
  const requestMessagesById = new Map<string, ListedChatConversationMessage[]>();
  const childRequestCountByParentId = buildChildRequestCountMap(requests);
  const promptFollowupCountByParentId = buildPromptFollowupCountMap(requests);

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

    if (!requestId) {
      return;
    }

    const current = requestMessagesById.get(requestId) ?? [];
    current.push(message);
    requestMessagesById.set(requestId, current);
  });

  const processingCount = requests.filter(
    (request) => !isPromptFollowupRoomRequest(request) && isRequestInFlight(request.status),
  ).length;
  const unansweredCount = requests.filter((request) =>
    isPendingCompletionRoomRequest(
      request,
      requestMessagesById.get(request.requestId.trim()) ?? [],
      childRequestCountByParentId,
      promptFollowupCountByParentId,
    ),
  ).length;

  return {
    processingCount,
    unansweredCount,
  };
}

function buildManagedSharePlaceholderRequest(tokenPayload: ChatShareTokenPayload, conversation: Awaited<ReturnType<typeof getChatConversation>>) {
  const normalizedSessionId = tokenPayload.sessionId.trim();
  const normalizedRequestId = tokenPayload.requestId.trim();

  if (
    tokenPayload.kind !== 'request-bundle' ||
    !normalizedSessionId.startsWith(MANAGED_CHAT_SHARE_SESSION_PREFIX) ||
    !normalizedRequestId
  ) {
    return null;
  }

  const createdAt = conversation?.createdAt?.trim() || new Date().toISOString();
  const updatedAt = conversation?.updatedAt?.trim() || createdAt;
  const chatTypeLabel = conversation?.contextLabel?.trim() || '기본처리';

  return {
    sessionId: normalizedSessionId,
    requestId: normalizedRequestId,
    requesterClientId: conversation?.clientId?.trim() || null,
    chatTypeId: conversation?.chatTypeId?.trim() || conversation?.lastChatTypeId?.trim() || null,
    chatTypeLabel,
    requestOrigin: 'composer',
    sharedResourceTokenId: tokenPayload.managedResourceTokenId?.trim() || null,
    parentRequestId: null,
    promptContextRef: null,
    status: 'completed',
    statusMessage: '공유 채팅방 시작 요청을 복원했습니다.',
    retryCount: 0,
    userMessageId: null,
    userText: '',
    responseMessageId: null,
    responseText: '',
    usageSnapshot: null,
    totalTokens: null,
    hasResponse: false,
    canDelete: false,
    manualPromptCompletedAt: null,
    manualVerificationCompletedAt: null,
    createdAt,
    updatedAt,
    answeredAt: null,
    terminalAt: createdAt,
  } satisfies ListedChatConversationRequest;
}

async function buildChatShareSnapshot(
  tokenPayload: ChatShareTokenPayload,
  options?: {
    sessionId?: string | null;
    requestId?: string | null;
    detailLevel?: 'full' | 'initial';
  },
) {
  const normalizedSessionId = options?.sessionId?.trim() || tokenPayload.sessionId.trim();
  const detailLevel = options?.detailLevel === 'initial' ? 'initial' : 'full';
  const conversation = await getChatConversation(normalizedSessionId, null);

  if (!conversation) {
    return null;
  }

  const isManagedShareRoomSession =
    tokenPayload.kind === 'request-bundle' && normalizedSessionId.startsWith(MANAGED_CHAT_SHARE_SESSION_PREFIX);
  const useInitialManagedShareRoomView = isManagedShareRoomSession && detailLevel === 'initial';
  const detailPage = useInitialManagedShareRoomView
    ? await listChatConversationDetailPage(normalizedSessionId, {
      limit: 12,
      includeActivityLogs: false,
      includePagination: false,
    })
    : null;
  const [requests, messages] = detailPage
    ? [detailPage.requests, detailPage.messages]
    : await Promise.all([
      listChatConversationRequests(normalizedSessionId, 1000),
      listChatConversationMessages(normalizedSessionId, { limit: 1000 }),
    ]);
  const requestMap = new Map(requests.map((request) => [request.requestId.trim(), request] as const));
  const targetRequestId = options?.requestId?.trim() || tokenPayload.requestId.trim();
  const targetRequestFromStore = requestMap.get(targetRequestId) ?? await getChatConversationRequest(normalizedSessionId, targetRequestId);
  const placeholderTargetRequest = targetRequestFromStore ? null : buildManagedSharePlaceholderRequest(tokenPayload, conversation);
  const targetRequest = targetRequestFromStore ?? placeholderTargetRequest;

  if (!targetRequest) {
    return null;
  }

  const childRequestIdsByParentRequestId = buildChildRequestIdsByParentRequestId(requests);
  const isManagedShareRoomPlaceholder = isManagedShareRoomSession && !targetRequestFromStore;
  const rootRequestId = isManagedShareRoomPlaceholder
    ? targetRequestId
    : resolveShareRootRequestId(targetRequestId, requestMap, tokenPayload.kind);
  const scopeRequestIds = useInitialManagedShareRoomView
    ? requests.map((request) => request.requestId.trim()).filter(Boolean)
    : isManagedShareRoomSession
    ? requests.map((request) => request.requestId.trim()).filter(Boolean)
    : collectShareScopeRequestIds(rootRequestId, childRequestIdsByParentRequestId);
  const scopeRequestIdSet = new Set(scopeRequestIds);
  const scopedRequests = requests.filter((request) => scopeRequestIdSet.has(request.requestId.trim()));
  const scopedMessages = messages.filter((message: ListedChatConversationMessage) => {
    const linkedRequestId = message.clientRequestId?.trim() || '';
    return linkedRequestId ? scopeRequestIdSet.has(linkedRequestId) : false;
  });
  const activityLogs = useInitialManagedShareRoomView
    ? []
    : await listChatConversationActivityLogsByRequestIds(normalizedSessionId, scopeRequestIds);
  const promptTarget = tokenPayload.kind === 'prompt' ? resolvePromptTarget(scopedMessages, tokenPayload) : null;
  const roomRequestCounts = useInitialManagedShareRoomView ? undefined : buildRoomRequestCounts(requests, messages);
  const oldestLoadedMessageId = detailPage?.oldestLoadedMessageId ?? null;
  const hasOlderMessages = detailPage?.hasOlderMessages ?? false;

  if (tokenPayload.kind === 'prompt' && !promptTarget) {
    return null;
  }

  return {
    conversation,
    rootRequestId,
    targetRequest,
    isRecoveredManagedShareRoom: isManagedShareRoomPlaceholder,
    requests: scopedRequests,
    messages: scopedMessages,
    activityLogs,
    roomRequestCounts,
    oldestLoadedMessageId,
    hasOlderMessages,
    promptTarget,
    detailLevel,
  } satisfies {
    conversation: Awaited<ReturnType<typeof getChatConversation>>;
    rootRequestId: string;
    targetRequest: ListedChatConversationRequest;
    isRecoveredManagedShareRoom: boolean;
    requests: ListedChatConversationRequest[];
    messages: ListedChatConversationMessage[];
    activityLogs: ListedChatConversationActivityLog[];
    roomRequestCounts: {
      processingCount: number;
      unansweredCount: number;
    } | undefined;
    oldestLoadedMessageId: number | null;
    hasOlderMessages: boolean;
    promptTarget:
      | {
          sourceMessageId: number;
          promptIndex: number;
          prompt: Extract<ListedChatConversationMessage['parts'][number], { type: 'prompt' }>;
        }
      | null;
    detailLevel: 'full' | 'initial';
  };
}

function resolveRecoveredShareParentRequestId(
  shareSnapshot: Awaited<ReturnType<typeof buildChatShareSnapshot>>,
  requestedParentRequestId: string | null | undefined,
  fallbackRequestIds: Array<string | null | undefined> = [],
) {
  if (!shareSnapshot) {
    return null;
  }

  const allowedRequestIds = new Set(shareSnapshot.requests.map((item) => item.requestId.trim()).filter(Boolean));
  const normalizedRequestedParentRequestId = requestedParentRequestId?.trim() || '';

  if (normalizedRequestedParentRequestId && allowedRequestIds.has(normalizedRequestedParentRequestId)) {
    return normalizedRequestedParentRequestId;
  }

  const normalizedFallbackRequestIds = fallbackRequestIds.map((value) => value?.trim() || '').filter(Boolean);

  for (const fallbackRequestId of normalizedFallbackRequestIds) {
    if (allowedRequestIds.has(fallbackRequestId)) {
      return fallbackRequestId;
    }
  }

  if (!shareSnapshot.isRecoveredManagedShareRoom) {
    return normalizedRequestedParentRequestId || null;
  }

  return shareSnapshot.requests[shareSnapshot.requests.length - 1]?.requestId?.trim() || null;
}

function resolveRequestUsageAt(request: ListedChatConversationRequest) {
  const candidates = [request.answeredAt, request.terminalAt, request.updatedAt, request.createdAt];

  for (const candidate of candidates) {
    const normalized = candidate?.trim();

    if (!normalized) {
      continue;
    }

    const parsed = Date.parse(normalized);

    if (Number.isFinite(parsed)) {
      return parsed;
    }
  }

  return 0;
}

function resolveRequestUsageTokens(request: ListedChatConversationRequest) {
  const total =
    request.usageSnapshot?.tokenTotals?.total ??
    request.usageSnapshot?.totalTokens ??
    request.totalTokens ??
    0;

  return Math.max(0, Math.round(Number(total) || 0));
}

type ShareTokenLimitSetting = Pick<TokenSettingRecord, 'maxTokensPer7Days' | 'maxTokensPer5Hours'>;

function resolvePeriodTokenLimit(setting: ShareTokenLimitSetting, periodKey: '7d' | '5h') {
  if (periodKey === '7d') {
    return setting.maxTokensPer7Days;
  }

  return setting.maxTokensPer5Hours;
}

function resolveTokenUsageInWindow(requests: ListedChatConversationRequest[], nowMs: number, windowMs: number) {
  const threshold = nowMs - windowMs;

  return requests.reduce((sum, request) => {
    if (resolveRequestUsageAt(request) < threshold) {
      return sum;
    }

    return sum + resolveRequestUsageTokens(request);
  }, 0);
}

function resolveShareBlockedReason(requests: ListedChatConversationRequest[], tokenSetting: ShareTokenLimitSetting) {
  const nowMs = Date.now();
  const periodWindows: Array<{ key: '7d' | '5h'; label: string; windowMs: number }> = [
    { key: '7d', label: '7일', windowMs: 7 * 24 * 60 * 60 * 1000 },
    { key: '5h', label: '5시간', windowMs: 5 * 60 * 60 * 1000 },
  ];

  for (const period of periodWindows) {
    const limit = resolvePeriodTokenLimit(tokenSetting, period.key);

    if (limit <= 0) {
      continue;
    }

    const used = resolveTokenUsageInWindow(requests, nowMs, period.windowMs);

    if (used >= limit) {
      return `${period.label} 사용 가능 토큰 한도(${limit.toLocaleString('ko-KR')})를 모두 사용했습니다.`;
    }
  }

  return null;
}

async function resolveManagedChatShareContext(token: string) {
  const sharePath = resolveChatSharePath(token);
  const managedResource = await getSharedResourceTokenDetailBySharePath(sharePath);

  return {
    sharePath,
    managedResource,
  };
}

function buildManagedShareRoomRuntimeSnapshot(
  snapshot: ReturnType<typeof chatRuntimeService.getSnapshot>,
  sessionId: string,
) {
  const normalizedSessionId = sessionId.trim();
  const running = snapshot.running.filter((item) => item.sessionId === normalizedSessionId);
  const queued = snapshot.queued.filter((item) => item.sessionId === normalizedSessionId);
  const sessions = snapshot.sessions.filter((item) => item.sessionId === normalizedSessionId);
  const recent = snapshot.recent.filter((item) => item.sessionId === normalizedSessionId);

  return {
    generatedAt: snapshot.generatedAt,
    runningCount: running.length,
    queuedCount: queued.length,
    sessionCount: sessions.length,
    running,
    queued,
    sessions,
    recent,
  };
}

function resolveChatSharePayloadFromManagedResource(
  managedResource:
    | Awaited<ReturnType<typeof getSharedResourceTokenDetailBySharePath>>
    | null
    | undefined,
) {
  const resourceContext = managedResource?.token.resourceContext;
  const sessionId = resourceContext?.sessionId?.trim() || '';
  const requestId = resourceContext?.requestId?.trim() || '';

  if (!resourceContext || !sessionId || !requestId) {
    return null;
  }

  return {
    version: CHAT_SHARE_TOKEN_VERSION,
    kind: resourceContext.kind ?? 'request-bundle',
    sessionId,
    requestId,
    tokenSettingId: managedResource?.token.tokenSettingSnapshot?.id ?? '',
    tokenSettingName: managedResource?.token.tokenSettingSnapshot?.name ?? '',
    tokenSettingDefaultExpiresInMinutes: managedResource?.token.tokenSettingSnapshot?.defaultExpiresInMinutes ?? 0,
    tokenSettingAllowedAppIds: managedResource?.token.allowedAppIds ?? managedResource?.token.tokenSettingSnapshot?.allowedAppIds ?? [],
    tokenSettingMaxTokensPer30Days: managedResource?.token.tokenSettingSnapshot?.maxTokensPer30Days ?? 0,
    tokenSettingMaxTokensPer7Days: managedResource?.token.tokenSettingSnapshot?.maxTokensPer7Days ?? 0,
    tokenSettingMaxTokensPer5Hours: managedResource?.token.tokenSettingSnapshot?.maxTokensPer5Hours ?? 0,
    tokenSettingOneTimeTokenLimit: managedResource?.token.tokenSettingSnapshot?.oneTimeTokenLimit ?? 0,
    managedResourceTokenId: managedResource?.token.id,
    sourceMessageId: resourceContext.sourceMessageId ?? undefined,
    promptIndex: resourceContext.promptIndex ?? undefined,
    promptSignature: resourceContext.promptSignature ?? undefined,
  } satisfies ChatShareTokenPayload;
}

function resolveManagedShareUnavailableMessage(
  managedResource:
    | {
        token: {
          enabled: boolean;
          revokedAt: string | null;
          expiresAt: string | null;
          createdAt: string;
          resourceType?: string | null;
          tokenSettingSnapshot?: SharedResourceTokenSettingSnapshot | null;
          linkedTokenSetting?: {
            defaultExpiresInMinutes: number;
            syncState: 'ok' | 'missing' | 'disabled' | 'chat-share-disallowed';
            syncMessage: string | null;
          } | null;
        };
      }
    | null
    | undefined,
) {
  if (!managedResource) {
    return null;
  }

  if (managedResource.token.revokedAt || managedResource.token.enabled === false) {
    return '이 공유 링크는 현재 비활성 또는 회수 상태입니다.';
  }

  const effectiveExpiresAt = resolveSharedResourceTokenEffectiveExpiresAt(
    managedResource.token,
    managedResource.token.tokenSettingSnapshot,
  );

  if (effectiveExpiresAt && Date.parse(effectiveExpiresAt) <= Date.now()) {
    return '이 공유 링크는 만료되었습니다.';
  }

  if (managedResource.token.resourceType === 'chat-share') {
    return null;
  }

  return null;
}

function getRequestChatSharePin(request: {
  headers: Record<string, string | string[] | undefined>;
  query?: unknown;
}) {
  const pinHeader = request.headers['x-chat-share-pin'];
  const headerPin = Array.isArray(pinHeader) ? pinHeader[0] : pinHeader;

  if (typeof headerPin === 'string' && headerPin.trim()) {
    return headerPin.trim();
  }

  const queryRecord = request.query && typeof request.query === 'object' ? (request.query as Record<string, unknown>) : null;
  const queryPin = queryRecord && typeof queryRecord.sharePin === 'string' ? queryRecord.sharePin : '';
  return queryPin.trim();
}

function getRequestClientId(request: {
  headers: Record<string, string | string[] | undefined>;
  query?: unknown;
}) {
  const headerClientId = getClientIdHeader(request as { headers: Record<string, unknown> });

  if (headerClientId) {
    return headerClientId;
  }

  const queryRecord = request.query && typeof request.query === 'object' ? (request.query as Record<string, unknown>) : null;
  const queryClientId = queryRecord && typeof queryRecord.clientId === 'string' ? queryRecord.clientId : '';
  return queryClientId.trim();
}

async function ensureManagedShareAccessPin(
  request: { headers: Record<string, string | string[] | undefined>; query?: unknown },
  reply: FastifyReply,
  sharePath: string,
) {
  const pinStatus = await validateSharedResourceAccessPinBySharePath(sharePath, getRequestChatSharePin(request), {
    clientId: getRequestClientId(request),
  });

  if (pinStatus.status === 'ok' || pinStatus.status === 'not-configured') {
    return pinStatus;
  }

  if (pinStatus.status === 'required') {
    reply.code(401).send({
      code: 'share_pin_required',
      message: '이 공유 채팅방은 4자리 비밀번호 입력이 필요합니다.',
    });
    return null;
  }

  if (pinStatus.status === 'invalid') {
    reply.code(401).send({
      code: 'share_pin_invalid',
      message: '공유 채팅방 비밀번호가 올바르지 않습니다.',
    });
    return null;
  }

  if (pinStatus.status === 'missing') {
    reply.code(404).send({
      message: '공유 링크를 찾을 수 없습니다.',
    });
    return null;
  }

  return null;
}

function hasManagedSharePermission(
  managedResource:
    | Awaited<ReturnType<typeof resolveManagedChatShareContext>>['managedResource']
    | null
    | undefined,
  permission: 'manage',
) {
  return managedResource?.token.permissions?.includes(permission) === true;
}

function hasManagedShareAllowedApp(
  managedResource:
    | Awaited<ReturnType<typeof resolveManagedChatShareContext>>['managedResource']
    | null
    | undefined,
  appId: string,
) {
  const normalizedAppId = appId.trim().toLowerCase();
  const allowedAppIds = managedResource?.token.allowedAppIds;

  return (allowedAppIds ?? []).some((item) => item.trim().toLowerCase() === normalizedAppId);
}

export async function registerChatRoutes(app: FastifyInstance) {
  app.addContentTypeParser('application/octet-stream', { parseAs: 'buffer' }, (request, body, done) => {
    done(null, body);
  });

  app.addHook('onSend', async (request, reply, payload) => {
    if (request.method.toUpperCase() === 'GET' && request.url.startsWith('/api/chat')) {
      applyChatApiNoStoreHeaders(reply);
    }

    return payload;
  });

  app.get(`${CHAT_PUBLIC_ROUTE_PREFIX}*`, async (request, reply) => {
    const wildcard = String((request.params as { '*': string | undefined })['*'] ?? '').trim();
    return serveChatPublicResource(resolveChatAttachmentRepoPath(), wildcard, reply);
  });

  app.get(`${CHAT_LEGACY_PUBLIC_ROUTE_PREFIX}*`, async (request, reply) => {
    const wildcard = String((request.params as { '*': string | undefined })['*'] ?? '').trim();
    return serveChatPublicResource(resolveChatAttachmentRepoPath(), wildcard, reply);
  });

  app.get(`${CHAT_API_RESOURCE_ROUTE_PREFIX}/*`, async (request, reply) => {
    const wildcard = String((request.params as { '*': string | undefined })['*'] ?? '').trim();
    return serveChatPublicResource(resolveChatAttachmentRepoPath(), wildcard, reply);
  });


  app.get(`${CHAT_SHARE_ROUTE_PREFIX}/:token/resources/*`, async (request, reply) => {
    const params = z.object({
      token: z.string().trim().min(1).max(16000),
    }).parse(request.params ?? {});
    const wildcard = String((request.params as { '*': string | undefined })['*'] ?? '').trim();
    const managedContext = await resolveManagedChatShareContext(params.token);
    const tokenPayload = resolveChatSharePayloadFromManagedResource(managedContext.managedResource) ?? parseChatShareToken(params.token);

    if (!tokenPayload) {
      return reply.code(404).send({
        message: '공유 링크가 유효하지 않습니다.',
      });
    }
    const unavailableMessage = resolveManagedShareUnavailableMessage(managedContext.managedResource);

    if (unavailableMessage) {
      return reply.code(403).send({
        message: unavailableMessage,
      });
    }

    const accessPinStatus = await ensureManagedShareAccessPin(request, reply, managedContext.sharePath);

    if (!accessPinStatus) {
      return;
    }

    const requestedRelativePath = normalizeChatResourceWildcard(wildcard);
    const mappedSessionIds = managedContext.managedResource?.token.id
      ? await resolveChatShareTokenRoomSessionIds(managedContext.managedResource.token.id)
      : [];
    const allowedSessionIds = Array.from(new Set([tokenPayload.sessionId, ...mappedSessionIds].filter(Boolean)));
    const isAllowedSessionResource = allowedSessionIds.some((sessionId) =>
      requestedRelativePath.startsWith(path.posix.join('.codex_chat', sessionId, 'resource') + '/'),
    );
    const isAllowedManagedResource = requestedRelativePath.startsWith(RESOURCE_MANAGER_ROOT_PREFIX);

    if (!requestedRelativePath || (!isAllowedSessionResource && !isAllowedManagedResource)) {
      return reply.code(403).send({
        message: '이 공유 링크에서 허용되지 않은 리소스입니다.',
      });
    }

    return serveChatSharedResource(resolveChatAttachmentRepoPath(), requestedRelativePath, reply);
  });

  app.get('/api/chat/setup', async () => {
    await ensureChatConversationTables();

    return {
      ok: true,
      tables: ['chat_conversations', 'chat_conversation_messages', 'chat_conversation_requests'],
    };
  });

  app.post(`${CHAT_SHARE_ROUTE_PREFIX}/rooms`, async (request, reply) => {
    const parsedPayload = z.object({
      tokenSettingId: z.string().trim().min(1).max(120),
      chatTypeId: z.string().trim().min(1).max(120),
      chatTypeLabel: z.string().trim().min(1).max(200),
      name: z.string().trim().max(160).optional(),
      tokenName: z.string().trim().max(160).optional(),
      title: z.string().trim().max(200).optional(),
      requestBadgeLabel: z.string().trim().max(120).optional().nullable(),
      seedMessage: z.string().trim().min(1).max(20000),
      allowManageAccess: z.boolean().optional(),
    }).parse(request.body ?? {});
    const payload = {
      ...parsedPayload,
      name: parsedPayload.name || parsedPayload.title || parsedPayload.tokenName || '',
    };

    if (!payload.name) {
      return reply.code(400).send({
        message: '공유 이름을 입력해 주세요.',
      });
    }

    const tokenSetting = await getTokenSettingById(payload.tokenSettingId);

    if (!tokenSetting || !tokenSetting.enabled) {
      return reply.code(404).send({
        message: '공유에 사용할 토큰 설정을 찾을 수 없습니다.',
      });
    }

    if (!tokenSetting.allowedAppIds.some((item) => item.trim().toLowerCase() === 'chat-live')) {
      return reply.code(409).send({
        message: '선택한 토큰 설정에는 공유 채팅방 권한이 없습니다.',
      });
    }

    const clientId = getClientIdHeader(request);
    const sessionId = createManagedChatShareSessionId();
    const requestId = createManagedChatShareRequestId();
    const { userMessageId } = createManagedChatShareMessageIds();
    const createdAt = new Date().toISOString();

    await createChatConversation({
      sessionId,
      clientId: clientId || null,
      title: payload.name,
      requestBadgeLabel: payload.requestBadgeLabel ?? null,
      chatTypeId: payload.chatTypeId,
      lastChatTypeId: payload.chatTypeId,
      contextLabel: payload.chatTypeLabel,
      contextDescription: null,
      notifyOffline: true,
    });

    await appendChatConversationMessage(
      {
        sessionId,
        clientId: clientId || null,
        title: payload.name,
        requestBadgeLabel: payload.requestBadgeLabel ?? null,
        chatTypeId: payload.chatTypeId,
        lastChatTypeId: payload.chatTypeId,
        contextLabel: payload.chatTypeLabel,
        contextDescription: null,
        notifyOffline: true,
      },
      {
        sessionId,
        messageId: userMessageId,
        author: 'user',
        text: payload.seedMessage,
        timestamp: createdAt,
        clientRequestId: requestId,
        parts: [],
      },
    );

    await upsertChatConversationRequest(sessionId, {
      requestId,
      requesterClientId: clientId || null,
      chatTypeId: payload.chatTypeId,
      chatTypeLabel: payload.chatTypeLabel,
      requestOrigin: 'composer',
      status: 'completed',
      statusMessage: '공유 채팅방 시작 요청을 준비했습니다.',
      userMessageId,
      userText: payload.seedMessage,
    });

    const managedResourceTokenId = createManagedChatShareTokenId();
    const token = randomUUID().replace(/-/g, '').slice(0, 24);
    const sharePath = resolveChatSharePath(token);
    const permissions = payload.allowManageAccess === true
      ? (['view', 'comment', 'download', 'upload', 'manage'] as const)
      : (['view', 'comment', 'download', 'upload'] as const);

    await upsertSharedResourceToken({
      id: managedResourceTokenId,
      name: payload.name,
      description: [tokenSetting.description, `공유 유형: room`, `채팅유형: ${payload.chatTypeLabel}`].filter(Boolean).join('\n'),
      tokenSettingId: tokenSetting.id,
      tokenSettingSnapshot: {
        id: tokenSetting.id,
        name: tokenSetting.name,
        defaultExpiresInMinutes: tokenSetting.defaultExpiresInMinutes,
        maxTokensPer30Days: tokenSetting.maxTokensPer30Days,
        maxTokensPer7Days: tokenSetting.maxTokensPer7Days,
        maxTokensPer5Hours: tokenSetting.maxTokensPer5Hours,
        oneTimeTokenLimit: tokenSetting.oneTimeTokenLimit,
        allowedAppIds: tokenSetting.allowedAppIds,
      },
      resourceContext: {
        kind: 'request-bundle',
        sessionId,
        requestId,
      },
      resourceAllowedAppIds: tokenSetting.allowedAppIds,
      resourceLabel: payload.name,
      resourcePath: sharePath,
      resourceType: 'chat-share',
      sharePath,
      permissions: [...permissions],
      enabled: true,
      expiresAt:
        tokenSetting.defaultExpiresInMinutes > 0
          ? new Date(Date.now() + tokenSetting.defaultExpiresInMinutes * 60 * 1000).toISOString()
          : null,
      usageLimit: 0,
    });

    await assignSharedResourceTokenToRequests(sessionId, [requestId], managedResourceTokenId);
    await upsertChatShareTokenRoomMap({
      tokenId: managedResourceTokenId,
      sessionId,
      rootRequestId: requestId,
      isDefault: true,
      createdByClientId: clientId || null,
    });

    return {
      ok: true,
      sessionId,
      requestId,
      token,
      sharePath,
      managedResourceTokenId,
      name: payload.name,
      requestBadgeLabel: payload.requestBadgeLabel ?? null,
      seedMessage: payload.seedMessage,
      permissions,
      hasAccessPin: false,
      tokenSetting: {
        id: tokenSetting.id,
        name: tokenSetting.name,
        defaultExpiresInMinutes: tokenSetting.defaultExpiresInMinutes,
        maxTokensPer30Days: tokenSetting.maxTokensPer30Days,
        maxTokensPer7Days: tokenSetting.maxTokensPer7Days,
        maxTokensPer5Hours: tokenSetting.maxTokensPer5Hours,
        oneTimeTokenLimit: tokenSetting.oneTimeTokenLimit,
        allowedAppIds: tokenSetting.allowedAppIds,
      },
    };
  });

  app.post(`${CHAT_SHARE_ROUTE_PREFIX}/:token/rooms`, async (request, reply) => {
    const params = z.object({
      token: z.string().trim().min(1).max(16000),
    }).parse(request.params ?? {});
    const payload = z.object({
      chatTypeId: z.string().trim().min(1).max(120),
      chatTypeLabel: z.string().trim().min(1).max(200),
      title: z.string().trim().min(1).max(200),
      requestBadgeLabel: z.string().trim().max(120).optional().nullable(),
      seedMessage: z.string().trim().min(1).max(20000),
      linkedSessionId: z.string().trim().min(1).max(120).optional().nullable(),
      linkedRequestId: z.string().trim().min(1).max(120).optional().nullable(),
      linkedTitle: z.string().trim().max(200).optional().nullable(),
      linkedRequestPreview: z.string().trim().max(1000).optional().nullable(),
      linkedChatTypeLabel: z.string().trim().max(200).optional().nullable(),
    }).parse(request.body ?? {});
    const managedContext = await resolveManagedChatShareContext(params.token);
    const tokenPayload = resolveChatSharePayloadFromManagedResource(managedContext.managedResource) ?? parseChatShareToken(params.token);

    if (!tokenPayload) {
      return reply.code(404).send({
        message: '공유 링크가 유효하지 않습니다.',
      });
    }

    const unavailableMessage = resolveManagedShareUnavailableMessage(managedContext.managedResource);

    if (unavailableMessage) {
      return reply.code(403).send({
        message: unavailableMessage,
      });
    }

    const accessPinStatus = await ensureManagedShareAccessPin(request, reply, managedContext.sharePath);

    if (!accessPinStatus) {
      return;
    }

    if (!managedContext.managedResource || managedContext.managedResource.token.resourceType !== 'chat-share') {
      return reply.code(404).send({
        message: '공유 채팅방 정보를 찾을 수 없습니다.',
      });
    }

    if (!managedContext.managedResource.token.permissions.includes('manage')) {
      return reply.code(403).send({
        message: '이 공유 링크에는 새 채팅방을 추가할 권한이 없습니다.',
      });
    }

    const currentToken = managedContext.managedResource.token;
    const clientId = getRequestClientId(request);
    const sessionId = createManagedChatShareSessionId();
    const requestId = createManagedChatShareRequestId();
    const { userMessageId } = createManagedChatShareMessageIds();
    const createdAt = new Date().toISOString();

    await createChatConversation({
      sessionId,
      clientId: clientId || null,
      title: payload.title,
      requestBadgeLabel: payload.requestBadgeLabel ?? null,
      chatTypeId: payload.chatTypeId,
      lastChatTypeId: payload.chatTypeId,
      contextLabel: payload.chatTypeLabel,
      contextDescription: null,
      notifyOffline: true,
    });

    await appendChatConversationMessage(
      {
        sessionId,
        clientId: clientId || null,
        title: payload.title,
        requestBadgeLabel: payload.requestBadgeLabel ?? null,
        chatTypeId: payload.chatTypeId,
        lastChatTypeId: payload.chatTypeId,
        contextLabel: payload.chatTypeLabel,
        contextDescription: null,
        notifyOffline: true,
      },
      {
        sessionId,
        messageId: userMessageId,
        author: 'user',
        text: payload.seedMessage,
        timestamp: createdAt,
        clientRequestId: requestId,
        parts: [],
      },
    );

    await upsertChatConversationRequest(sessionId, {
      requestId,
      requesterClientId: clientId || null,
      chatTypeId: payload.chatTypeId,
      chatTypeLabel: payload.chatTypeLabel,
      requestOrigin: 'composer',
      sharedResourceTokenId: currentToken.id,
      status: 'completed',
      statusMessage: '공유 채팅방 시작 요청을 준비했습니다.',
      userMessageId,
      userText: payload.seedMessage,
    });

    await assignSharedResourceTokenToRequests(sessionId, [requestId], currentToken.id);
    await upsertChatShareTokenRoomMap({
      tokenId: currentToken.id,
      sessionId,
      rootRequestId: requestId,
      isDefault: false,
      createdByClientId: clientId || null,
      linkContext: normalizeShareRoomLinkContext(payload),
    });

    const room = await getChatShareTokenRoomMap(currentToken.id, sessionId);

    return {
      ok: true,
      room: room
        ? mapResolvedShareRoomItem(room)
        : {
            sessionId,
            requestId,
            isDefault: false,
            sortOrder: 0,
            title: payload.title,
            requestBadgeLabel: payload.requestBadgeLabel ?? null,
            chatTypeId: payload.chatTypeId,
            lastChatTypeId: payload.chatTypeId,
            contextLabel: payload.chatTypeLabel,
            contextDescription: null,
            notifyOffline: true,
            linkContext: normalizeShareRoomLinkContext(payload),
            createdAt,
            updatedAt: createdAt,
          },
    };
  });

  app.post(`${CHAT_SHARE_ROUTE_PREFIX}/:token/room-settings`, async (request, reply) => {
    const params = z.object({
      token: z.string().trim().min(1).max(16000),
    }).parse(request.params ?? {});
    const payload = z.object({
      sessionId: z.string().trim().min(1).max(120).optional().nullable(),
      accessPin: z.string().regex(/^\d{4}$/u).optional().nullable(),
      accessPinPromptTtlMinutes: z.number().int().min(0).max(7 * 24 * 60).optional().nullable(),
      chatTypeId: z.string().trim().min(1).max(120).optional().nullable(),
      chatTypeLabel: z.string().trim().min(1).max(200).optional().nullable(),
      title: z.string().trim().min(1).max(200).optional().nullable(),
      notifyOffline: z.boolean().optional().nullable(),
    }).parse(request.body ?? {});
    const managedContext = await resolveManagedChatShareContext(params.token);
    const tokenPayload = resolveChatSharePayloadFromManagedResource(managedContext.managedResource) ?? parseChatShareToken(params.token);

    if (!tokenPayload) {
      return reply.code(404).send({
        message: '공유 링크가 유효하지 않습니다.',
      });
    }
    const unavailableMessage = resolveManagedShareUnavailableMessage(managedContext.managedResource);

    if (unavailableMessage) {
      return reply.code(403).send({
        message: unavailableMessage,
      });
    }

    const accessPinStatus = await ensureManagedShareAccessPin(request, reply, managedContext.sharePath);

    if (!accessPinStatus) {
      return;
    }

    if (!managedContext.managedResource || managedContext.managedResource.token.resourceType !== 'chat-share') {
      return reply.code(404).send({
        message: '공유 채팅방 설정 대상을 찾을 수 없습니다.',
      });
    }

    const resolvedRoomContext = await resolveActiveManagedShareRoom({
      managedResource: managedContext.managedResource,
      tokenPayload,
      requestedSessionId: payload.sessionId ?? null,
    });

    if (!resolvedRoomContext.requestedRoomMatched || !resolvedRoomContext.activeRoom) {
      return reply.code(403).send({
        message: '이 공유 링크에서 접근할 수 없는 채팅방입니다.',
      });
    }

    const currentToken = managedContext.managedResource.token;

    if (!hasManagedShareAllowedApp(managedContext.managedResource, 'chat-room-settings')) {
      return reply.code(403).send({
        message: '이 공유 링크에는 채팅방 설정 앱 권한이 없습니다.',
      });
    }

    const saved = await upsertSharedResourceToken({
      id: currentToken.id,
      name: currentToken.name,
      description: currentToken.description,
      tokenSettingId: currentToken.tokenSettingId,
      tokenSettingSnapshot: currentToken.tokenSettingSnapshot,
      resourceLabel: currentToken.resourceLabel,
      resourcePath: currentToken.resourcePath,
      resourceType: currentToken.resourceType,
      shareToken: currentToken.shareToken,
      sharePath: currentToken.sharePath,
      resourceAllowedAppIds: currentToken.allowedAppIds,
      resourceAllowedAppIdsOverrideEnabled: currentToken.resourceAllowedAppIdsOverrideEnabled,
      permissions: currentToken.permissions,
      enabled: currentToken.enabled,
      expiresAt: currentToken.expiresAt,
      usageLimit: currentToken.usageLimit,
      accessPin: payload.accessPin,
      accessPinPromptTtlMinutes: payload.accessPinPromptTtlMinutes,
      allowAccessPinChangeWithoutManage: false,
    });

    if (!saved) {
      return reply.code(404).send({
        message: '공유 채팅방 설정 저장에 실패했습니다.',
      });
    }

    let updatedConversation = await getChatConversation(resolvedRoomContext.activeRoom.sessionId, getRequestClientId(request));

    if (payload.chatTypeId || payload.title || payload.notifyOffline != null) {
      updatedConversation = await updateChatConversationContext(resolvedRoomContext.activeRoom.sessionId, {
        clientId: getRequestClientId(request),
        chatTypeId: payload.chatTypeId?.trim() || undefined,
        lastChatTypeId: payload.chatTypeId?.trim() || undefined,
        contextLabel: payload.chatTypeLabel?.trim() || undefined,
        contextDescription: payload.chatTypeId ? null : undefined,
        title: payload.title?.trim() || undefined,
        notifyOffline: payload.notifyOffline ?? undefined,
      });
    }

    return {
      ok: true,
      hasAccessPin: saved.token.hasAccessPin,
      accessPinPromptTtlMinutes: saved.token.accessPinPromptTtlMinutes,
      conversation: updatedConversation
        ? {
            sessionId: updatedConversation.sessionId,
            title: updatedConversation.title,
            requestBadgeLabel: updatedConversation.requestBadgeLabel ?? null,
            chatTypeId: updatedConversation.chatTypeId ?? null,
            lastChatTypeId: updatedConversation.lastChatTypeId ?? null,
            contextLabel: updatedConversation.contextLabel ?? null,
            contextDescription: updatedConversation.contextDescription ?? null,
            notifyOffline: updatedConversation.notifyOffline,
          }
        : null,
    };
  });

  app.get(`${CHAT_SHARE_ROUTE_PREFIX}/:token/runtime`, async (request, reply) => {
    const params = z.object({
      token: z.string().trim().min(1).max(16000),
    }).parse(request.params ?? {});
    const query = z.object({
      sessionId: z.string().trim().min(1).max(120).optional().nullable(),
    }).parse(request.query ?? {});
    const managedContext = await resolveManagedChatShareContext(params.token);
    const tokenPayload = resolveChatSharePayloadFromManagedResource(managedContext.managedResource) ?? parseChatShareToken(params.token);

    if (!tokenPayload) {
      return reply.code(404).send({
        message: '공유 링크가 유효하지 않습니다.',
      });
    }

    const unavailableMessage = resolveManagedShareUnavailableMessage(managedContext.managedResource);

    if (unavailableMessage) {
      return reply.code(403).send({
        message: unavailableMessage,
      });
    }

    if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
      return;
    }

    if (!hasManagedShareAllowedApp(managedContext.managedResource, 'chat-room-settings')) {
      return reply.code(403).send({
        message: '이 공유 링크에는 채팅방 설정 앱 권한이 없습니다.',
      });
    }

    const resolvedRoomContext = await resolveActiveManagedShareRoom({
      managedResource: managedContext.managedResource,
      tokenPayload,
      requestedSessionId: query.sessionId ?? null,
    });

    if (!resolvedRoomContext.requestedRoomMatched || !resolvedRoomContext.activeRoom) {
      return reply.code(403).send({
        message: '이 공유 링크에서 접근할 수 없는 채팅방입니다.',
      });
    }

    return {
      ok: true,
      item: buildManagedShareRoomRuntimeSnapshot(chatRuntimeService.getSnapshot(), resolvedRoomContext.activeRoom.sessionId),
    };
  });

  app.post(`${CHAT_SHARE_ROUTE_PREFIX}/:token/runtime-requests/:requestId/cancel`, async (request, reply) => {
    const params = z.object({
      token: z.string().trim().min(1).max(16000),
      requestId: z.string().trim().min(1).max(120),
    }).parse(request.params ?? {});
    const payload = z.object({
      sessionId: z.string().trim().min(1).max(120).optional().nullable(),
    }).parse(request.body ?? {});
    const managedContext = await resolveManagedChatShareContext(params.token);
    const tokenPayload = resolveChatSharePayloadFromManagedResource(managedContext.managedResource) ?? parseChatShareToken(params.token);

    if (!tokenPayload) {
      return reply.code(404).send({
        message: '공유 링크가 유효하지 않습니다.',
      });
    }

    const unavailableMessage = resolveManagedShareUnavailableMessage(managedContext.managedResource);

    if (unavailableMessage) {
      return reply.code(403).send({
        message: unavailableMessage,
      });
    }

    if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
      return;
    }

    if (!managedContext.managedResource?.token.permissions?.includes('manage')) {
      return reply.code(403).send({
        message: '이 공유 링크에는 처리중 세션 관리 권한이 없습니다.',
      });
    }

    if (!hasManagedShareAllowedApp(managedContext.managedResource, 'chat-room-settings')) {
      return reply.code(403).send({
        message: '이 공유 링크에는 채팅방 설정 앱 권한이 없습니다.',
      });
    }

    const resolvedRoomContext = await resolveActiveManagedShareRoom({
      managedResource: managedContext.managedResource,
      tokenPayload,
      requestedSessionId: payload.sessionId ?? null,
    });

    if (!resolvedRoomContext.requestedRoomMatched || !resolvedRoomContext.activeRoom) {
      return reply.code(403).send({
        message: '이 공유 링크에서 접근할 수 없는 채팅방입니다.',
      });
    }

    const controller = getChatRuntimeController();

    if (!controller) {
      return reply.code(503).send({
        message: '채팅 런타임 컨트롤러가 준비되지 않았습니다.',
      });
    }

    const detail = controller.getJobDetail(params.requestId);

    if (!detail.item || detail.item.sessionId !== resolvedRoomContext.activeRoom.sessionId) {
      return reply.code(404).send({
        message: '이 채팅방에서 취소할 처리중 세션을 찾지 못했습니다.',
      });
    }

    if (detail.availableActions.remove) {
      const removed = await controller.removeQueuedJob(params.requestId);

      if (!removed) {
        return reply.code(409).send({
          message: '대기 요청 취소 처리에 실패했습니다. 상태를 새로고침한 뒤 다시 시도해 주세요.',
        });
      }

      return {
        ok: true,
        action: 'removed',
      };
    }

    if (detail.availableActions.cancel) {
      const cancelled = await controller.cancelJob(params.requestId);

      if (!cancelled) {
        return reply.code(409).send({
          message: '실행 중 요청 취소 처리에 실패했습니다. 상태를 새로고침한 뒤 다시 시도해 주세요.',
        });
      }

      return {
        ok: true,
        action: 'cancelled',
      };
    }

    return reply.code(409).send({
      message: '이미 종료된 요청이라 지금은 취소할 수 없습니다.',
    });
  });

  app.post(`${CHAT_SHARE_ROUTE_PREFIX}`, async (request, reply) => {
    const parsedPayload = z.object({
      kind: z.enum(['request-bundle', 'inquiry-message', 'prompt']),
      sessionId: z.string().trim().min(1).max(120),
      requestId: z.string().trim().min(1).max(120),
      tokenSettingId: z.string().trim().min(1).max(120),
      name: z.string().trim().max(160).optional(),
      tokenName: z.string().trim().max(160).optional(),
      accessPin: z.string().regex(/^\d{4}$/u).optional().nullable(),
      sourceMessageId: z.coerce.number().int().positive().max(Number.MAX_SAFE_INTEGER).optional(),
      promptIndex: z.coerce.number().int().min(0).max(99).optional(),
      promptSignature: z.string().trim().min(1).max(8000).optional(),
    }).parse(request.body ?? {});
    const payload = {
      ...parsedPayload,
      name: parsedPayload.name || parsedPayload.tokenName || '',
    };

    if (!payload.name) {
      return reply.code(400).send({
        message: '공유 이름을 입력해 주세요.',
      });
    }

    const viewerClientId = getClientIdHeader(request);
    const clientId = canViewAllConversations(request) ? null : viewerClientId;
    const conversation = await getChatConversation(payload.sessionId, clientId || null);

    if (!conversation) {
      return reply.code(404).send({
        message: '공유할 채팅방을 찾을 수 없습니다.',
      });
    }

    const requestItem = await getChatConversationRequest(payload.sessionId, payload.requestId);

    if (!requestItem) {
      return reply.code(404).send({
        message: '공유할 요청을 찾을 수 없습니다.',
      });
    }

    const tokenSetting = await getTokenSettingById(payload.tokenSettingId);

    if (!tokenSetting || !tokenSetting.enabled) {
      return reply.code(404).send({
        message: '공유에 사용할 토큰 설정을 찾을 수 없습니다.',
      });
    }

    if (!tokenSetting.allowedAppIds.some((item) => item.trim().toLowerCase() === 'chat-live')) {
      return reply.code(409).send({
        message: '선택한 토큰 설정에는 공유 채팅방 권한이 없습니다.',
      });
    }

    const shareSnapshot = await buildChatShareSnapshot({
      version: CHAT_SHARE_TOKEN_VERSION,
      kind: payload.kind,
      sessionId: payload.sessionId,
      requestId: payload.requestId,
      tokenSettingId: tokenSetting.id,
      tokenSettingName: tokenSetting.name,
      tokenSettingDefaultExpiresInMinutes: tokenSetting.defaultExpiresInMinutes,
      tokenSettingAllowedAppIds: tokenSetting.allowedAppIds,
      tokenSettingMaxTokensPer30Days: tokenSetting.maxTokensPer30Days,
      tokenSettingMaxTokensPer7Days: tokenSetting.maxTokensPer7Days,
      tokenSettingMaxTokensPer5Hours: tokenSetting.maxTokensPer5Hours,
      tokenSettingOneTimeTokenLimit: tokenSetting.oneTimeTokenLimit,
      sourceMessageId: payload.sourceMessageId,
      promptIndex: payload.promptIndex,
      promptSignature: payload.promptSignature,
    });

    if (!shareSnapshot) {
      return reply.code(404).send({
        message: '공유할 채팅 범위를 찾을 수 없습니다.',
      });
    }

    if (payload.kind === 'prompt') {
      if (payload.promptIndex == null || !payload.promptSignature) {
        return reply.code(400).send({
          message: 'prompt 공유에는 prompt 대상 정보가 필요합니다.',
        });
      }

      if (!shareSnapshot.promptTarget) {
        return reply.code(404).send({
          message: '공유할 prompt 대상을 찾을 수 없습니다.',
        });
      }
    }

    const managedResourceTokenId = createManagedChatShareTokenId();
    const token = randomUUID().replace(/-/g, '').slice(0, 24);
    const sharePath = resolveChatSharePath(token);
    const managedShareConversation = await materializeManagedShareConversation({
      shareSnapshot,
      managedResourceTokenId,
      ownerClientId: clientId || null,
      shareTitle: payload.name,
    });

    await upsertSharedResourceToken({
      id: managedResourceTokenId,
      name: payload.name,
      description: [tokenSetting.description, `공유 유형: ${payload.kind}`].filter(Boolean).join('\n'),
      tokenSettingId: tokenSetting.id,
      tokenSettingSnapshot: {
        id: tokenSetting.id,
        name: tokenSetting.name,
        defaultExpiresInMinutes: tokenSetting.defaultExpiresInMinutes,
        maxTokensPer30Days: tokenSetting.maxTokensPer30Days,
        maxTokensPer7Days: tokenSetting.maxTokensPer7Days,
        maxTokensPer5Hours: tokenSetting.maxTokensPer5Hours,
        oneTimeTokenLimit: tokenSetting.oneTimeTokenLimit,
        allowedAppIds: tokenSetting.allowedAppIds,
      },
      resourceContext: {
        kind: payload.kind,
        sessionId: managedShareConversation.sessionId,
        requestId: managedShareConversation.requestId,
        sourceMessageId: payload.sourceMessageId ?? null,
        promptIndex: payload.promptIndex ?? null,
        promptSignature: payload.promptSignature ?? null,
      },
      resourceAllowedAppIds: tokenSetting.allowedAppIds,
      resourceLabel: payload.name,
      resourcePath: sharePath,
      resourceType: 'chat-share',
      sharePath,
      permissions: payload.kind === 'prompt' ? ['view', 'comment', 'upload'] : ['view', 'comment', 'download', 'upload'],
      enabled: true,
      accessPin: payload.accessPin ?? null,
      expiresAt:
        tokenSetting.defaultExpiresInMinutes > 0
          ? new Date(Date.now() + tokenSetting.defaultExpiresInMinutes * 60 * 1000).toISOString()
          : null,
      usageLimit: 0,
    });

    return {
      ok: true,
      token,
      sharePath,
      name: payload.name,
      tokenSetting: {
        id: tokenSetting.id,
        name: tokenSetting.name,
        defaultExpiresInMinutes: tokenSetting.defaultExpiresInMinutes,
        maxTokensPer30Days: tokenSetting.maxTokensPer30Days,
        maxTokensPer7Days: tokenSetting.maxTokensPer7Days,
        maxTokensPer5Hours: tokenSetting.maxTokensPer5Hours,
        oneTimeTokenLimit: tokenSetting.oneTimeTokenLimit,
        allowedAppIds: tokenSetting.allowedAppIds,
      },
      managedResourceTokenId,
      hasAccessPin: Boolean(payload.accessPin),
    };
  });

  app.get(`${CHAT_SHARE_ROUTE_PREFIX}/:token`, async (request, reply) => {
    const params = z.object({
      token: z.string().trim().min(1).max(16000),
    }).parse(request.params ?? {});
    const query = z.object({
      sessionId: z.string().trim().min(1).max(120).optional(),
      view: z.enum(['full', 'initial']).optional(),
    }).parse(request.query ?? {});
    const managedContext = await resolveManagedChatShareContext(params.token);
    const tokenPayload = resolveChatSharePayloadFromManagedResource(managedContext.managedResource) ?? parseChatShareToken(params.token);

    if (!tokenPayload) {
      return reply.code(404).send({
        message: '공유 링크가 유효하지 않습니다.',
      });
    }

    const unavailableMessage = resolveManagedShareUnavailableMessage(managedContext.managedResource);

    if (unavailableMessage) {
      return reply.code(403).send({
        message: unavailableMessage,
      });
    }

    const accessPinStatus = await ensureManagedShareAccessPin(request, reply, managedContext.sharePath);

    if (!accessPinStatus) {
      return;
    }

    const resolvedRoomContext = await resolveActiveManagedShareRoom({
      managedResource: managedContext.managedResource,
      tokenPayload,
      requestedSessionId: query.sessionId ?? null,
    });

    const activeRoom = resolvedRoomContext.activeRoom;

    if (!activeRoom) {
      return reply.code(404).send({
        message: '공유 채팅방 목록을 찾을 수 없습니다.',
      });
    }

    const shareSnapshot = await buildChatShareSnapshot(tokenPayload, {
      sessionId: activeRoom.sessionId,
      requestId: activeRoom.requestId,
      detailLevel: query.view === 'initial' ? 'initial' : 'full',
    });

    if (!shareSnapshot) {
      return reply.code(404).send({
        message: '공유 대상을 찾을 수 없습니다.',
      });
    }

    if (managedContext.managedResource) {
      void recordSharedResourceTokenUsage(managedContext.managedResource.token.id, {
        actorLabel: 'share-viewer',
        summary: '공유 채팅 링크를 열었습니다.',
        detail: managedContext.managedResource.token.resourceLabel,
      }).catch((error) => {
        request.log.warn(
          {
            err: error,
            managedResourceTokenId: managedContext.managedResource?.token.id,
          },
          'Failed to record shared chat view usage',
        );
      });
    }

    const effectiveTokenSetting = managedContext.managedResource?.token.tokenSettingSnapshot ?? resolveChatShareTokenSettingSnapshot(tokenPayload);
    const effectiveExpiresAt = managedContext.managedResource
      ? resolveSharedResourceTokenEffectiveExpiresAt(managedContext.managedResource.token, effectiveTokenSetting)
      : null;
    const blockedReason = resolveShareBlockedReason(shareSnapshot.requests, effectiveTokenSetting);

    return {
      ok: true,
      share: {
        kind: tokenPayload.kind,
        sessionId: activeRoom.sessionId,
        requestId: activeRoom.requestId,
        sharePath: resolveChatSharePath(params.token),
        createdAt: managedContext.managedResource?.token.createdAt ?? null,
        expiresAt: effectiveExpiresAt,
        tokenSetting: {
          id: effectiveTokenSetting.id,
          name: effectiveTokenSetting.name,
          defaultExpiresInMinutes: effectiveTokenSetting.defaultExpiresInMinutes,
          maxTokensPer30Days: effectiveTokenSetting.maxTokensPer30Days,
          maxTokensPer7Days: effectiveTokenSetting.maxTokensPer7Days,
          maxTokensPer5Hours: effectiveTokenSetting.maxTokensPer5Hours,
          oneTimeTokenLimit: effectiveTokenSetting.oneTimeTokenLimit,
          allowedAppIds: managedContext.managedResource?.token.allowedAppIds ?? effectiveTokenSetting.allowedAppIds,
        },
        managedResourceTokenId: managedContext.managedResource?.token.id ?? tokenPayload.managedResourceTokenId ?? null,
        permissions: managedContext.managedResource?.token.permissions ?? [],
        hasAccessPin: managedContext.managedResource?.token.hasAccessPin ?? false,
        accessPinPromptTtlMinutes: managedContext.managedResource?.token.accessPinPromptTtlMinutes ?? null,
        accessPinSessionExpiresAt: accessPinStatus.status === 'ok' ? accessPinStatus.sessionExpiresAt ?? null : null,
        canSendMessage: !blockedReason,
        blockedReason,
      },
      conversation: {
        sessionId: shareSnapshot.conversation?.sessionId ?? activeRoom.sessionId,
        title: shareSnapshot.conversation?.title ?? activeRoom.title ?? '공유 채팅',
        requestBadgeLabel: shareSnapshot.conversation?.requestBadgeLabel ?? null,
        chatTypeId: shareSnapshot.conversation?.chatTypeId ?? null,
        lastChatTypeId: shareSnapshot.conversation?.lastChatTypeId ?? null,
        contextLabel: shareSnapshot.conversation?.contextLabel ?? null,
        contextDescription: shareSnapshot.conversation?.contextDescription ?? null,
        notifyOffline: shareSnapshot.conversation?.notifyOffline === true,
      },
      rootRequestId: shareSnapshot.rootRequestId,
      targetRequest: shareSnapshot.targetRequest,
      requests: shareSnapshot.requests,
      messages: shareSnapshot.messages,
      activityLogs: shareSnapshot.activityLogs,
      roomRequestCounts: shareSnapshot.roomRequestCounts,
      oldestLoadedMessageId: shareSnapshot.oldestLoadedMessageId,
      hasOlderMessages: shareSnapshot.hasOlderMessages,
      rooms: resolvedRoomContext.rooms,
      activeSessionId: activeRoom.sessionId,
      promptTarget: shareSnapshot.promptTarget,
      detailLevel: shareSnapshot.detailLevel,
      refreshedAt: new Date().toISOString(),
    };
  });

  app.get(`${CHAT_SHARE_ROUTE_PREFIX}/:token/conversations/:sessionId`, async (request, reply) => {
    const params = z.object({
      token: z.string().trim().min(1).max(16000),
      sessionId: z.string().trim().min(1).max(120),
    }).parse(request.params ?? {});
    const query = z.object({
      limit: z.coerce.number().int().min(1).max(200).optional(),
      beforeMessageId: z.coerce.number().int().positive().optional(),
    }).parse(request.query ?? {});
    const managedContext = await resolveManagedChatShareContext(params.token);
    const tokenPayload = resolveChatSharePayloadFromManagedResource(managedContext.managedResource) ?? parseChatShareToken(params.token);

    if (!tokenPayload) {
      return reply.code(404).send({
        message: '공유 링크가 유효하지 않습니다.',
      });
    }

    const unavailableMessage = resolveManagedShareUnavailableMessage(managedContext.managedResource);

    if (unavailableMessage) {
      return reply.code(403).send({
        message: unavailableMessage,
      });
    }

    if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
      return;
    }

    const resolvedRoomContext = await resolveActiveManagedShareRoom({
      managedResource: managedContext.managedResource,
      tokenPayload,
      requestedSessionId: params.sessionId,
    });

    if (!resolvedRoomContext.requestedRoomMatched || !resolvedRoomContext.activeRoom) {
      return reply.code(403).send({
        message: '이 공유 링크에서 접근할 수 없는 채팅방입니다.',
      });
    }

    const item = await getChatConversation(resolvedRoomContext.activeRoom.sessionId, null);

    if (!item) {
      return reply.code(404).send({
        message: '채팅방을 찾을 수 없습니다.',
      });
    }

    const detailPage = await listChatConversationDetailPage(resolvedRoomContext.activeRoom.sessionId, {
      limit: query.limit ?? 40,
      beforeMessageId: query.beforeMessageId ?? null,
    });

    return {
      ok: true,
      item,
      messages: detailPage.messages,
      requests: detailPage.requests,
      activityLogs: detailPage.activityLogs,
      oldestLoadedMessageId: detailPage.oldestLoadedMessageId,
      hasOlderMessages: detailPage.hasOlderMessages,
    };
  });

  app.post(`${CHAT_SHARE_ROUTE_PREFIX}/:token/clear`, async (request, reply) => {
    const params = z.object({
      token: z.string().trim().min(1).max(16000),
    }).parse(request.params ?? {});
    const payload = z.object({
      sessionId: z.string().trim().min(1).max(120).optional().nullable(),
    }).parse(request.body ?? {});
    const managedContext = await resolveManagedChatShareContext(params.token);
    const tokenPayload = resolveChatSharePayloadFromManagedResource(managedContext.managedResource) ?? parseChatShareToken(params.token);

    if (!tokenPayload) {
      return reply.code(404).send({
        message: '공유 링크가 유효하지 않습니다.',
      });
    }

    if (tokenPayload.kind === 'prompt') {
      return reply.code(400).send({
        message: 'prompt 공유 링크에서는 채팅방 기록을 비울 수 없습니다.',
      });
    }

    const resolvedRoomContext = await resolveActiveManagedShareRoom({
      managedResource: managedContext.managedResource,
      tokenPayload,
      requestedSessionId: payload.sessionId ?? null,
    });

    if (!resolvedRoomContext.requestedRoomMatched || !resolvedRoomContext.activeRoom) {
      return reply.code(403).send({
        message: '이 공유 링크에서 접근할 수 없는 채팅방입니다.',
      });
    }

    const shareSnapshot = await buildChatShareSnapshot(tokenPayload, {
      sessionId: resolvedRoomContext.activeRoom.sessionId,
      requestId: resolvedRoomContext.activeRoom.requestId,
    });

    if (!shareSnapshot) {
      return reply.code(404).send({
        message: '공유 대상을 찾을 수 없습니다.',
      });
    }

    const unavailableMessage = resolveManagedShareUnavailableMessage(managedContext.managedResource);

    if (unavailableMessage) {
      return reply.code(403).send({
        message: unavailableMessage,
      });
    }

    if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
      return;
    }

    if (managedContext.managedResource?.token.permissions
      && !managedContext.managedResource.token.permissions.includes('comment')
      && !managedContext.managedResource.token.permissions.includes('upload')) {
      return reply.code(403).send({
        message: '이 공유 링크에는 채팅 기록을 비울 권한이 없습니다.',
      });
    }

    getActiveChatService()?.resetSessionData(resolvedRoomContext.activeRoom.sessionId);
    chatRuntimeService.clearSession(resolvedRoomContext.activeRoom.sessionId);
    const item = await clearChatConversationData(resolvedRoomContext.activeRoom.sessionId, null);

    if (!item) {
      return reply.code(404).send({
        message: '초기화할 채팅방을 찾을 수 없습니다.',
      });
    }

    if (managedContext.managedResource) {
      await recordSharedResourceTokenUsage(managedContext.managedResource.token.id, {
        actorLabel: 'share-viewer',
        summary: '공유 채팅에서 채팅방 기록을 초기화했습니다.',
        detail: resolvedRoomContext.activeRoom.sessionId,
      });
    }

    return {
      ok: true,
      item,
    };
  });

  app.delete(`${CHAT_SHARE_ROUTE_PREFIX}/:token/rooms/:sessionId`, async (request, reply) => {
    const params = z.object({
      token: z.string().trim().min(1).max(16000),
      sessionId: z.string().trim().min(1).max(120),
    }).parse(request.params ?? {});
    const managedContext = await resolveManagedChatShareContext(params.token);
    const tokenPayload = resolveChatSharePayloadFromManagedResource(managedContext.managedResource) ?? parseChatShareToken(params.token);

    if (!tokenPayload) {
      return reply.code(404).send({
        message: '공유 링크가 유효하지 않습니다.',
      });
    }

    if (tokenPayload.kind === 'prompt') {
      return reply.code(400).send({
        message: 'prompt 공유 링크에서는 채팅방을 삭제할 수 없습니다.',
      });
    }

    const unavailableMessage = resolveManagedShareUnavailableMessage(managedContext.managedResource);

    if (unavailableMessage) {
      return reply.code(403).send({
        message: unavailableMessage,
      });
    }

    if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
      return;
    }

    if (!managedContext.managedResource || managedContext.managedResource.token.resourceType !== 'chat-share') {
      return reply.code(404).send({
        message: '공유 채팅방 정보를 찾을 수 없습니다.',
      });
    }

    if (!managedContext.managedResource.token.permissions.includes('manage')) {
      return reply.code(403).send({
        message: '이 공유 링크에는 채팅방을 삭제할 권한이 없습니다.',
      });
    }

    if (!hasManagedShareAllowedApp(managedContext.managedResource, 'chat-room-settings')) {
      return reply.code(403).send({
        message: '이 공유 링크에는 채팅방 설정 앱 권한이 없습니다.',
      });
    }

    const resolvedRoomContext = await resolveActiveManagedShareRoom({
      managedResource: managedContext.managedResource,
      tokenPayload,
      requestedSessionId: params.sessionId,
    });

    if (!resolvedRoomContext.requestedRoomMatched || !resolvedRoomContext.activeRoom) {
      return reply.code(403).send({
        message: '이 공유 링크에서 접근할 수 없는 채팅방입니다.',
      });
    }

    if (resolvedRoomContext.rooms.length <= 1) {
      return reply.code(400).send({
        message: '마지막 채팅방은 삭제할 수 없습니다.',
      });
    }

    getActiveChatService()?.forgetSession(params.sessionId);
    chatRuntimeService.clearSession(params.sessionId);
    const deleted = await deleteChatConversation(params.sessionId);
    const archived = await archiveChatShareTokenRoomMap(managedContext.managedResource.token.id, params.sessionId);

    if (!deleted || !archived.archived) {
      return reply.code(404).send({
        message: '삭제할 채팅방을 찾을 수 없습니다.',
      });
    }

    if (managedContext.managedResource) {
      await recordSharedResourceTokenUsage(managedContext.managedResource.token.id, {
        actorLabel: 'share-manager',
        summary: '공유 채팅방을 삭제했습니다.',
        detail: params.sessionId,
      });
    }

    return {
      ok: true,
      deleted: true,
      deletedSessionId: params.sessionId,
      nextRoomSessionId: archived.nextDefaultRoom?.sessionId ?? null,
    };
  });

  app.post(`${CHAT_SHARE_ROUTE_PREFIX}/:token/messages`, async (request, reply) => {
    const params = z.object({
      token: z.string().trim().min(1).max(16000),
    }).parse(request.params ?? {});
    const payload = z.object({
      text: z.string().trim().min(1).max(20000),
      mode: z.enum(['queue', 'direct']).optional(),
      parentRequestId: z.string().trim().min(1).max(120).optional().nullable(),
      sessionId: z.string().trim().min(1).max(120).optional().nullable(),
      codexModel: z.string().trim().max(120).optional().nullable(),
    }).parse(request.body ?? {});
    const managedContext = await resolveManagedChatShareContext(params.token);
    const tokenPayload = resolveChatSharePayloadFromManagedResource(managedContext.managedResource) ?? parseChatShareToken(params.token);

    if (!tokenPayload) {
      return reply.code(404).send({
        message: '공유 링크가 유효하지 않습니다.',
      });
    }

    if (tokenPayload.kind === 'prompt') {
      return reply.code(400).send({
        message: 'prompt 공유 링크에서는 일반 메시지를 전송할 수 없습니다.',
      });
    }

    const resolvedRoomContext = await resolveActiveManagedShareRoom({
      managedResource: managedContext.managedResource,
      tokenPayload,
      requestedSessionId: payload.sessionId ?? null,
    });

    if (!resolvedRoomContext.requestedRoomMatched || !resolvedRoomContext.activeRoom) {
      return reply.code(403).send({
        message: '이 공유 링크에서 접근할 수 없는 채팅방입니다.',
      });
    }

    const shareSnapshot = await buildChatShareSnapshot(tokenPayload, {
      sessionId: resolvedRoomContext.activeRoom.sessionId,
      requestId: resolvedRoomContext.activeRoom.requestId,
    });

    if (!shareSnapshot) {
      return reply.code(404).send({
        message: '공유 대상을 찾을 수 없습니다.',
      });
    }

    const unavailableMessage = resolveManagedShareUnavailableMessage(managedContext.managedResource);

    if (unavailableMessage) {
      return reply.code(403).send({
        message: unavailableMessage,
      });
    }

    if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
      return;
    }

    if (managedContext.managedResource?.token.permissions
      && !managedContext.managedResource.token.permissions.includes('comment')
      && !managedContext.managedResource.token.permissions.includes('upload')) {
      return reply.code(403).send({
        message: '이 공유 링크에는 답변 전송 권한이 없습니다.',
      });
    }

    const blockedReason = resolveShareBlockedReason(
      shareSnapshot.requests,
      resolveChatShareTokenSettingSnapshot(tokenPayload),
    );

    if (blockedReason) {
      return reply.code(409).send({
        message: blockedReason,
      });
    }

    const requestedParentRequestId = payload.parentRequestId?.trim() || '';
    const resolvedParentRequestId = requestedParentRequestId
      ? resolveRecoveredShareParentRequestId(
          shareSnapshot,
          requestedParentRequestId,
          [shareSnapshot.targetRequest.requestId],
        )
      : null;

    if (
      resolvedParentRequestId
      && !shareSnapshot.requests.some((request) => request.requestId.trim() === resolvedParentRequestId)
    ) {
      return reply.code(403).send({
        message: '이 공유 링크 범위를 벗어난 답변 참조입니다.',
      });
    }

    const queuedRequestId = await getActiveChatService()?.submitExternalMessage(
      resolvedRoomContext.activeRoom.sessionId,
      payload.text,
      {
        mode: payload.mode === 'direct' ? 'direct' : 'queue',
        requestOrigin: 'composer',
        sharedResourceTokenId: managedContext.managedResource?.token.id ?? tokenPayload.managedResourceTokenId ?? null,
        parentRequestId: resolvedParentRequestId,
        contextOverride: Object.prototype.hasOwnProperty.call(payload, 'codexModel')
          ? { codexModel: payload.codexModel ?? null }
          : undefined,
        clientId: shareSnapshot.targetRequest.requesterClientId ?? shareSnapshot.conversation?.clientId ?? null,
      },
    );

    if (!queuedRequestId) {
      return reply.code(503).send({
        message: '공유 요청을 즉시 전송하지 못했습니다.',
      });
    }

    if (managedContext.managedResource) {
      await recordSharedResourceTokenUsage(managedContext.managedResource.token.id, {
        actorLabel: 'share-viewer',
        summary: '공유 채팅에서 후속 메시지를 전송했습니다.',
        detail: resolvedParentRequestId,
      });
    }

    return {
      ok: true,
      queuedRequestId,
    };
  });

  app.post(`${CHAT_SHARE_ROUTE_PREFIX}/:token/origin-reply`, async (request, reply) => {
    const params = z.object({
      token: z.string().trim().min(1).max(16000),
    }).parse(request.params ?? {});
    const payload = z.object({
      sessionId: z.string().trim().min(1).max(120).optional().nullable(),
      sourceSessionId: z.string().trim().min(1).max(120),
      sourceRequestId: z.string().trim().min(1).max(120),
      text: z.string().trim().min(1).max(20000),
      mode: z.enum(['queue', 'direct']).optional(),
    }).parse(request.body ?? {});
    const managedContext = await resolveManagedChatShareContext(params.token);
    const tokenPayload = resolveChatSharePayloadFromManagedResource(managedContext.managedResource) ?? parseChatShareToken(params.token);

    if (!tokenPayload) {
      return reply.code(404).send({
        message: '공유 링크가 유효하지 않습니다.',
      });
    }

    const resolvedRoomContext = await resolveActiveManagedShareRoom({
      managedResource: managedContext.managedResource,
      tokenPayload,
      requestedSessionId: payload.sessionId ?? null,
    });

    if (!resolvedRoomContext.requestedRoomMatched || !resolvedRoomContext.activeRoom) {
      return reply.code(403).send({
        message: '이 공유 링크에서 접근할 수 없는 채팅방입니다.',
      });
    }

    const unavailableMessage = resolveManagedShareUnavailableMessage(managedContext.managedResource);

    if (unavailableMessage) {
      return reply.code(403).send({
        message: unavailableMessage,
      });
    }

    if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
      return;
    }

    if (managedContext.managedResource?.token.permissions && !managedContext.managedResource.token.permissions.includes('comment')) {
      return reply.code(403).send({
        message: '이 공유 링크에는 원 세션 답변 전송 권한이 없습니다.',
      });
    }

    const normalizedSourceSessionId = payload.sourceSessionId.trim();
    const normalizedSourceRequestId = payload.sourceRequestId.trim();
    const allowedLinkTargets = resolvedRoomContext.rooms
      .map((room) => room.linkContext)
      .filter((item): item is ChatShareRoomLinkContext => item?.kind === 'linked-session');
    const matchedLinkTarget = allowedLinkTargets.find((item) =>
      item.sourceSessionId === normalizedSourceSessionId && item.sourceRequestId === normalizedSourceRequestId,
    );

    if (!matchedLinkTarget) {
      return reply.code(403).send({
        message: '현재 공유채팅에 연결된 원 세션만 답변 전송할 수 있습니다.',
      });
    }

    const targetRequest = await getChatConversationRequest(normalizedSourceSessionId, normalizedSourceRequestId);

    if (!targetRequest) {
      return reply.code(404).send({
        message: '원 세션 요청을 찾지 못했습니다.',
      });
    }

    const targetConversation = await getChatConversation(normalizedSourceSessionId, null);
    const queuedRequestId = await getActiveChatService()?.submitExternalMessage(
      normalizedSourceSessionId,
      payload.text,
      {
        mode: payload.mode === 'direct' ? 'direct' : 'queue',
        requestOrigin: 'composer',
        sharedResourceTokenId: managedContext.managedResource?.token.id ?? tokenPayload.managedResourceTokenId ?? null,
        parentRequestId: normalizedSourceRequestId,
        clientId: targetRequest.requesterClientId ?? targetConversation?.clientId ?? null,
      },
    );

    if (!queuedRequestId) {
      return reply.code(503).send({
        message: '원 세션 답변 전송을 시작하지 못했습니다.',
      });
    }

    if (managedContext.managedResource) {
      await recordSharedResourceTokenUsage(managedContext.managedResource.token.id, {
        actorLabel: 'share-viewer',
        summary: '공유채팅에서 원 세션으로 답변을 전송했습니다.',
        detail: `${normalizedSourceSessionId}:${normalizedSourceRequestId}`,
      });
    }

    return {
      ok: true,
      queuedRequestId,
    };
  });

  app.post(`${CHAT_SHARE_ROUTE_PREFIX}/:token/attachments`, { bodyLimit: CHAT_ATTACHMENT_ROUTE_BODY_LIMIT }, async (request, reply) => {
    const params = z.object({
      token: z.string().trim().min(1).max(16000),
    }).parse(request.params ?? {});
    const isBinaryRequest = isOctetStreamRequest(request.headers['content-type']);
    const payload = isBinaryRequest
      ? parseChatAttachmentBinaryHeaders(request.headers, { allowOptionalSessionId: true })
      : chatAttachmentShareJsonBodySchema.parse(request.body ?? {});
    const managedContext = await resolveManagedChatShareContext(params.token);
    const tokenPayload = resolveChatSharePayloadFromManagedResource(managedContext.managedResource) ?? parseChatShareToken(params.token);

    if (!tokenPayload) {
      return reply.code(404).send({
        message: '공유 링크가 유효하지 않습니다.',
      });
    }

    const resolvedRoomContext = await resolveActiveManagedShareRoom({
      managedResource: managedContext.managedResource,
      tokenPayload,
      requestedSessionId: payload.sessionId ?? null,
    });

    if (!resolvedRoomContext.requestedRoomMatched || !resolvedRoomContext.activeRoom) {
      return reply.code(403).send({
        message: '이 공유 링크에서 접근할 수 없는 채팅방입니다.',
      });
    }

    const shareSnapshot = await buildChatShareSnapshot(tokenPayload, {
      sessionId: resolvedRoomContext.activeRoom.sessionId,
      requestId: resolvedRoomContext.activeRoom.requestId,
    });

    if (!shareSnapshot) {
      return reply.code(404).send({
        message: '공유 대상을 찾을 수 없습니다.',
      });
    }

    const unavailableMessage = resolveManagedShareUnavailableMessage(managedContext.managedResource);

    if (unavailableMessage) {
      return reply.code(403).send({
        message: unavailableMessage,
      });
    }

    if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
      return;
    }

    if (managedContext.managedResource?.token.permissions
      && !managedContext.managedResource.token.permissions.includes('comment')
      && !managedContext.managedResource.token.permissions.includes('upload')) {
      return reply.code(403).send({
        message: '이 공유 링크에는 첨부 업로드 권한이 없습니다.',
      });
    }

    const blockedReason = resolveShareBlockedReason(
      shareSnapshot.requests,
      resolveChatShareTokenSettingSnapshot(tokenPayload),
    );

    if (blockedReason) {
      return reply.code(409).send({
        message: blockedReason,
      });
    }

    const saved = isBinaryRequest
      ? await saveChatAttachmentBuffer({
          sessionId: resolvedRoomContext.activeRoom.sessionId,
          fileName: payload.fileName,
          mimeType: payload.mimeType,
          buffer: Buffer.isBuffer(request.body) ? request.body : Buffer.alloc(0),
        })
      : await saveChatAttachmentFile({
          sessionId: resolvedRoomContext.activeRoom.sessionId,
          fileName: payload.fileName,
          mimeType: payload.mimeType,
          contentBase64: chatAttachmentShareJsonBodySchema.parse(request.body ?? {}).contentBase64,
        });

    if (!saved.ok) {
      return reply.code(saved.statusCode).send({
        message: saved.message,
      });
    }

    if (managedContext.managedResource) {
      await recordSharedResourceTokenUsage(managedContext.managedResource.token.id, {
        actorLabel: 'share-viewer',
        summary: '공유 채팅에 첨부 파일을 업로드했습니다.',
        detail: shareSnapshot.targetRequest.requestId,
      });
    }

    return {
      ok: true,
      item: saved.item,
    };
  });

  app.post(`${CHAT_SHARE_ROUTE_PREFIX}/:token/prompt-submit`, async (request, reply) => {
    const params = z.object({
      token: z.string().trim().min(1).max(16000),
    }).parse(request.params ?? {});
    const payload = z.object({
      sessionId: z.string().trim().min(1).max(120).optional().nullable(),
      parentRequestId: z.string().trim().min(1).max(120),
      promptIndex: z.number().int().min(0).max(99),
      promptTitle: z.string().trim().min(1).max(500),
      promptSignature: z.string().trim().min(1).max(8000),
      sourceMessageId: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
      selectedValues: z.array(z.string().trim().min(1).max(500)).max(50),
      freeText: z.string().max(10000).optional().nullable(),
      stepSelections: z.array(
        z.object({
          stepKey: z.string().trim().min(1).max(120),
          stepTitle: z.string().trim().max(500).optional().nullable(),
          selectedValues: z.array(z.string().trim().min(1).max(500)).max(50),
          freeText: z.string().max(10000),
          skipped: z.boolean().optional(),
        }),
      ).max(20).optional(),
      summaryText: z.string().max(10000).optional().nullable(),
      attachments: z.array(chatComposerAttachmentSchema).max(20).optional(),
      followupText: z.string().trim().min(1).max(20000),
      mode: z.enum(['queue', 'direct']).optional(),
      contextRef: chatPromptContextRefSchema,
    }).parse(request.body ?? {});
    const managedContext = await resolveManagedChatShareContext(params.token);
    const tokenPayload = resolveChatSharePayloadFromManagedResource(managedContext.managedResource) ?? parseChatShareToken(params.token);

    if (!tokenPayload) {
      return reply.code(404).send({
        message: '공유 링크가 유효하지 않습니다.',
      });
    }

    const resolvedRoomContext = await resolveActiveManagedShareRoom({
      managedResource: managedContext.managedResource,
      tokenPayload,
      requestedSessionId: payload.sessionId ?? null,
    });

    if (!resolvedRoomContext.requestedRoomMatched || !resolvedRoomContext.activeRoom) {
      return reply.code(403).send({
        message: '이 공유 링크에서 접근할 수 없는 채팅방입니다.',
      });
    }

    const shareSnapshot = await buildChatShareSnapshot(tokenPayload, {
      sessionId: resolvedRoomContext.activeRoom.sessionId,
      requestId: resolvedRoomContext.activeRoom.requestId,
    });

    if (!shareSnapshot) {
      return reply.code(404).send({
        message: '공유 대상을 찾을 수 없습니다.',
      });
    }

    const unavailableMessage = resolveManagedShareUnavailableMessage(managedContext.managedResource);

    if (unavailableMessage) {
      return reply.code(403).send({
        message: unavailableMessage,
      });
    }

    if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
      return;
    }

    if (managedContext.managedResource?.token.permissions && !managedContext.managedResource.token.permissions.includes('comment')) {
      return reply.code(403).send({
        message: '이 공유 링크에는 prompt 응답 권한이 없습니다.',
      });
    }

    const blockedReason = resolveShareBlockedReason(
      shareSnapshot.requests,
      resolveChatShareTokenSettingSnapshot(tokenPayload),
    );

    if (blockedReason) {
      return reply.code(409).send({
        message: blockedReason,
      });
    }

    const sourceMessageParentRequestId = shareSnapshot.messages
      .find((message: { id: number; clientRequestId?: string | null }) => message.id === payload.sourceMessageId)
      ?.clientRequestId?.trim() || '';
    const normalizedParentRequestId = resolveRecoveredShareParentRequestId(
      shareSnapshot,
      payload.parentRequestId,
      [sourceMessageParentRequestId, shareSnapshot.targetRequest.requestId],
    );

    if (!normalizedParentRequestId || !shareSnapshot.requests.some((request) => request.requestId.trim() === normalizedParentRequestId)) {
      return reply.code(403).send({
        message: '이 공유 링크 범위를 벗어난 prompt 입니다.',
      });
    }

    if (
      tokenPayload.kind === 'prompt' &&
      (normalizedParentRequestId !== tokenPayload.requestId ||
        payload.promptIndex !== tokenPayload.promptIndex ||
        payload.sourceMessageId !== tokenPayload.sourceMessageId ||
        payload.promptSignature !== tokenPayload.promptSignature)
    ) {
      return reply.code(403).send({
        message: '이 공유 링크에서 허용되지 않은 prompt 입니다.',
      });
    }

    const existingPromptRequest = await findExistingActivePromptFollowupRequest(
      resolvedRoomContext.activeRoom.sessionId,
      normalizedParentRequestId,
      payload.followupText,
    );

    const queuedRequestId = existingPromptRequest?.requestId ?? await getActiveChatService()?.submitExternalMessage(
      resolvedRoomContext.activeRoom.sessionId,
      payload.followupText,
      {
        mode: resolvePromptFollowupMode(payload.mode),
        requestOrigin: 'prompt',
        sharedResourceTokenId: managedContext.managedResource?.token.id ?? tokenPayload.managedResourceTokenId ?? null,
        parentRequestId: normalizedParentRequestId,
        promptContextRef: payload.contextRef ?? null,
        clientId: shareSnapshot.targetRequest.requesterClientId ?? shareSnapshot.conversation?.clientId ?? null,
      },
    );

    if (!queuedRequestId) {
      return reply.code(503).send({
        message: 'prompt 선택 전송을 시작하지 못했습니다.',
      });
    }

    const persisted = await persistChatConversationPromptSelection(
      resolvedRoomContext.activeRoom.sessionId,
      normalizedParentRequestId,
      payload,
    );

    if (!persisted) {
      return reply.code(404).send({
        message: '즉시 전송은 접수됐지만 prompt 선택 상태를 저장하지 못했습니다.',
      });
    }

    getActiveChatService()?.broadcastRequestUpdate(resolvedRoomContext.activeRoom.sessionId, persisted.request);
    getActiveChatService()?.broadcastMessageUpdate(resolvedRoomContext.activeRoom.sessionId, persisted.message);

    if (managedContext.managedResource) {
      await recordSharedResourceTokenUsage(managedContext.managedResource.token.id, {
        actorLabel: 'share-viewer',
        summary: '공유 prompt 응답을 제출했습니다.',
        detail: normalizedParentRequestId,
      });
    }

    return {
      ok: true,
      item: persisted.request,
      message: persisted.message,
      queuedRequestId,
    };
  });

  app.post(`${CHAT_SHARE_ROUTE_PREFIX}/:token/manual-completion`, async (request, reply) => {
    const params = z.object({
      token: z.string().trim().min(1).max(16000),
    }).parse(request.params ?? {});
    const payload = z.object({
      sessionId: z.string().trim().min(1).max(120).optional().nullable(),
      parentRequestId: z.string().trim().min(1).max(120),
      type: z.enum(['prompt', 'verification']),
    }).parse(request.body ?? {});
    const managedContext = await resolveManagedChatShareContext(params.token);
    const tokenPayload = resolveChatSharePayloadFromManagedResource(managedContext.managedResource) ?? parseChatShareToken(params.token);

    if (!tokenPayload) {
      return reply.code(404).send({
        message: '공유 링크가 유효하지 않습니다.',
      });
    }

    const resolvedRoomContext = await resolveActiveManagedShareRoom({
      managedResource: managedContext.managedResource,
      tokenPayload,
      requestedSessionId: payload.sessionId ?? null,
    });

    if (!resolvedRoomContext.requestedRoomMatched || !resolvedRoomContext.activeRoom) {
      return reply.code(403).send({
        message: '이 공유 링크에서 접근할 수 없는 채팅방입니다.',
      });
    }

    const shareSnapshot = await buildChatShareSnapshot(tokenPayload, {
      sessionId: resolvedRoomContext.activeRoom.sessionId,
      requestId: resolvedRoomContext.activeRoom.requestId,
    });

    if (!shareSnapshot) {
      return reply.code(404).send({
        message: '공유 대상을 찾을 수 없습니다.',
      });
    }

    const unavailableMessage = resolveManagedShareUnavailableMessage(managedContext.managedResource);

    if (unavailableMessage) {
      return reply.code(403).send({
        message: unavailableMessage,
      });
    }

    if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
      return;
    }

    if (managedContext.managedResource?.token.permissions && !managedContext.managedResource.token.permissions.includes('comment')) {
      return reply.code(403).send({
        message: '이 공유 링크에는 완료 처리 권한이 없습니다.',
      });
    }

    const normalizedParentRequestId = resolveRecoveredShareParentRequestId(
      shareSnapshot,
      payload.parentRequestId,
      [shareSnapshot.targetRequest.requestId],
    );

    if (!normalizedParentRequestId || !shareSnapshot.requests.some((request) => request.requestId.trim() === normalizedParentRequestId)) {
      return reply.code(403).send({
        message: '이 공유 링크 범위를 벗어난 요청입니다.',
      });
    }

    if (tokenPayload.kind === 'prompt' && normalizedParentRequestId !== tokenPayload.requestId) {
      return reply.code(403).send({
        message: '이 공유 링크에서 허용되지 않은 요청입니다.',
      });
    }

    let item = null;

    try {
      item = await markChatConversationRequestManualCompletion(
        resolvedRoomContext.activeRoom.sessionId,
        normalizedParentRequestId,
        payload.type,
      );
    } catch (error) {
      if (error instanceof ChatConversationManualCompletionBlockedError) {
        return reply.code(409).send({
          message: error.message,
        });
      }

      throw error;
    }

    if (!item) {
      return reply.code(404).send({
        message: '완료 처리할 요청을 찾지 못했습니다.',
      });
    }

    getActiveChatService()?.broadcastRequestUpdate(resolvedRoomContext.activeRoom.sessionId, item);

    return {
      ok: true,
      item,
    };
  });

  app.post(`${CHAT_SHARE_ROUTE_PREFIX}/:token/request-cancel`, async (request, reply) => {
    const params = z.object({
      token: z.string().trim().min(1).max(16000),
    }).parse(request.params ?? {});
    const payload = z.object({
      sessionId: z.string().trim().min(1).max(120).optional().nullable(),
      parentRequestId: z.string().trim().min(1).max(120),
    }).parse(request.body ?? {});
    const managedContext = await resolveManagedChatShareContext(params.token);
    const tokenPayload = resolveChatSharePayloadFromManagedResource(managedContext.managedResource) ?? parseChatShareToken(params.token);

    if (!tokenPayload) {
      return reply.code(404).send({
        message: '공유 링크가 유효하지 않습니다.',
      });
    }

    const resolvedRoomContext = await resolveActiveManagedShareRoom({
      managedResource: managedContext.managedResource,
      tokenPayload,
      requestedSessionId: payload.sessionId ?? null,
    });

    if (!resolvedRoomContext.requestedRoomMatched || !resolvedRoomContext.activeRoom) {
      return reply.code(403).send({
        message: '이 공유 링크에서 접근할 수 없는 채팅방입니다.',
      });
    }

    const shareSnapshot = await buildChatShareSnapshot(tokenPayload, {
      sessionId: resolvedRoomContext.activeRoom.sessionId,
      requestId: resolvedRoomContext.activeRoom.requestId,
    });

    if (!shareSnapshot) {
      return reply.code(404).send({
        message: '공유 대상을 찾을 수 없습니다.',
      });
    }

    const unavailableMessage = resolveManagedShareUnavailableMessage(managedContext.managedResource);

    if (unavailableMessage) {
      return reply.code(403).send({
        message: unavailableMessage,
      });
    }

    if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
      return;
    }

    if (managedContext.managedResource?.token.permissions && !managedContext.managedResource.token.permissions.includes('comment')) {
      return reply.code(403).send({
        message: '이 공유 링크에는 요청 취소 처리 권한이 없습니다.',
      });
    }

    const normalizedParentRequestId = resolveRecoveredShareParentRequestId(
      shareSnapshot,
      payload.parentRequestId,
      [shareSnapshot.targetRequest.requestId],
    );

    if (!normalizedParentRequestId || !shareSnapshot.requests.some((request) => request.requestId.trim() === normalizedParentRequestId)) {
      return reply.code(403).send({
        message: '이 공유 링크 범위를 벗어난 요청입니다.',
      });
    }

    if (tokenPayload.kind === 'prompt' && normalizedParentRequestId !== tokenPayload.requestId) {
      return reply.code(403).send({
        message: '이 공유 링크에서 허용되지 않은 요청입니다.',
      });
    }

    const targetRequest = shareSnapshot.requests.find((request) => request.requestId.trim() === normalizedParentRequestId) ?? null;

    if (
      !targetRequest
      || targetRequest.hasResponse
      || targetRequest.status !== 'failed'
      || (targetRequest.statusMessage?.trim() ?? '') !== '중단된 오래된 요청'
    ) {
      return reply.code(409).send({
        message: '지금은 이 요청을 취소 처리할 수 없습니다.',
      });
    }

    const result = await cancelUnansweredChatConversationRequest(
      resolvedRoomContext.activeRoom.sessionId,
      normalizedParentRequestId,
      '사용자 요청으로 중단된 요청을 취소 처리했습니다.',
    );

    if (!result.cancelled || !result.item) {
      if (result.reason === 'answered') {
        return reply.code(409).send({
          message: '이미 답변이 연결된 요청은 취소 처리할 수 없습니다.',
        });
      }

      if (result.reason === 'active') {
        return reply.code(409).send({
          message: '현재 처리 중인 요청은 여기서 취소 처리할 수 없습니다.',
        });
      }

      if (result.reason === 'already_terminal') {
        return reply.code(409).send({
          message: '이미 취소 처리된 요청입니다.',
        });
      }

      return reply.code(404).send({
        message: '취소 처리할 요청을 찾지 못했습니다.',
      });
    }

    getActiveChatService()?.broadcastRequestUpdate(resolvedRoomContext.activeRoom.sessionId, result.item);

    return {
      ok: true,
      item: result.item,
    };
  });

  app.post(`${CHAT_SHARE_ROUTE_PREFIX}/:token/request-retry`, async (request, reply) => {
    const params = z.object({
      token: z.string().trim().min(1).max(16000),
    }).parse(request.params ?? {});
    const payload = z.object({
      sessionId: z.string().trim().min(1).max(120).optional().nullable(),
      parentRequestId: z.string().trim().min(1).max(120),
    }).parse(request.body ?? {});
    const managedContext = await resolveManagedChatShareContext(params.token);
    const tokenPayload = resolveChatSharePayloadFromManagedResource(managedContext.managedResource) ?? parseChatShareToken(params.token);

    if (!tokenPayload) {
      return reply.code(404).send({
        message: '공유 링크가 유효하지 않습니다.',
      });
    }

    const resolvedRoomContext = await resolveActiveManagedShareRoom({
      managedResource: managedContext.managedResource,
      tokenPayload,
      requestedSessionId: payload.sessionId ?? null,
    });

    if (!resolvedRoomContext.requestedRoomMatched || !resolvedRoomContext.activeRoom) {
      return reply.code(403).send({
        message: '이 공유 링크에서 접근할 수 없는 채팅방입니다.',
      });
    }

    const shareSnapshot = await buildChatShareSnapshot(tokenPayload, {
      sessionId: resolvedRoomContext.activeRoom.sessionId,
      requestId: resolvedRoomContext.activeRoom.requestId,
    });

    if (!shareSnapshot) {
      return reply.code(404).send({
        message: '공유 대상을 찾을 수 없습니다.',
      });
    }

    const unavailableMessage = resolveManagedShareUnavailableMessage(managedContext.managedResource);

    if (unavailableMessage) {
      return reply.code(403).send({
        message: unavailableMessage,
      });
    }

    if (!(await ensureManagedShareAccessPin(request, reply, managedContext.sharePath))) {
      return;
    }

    if (managedContext.managedResource?.token.permissions && !managedContext.managedResource.token.permissions.includes('comment')) {
      return reply.code(403).send({
        message: '이 공유 링크에는 요청 재처리 권한이 없습니다.',
      });
    }

    const blockedReason = resolveShareBlockedReason(
      shareSnapshot.requests,
      resolveChatShareTokenSettingSnapshot(tokenPayload),
    );

    if (blockedReason) {
      return reply.code(409).send({
        message: blockedReason,
      });
    }

    const normalizedParentRequestId = resolveRecoveredShareParentRequestId(
      shareSnapshot,
      payload.parentRequestId,
      [shareSnapshot.targetRequest.requestId],
    );

    if (!normalizedParentRequestId || !shareSnapshot.requests.some((request) => request.requestId.trim() === normalizedParentRequestId)) {
      return reply.code(403).send({
        message: '이 공유 링크 범위를 벗어난 요청입니다.',
      });
    }

    if (tokenPayload.kind === 'prompt' && normalizedParentRequestId !== tokenPayload.requestId) {
      return reply.code(403).send({
        message: '이 공유 링크에서 허용되지 않은 요청입니다.',
      });
    }

    const targetRequest = shareSnapshot.requests.find((request) => request.requestId.trim() === normalizedParentRequestId) ?? null;

    if (
      !targetRequest
      || targetRequest.hasResponse
      || targetRequest.status !== 'failed'
      || (targetRequest.statusMessage?.trim() ?? '') !== '중단된 오래된 요청'
    ) {
      return reply.code(409).send({
        message: '지금은 이 요청을 재처리할 수 없습니다.',
      });
    }

    const normalizedUserText = targetRequest.userText.trim();

    if (!normalizedUserText) {
      return reply.code(409).send({
        message: '재처리할 요청 본문이 없습니다.',
      });
    }

    const queuedRequestId = await getActiveChatService()?.submitExternalMessage(
      resolvedRoomContext.activeRoom.sessionId,
      normalizedUserText,
      {
        requestId: normalizedParentRequestId,
        mode: 'direct',
        requestOrigin: targetRequest.requestOrigin === 'prompt' ? 'prompt' : 'composer',
        sharedResourceTokenId: managedContext.managedResource?.token.id ?? tokenPayload.managedResourceTokenId ?? null,
        parentRequestId: targetRequest.requestOrigin === 'prompt' ? targetRequest.parentRequestId?.trim() || null : null,
        clientId: targetRequest.requesterClientId ?? shareSnapshot.conversation?.clientId ?? null,
      },
    );

    if (!queuedRequestId) {
      return reply.code(503).send({
        message: '중단된 요청 재처리를 시작하지 못했습니다.',
      });
    }

    if (managedContext.managedResource) {
      await recordSharedResourceTokenUsage(managedContext.managedResource.token.id, {
        actorLabel: 'share-viewer',
        summary: '공유 채팅에서 중단 요청 재처리를 시작했습니다.',
        detail: normalizedParentRequestId,
      });
    }

    return {
      ok: true,
      queuedRequestId,
    };
  });

  app.get('/api/chat/conversations', async (request) => {
    const query = z.object({
      limit: z.coerce.number().int().min(1).max(200).optional(),
    }).parse(request.query ?? {});

    const viewerClientId = getClientIdHeader(request);
    const clientId = canViewAllConversations(request) ? null : viewerClientId;
    const items = await listChatConversations(clientId, query.limit ?? 200, viewerClientId || null);

    return {
      ok: true,
      items,
    };
  });

  app.get('/api/chat/source-changes', async (request) => {
    const query = z.object({
      limit: z.coerce.number().int().min(1).max(500).optional(),
    }).parse(request.query ?? {});

    const viewerClientId = getClientIdHeader(request);
    const clientId = canViewAllConversations(request) ? null : viewerClientId;
    const items = await listChatSourceChangeSnapshots(clientId, query.limit ?? 300);

    return {
      ok: true,
      items,
    };
  });

  app.get('/api/chat/runtime', async () => {
    return {
      ok: true,
      item: chatRuntimeService.getSnapshot(),
    };
  });

  app.post('/api/chat/attachments', { bodyLimit: CHAT_ATTACHMENT_ROUTE_BODY_LIMIT }, async (request, reply) => {
    const isBinaryRequest = isOctetStreamRequest(request.headers['content-type']);
    const saved = isBinaryRequest
      ? await (() => {
          const binaryPayload = parseChatAttachmentBinaryHeaders(request.headers);
          return saveChatAttachmentBuffer({
            sessionId: binaryPayload.sessionId ?? '',
            fileName: binaryPayload.fileName,
            mimeType: binaryPayload.mimeType,
            buffer: Buffer.isBuffer(request.body) ? request.body : Buffer.alloc(0),
          });
        })()
      : await saveChatAttachmentFile(chatAttachmentJsonBodySchema.parse(request.body ?? {}));

    if (!saved.ok) {
      return reply.code(saved.statusCode).send({
        message: saved.message,
      });
    }

    return {
      ok: true,
      item: saved.item,
    };
  });

  app.get('/api/chat/runtime/jobs/:requestId', async (request, reply) => {
    const params = z.object({
      requestId: z.string().trim().min(1).max(120),
    }).parse(request.params ?? {});
    const controller = getChatRuntimeController();

    if (!controller) {
      return reply.code(503).send({
        message: '채팅 런타임 컨트롤러가 준비되지 않았습니다.',
      });
    }

    return {
      ok: true,
      item: controller.getJobDetail(params.requestId),
    };
  });

  app.post('/api/chat/runtime/jobs/:requestId/cancel', async (request, reply) => {
    const params = z.object({
      requestId: z.string().trim().min(1).max(120),
    }).parse(request.params ?? {});
    const controller = getChatRuntimeController();

    if (!controller) {
      return reply.code(503).send({
        message: '채팅 런타임 컨트롤러가 준비되지 않았습니다.',
      });
    }

    const cancelled = await controller.cancelJob(params.requestId);

    if (!cancelled) {
      return reply.code(404).send({
        message: '취소할 실행 중 요청을 찾지 못했습니다.',
      });
    }

    return {
      ok: true,
      cancelled: true,
    };
  });

  app.post('/api/chat/runtime/jobs/:requestId/remove', async (request, reply) => {
    const params = z.object({
      requestId: z.string().trim().min(1).max(120),
    }).parse(request.params ?? {});
    const controller = getChatRuntimeController();

    if (!controller) {
      return reply.code(503).send({
        message: '채팅 런타임 컨트롤러가 준비되지 않았습니다.',
      });
    }

    const removed = await controller.removeQueuedJob(params.requestId);

    if (!removed) {
      return reply.code(404).send({
        message: '제거할 대기 요청을 찾지 못했습니다.',
      });
    }

    return {
      ok: true,
      removed: true,
    };
  });

  app.post('/api/chat/runtime/jobs/:requestId/rollback', async (request, reply) => {
    const params = z.object({
      requestId: z.string().trim().min(1).max(120),
    }).parse(request.params ?? {});
    const payload = z
      .object({
        sessionId: z.string().trim().min(1).max(120).optional(),
      })
      .parse(request.body ?? {});

    chatRuntimeService.appendLog(params.requestId, '사용자 요청으로 최근 실행 롤백을 시작합니다.');

    try {
      const result = await rollbackChatRuntimeRequest({
        requestId: params.requestId,
        sessionId: payload.sessionId,
      });

      await upsertChatConversationRequest(result.sessionId, {
        requestId: result.requestId,
        status: 'cancelled',
        statusMessage: '사용자 요청으로 최근 실행 변경을 롤백했습니다.',
      });
      chatRuntimeService.setArchivedJobTerminalStatus(
        params.requestId,
        'cancelled',
        '최근 실행이 롤백되어 상태를 취소로 변경했습니다.',
      );
      chatRuntimeService.appendLog(params.requestId, '최근 실행 롤백이 완료되었습니다.');

      return {
        ok: true,
        ...result,
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : '최근 실행 롤백에 실패했습니다.';
      chatRuntimeService.appendLog(params.requestId, `최근 실행 롤백 실패: ${message}`);

      return reply.code(409).send({
        message,
      });
    }
  });

  app.post('/api/chat/conversations', async (request) => {
    const payload = z.object({
      sessionId: z.string().trim().min(1).max(120),
      title: z.string().trim().max(200).optional(),
      draftText: z.string().max(200000).optional().nullable(),
      requestBadgeLabel: z.string().trim().max(120).optional().nullable(),
      codexModel: z.string().trim().max(120).optional().nullable(),
      chatTypeId: z.string().trim().max(120).nullable().optional(),
      lastChatTypeId: z.string().trim().max(120).nullable().optional(),
      generalSectionName: z.string().trim().max(120).optional().nullable(),
      contextLabel: z.string().trim().max(200).optional(),
      contextDescription: z.string().trim().max(CHAT_CONTEXT_DESCRIPTION_MAX_LENGTH).optional(),
      notifyOffline: z.boolean().optional(),
    }).parse(request.body ?? {});

    const clientId = getClientIdHeader(request);
    const item = await createChatConversation({
      sessionId: payload.sessionId,
      clientId: clientId || null,
      title: payload.title ?? '새 대화',
      draftText: payload.draftText ?? '',
      requestBadgeLabel: payload.requestBadgeLabel ?? null,
      codexModel: payload.codexModel ?? null,
      chatTypeId: payload.chatTypeId ?? null,
      lastChatTypeId: payload.lastChatTypeId ?? payload.chatTypeId ?? null,
      generalSectionName: payload.generalSectionName ?? null,
      contextLabel: payload.contextLabel ?? null,
      contextDescription: payload.contextDescription ?? null,
      notifyOffline: payload.notifyOffline ?? true,
    });

    return {
      ok: true,
      item,
    };
  });

  app.get('/api/chat/conversations/:sessionId', async (request, reply) => {
    const params = z.object({
      sessionId: z.string().trim().min(1).max(120),
    }).parse(request.params ?? {});
    const query = z.object({
      limit: z.coerce.number().int().min(1).max(500).optional(),
      beforeMessageId: z.coerce.number().int().positive().optional(),
    }).parse(request.query ?? {});

    const clientId = canViewAllConversations(request) ? null : getClientIdHeader(request);
    const item = await getChatConversation(params.sessionId, clientId || null);

    if (!item) {
      return reply.code(404).send({
        message: '채팅방을 찾을 수 없습니다.',
      });
    }

    const messageLimit = query.limit ?? 10;
    const detailPage = await listChatConversationDetailPage(params.sessionId, {
      limit: messageLimit,
      beforeMessageId: query.beforeMessageId ?? null,
    });

    return {
      ok: true,
      item,
      messages: detailPage.messages,
      requests: detailPage.requests,
      activityLogs: detailPage.activityLogs,
      oldestLoadedMessageId: detailPage.oldestLoadedMessageId,
      hasOlderMessages: detailPage.hasOlderMessages,
    };
  });

  app.post('/api/chat/conversations/:sessionId/read', async (request, reply) => {
    const params = z.object({
      sessionId: z.string().trim().min(1).max(120),
    }).parse(request.params ?? {});

    const clientId = getClientIdHeader(request);

    if (!clientId) {
      return reply.code(400).send({
        message: '읽음 처리를 위한 clientId가 필요합니다.',
      });
    }

    const result = await markChatConversationResponsesRead(params.sessionId, clientId);

    if (!result) {
      return reply.code(404).send({
        message: '읽음 처리할 채팅방을 찾지 못했습니다.',
      });
    }

    return {
      ok: true,
      ...result,
    };
  });

  app.delete('/api/chat/conversations/:sessionId/requests/:requestId', async (request, reply) => {
    const params = z.object({
      sessionId: z.string().trim().min(1).max(120),
      requestId: z.string().trim().min(1).max(120),
    }).parse(request.params ?? {});

    const result = await deleteUnansweredChatConversationRequest(params.sessionId, params.requestId);

    if (!result.deleted) {
      if (result.reason === 'not_found') {
        return reply.code(404).send({
          message: '삭제할 요청을 찾지 못했습니다.',
        });
      }

      if (result.reason === 'answered') {
        return reply.code(409).send({
          message: '이미 답변이 연결된 요청은 삭제할 수 없습니다.',
        });
      }

      return reply.code(409).send({
        message: '현재 처리 중인 요청은 삭제할 수 없습니다.',
      });
    }

    return {
      ok: true,
      deleted: true,
      sessionId: params.sessionId,
      requestId: params.requestId,
    };
  });

  app.post('/api/chat/conversations/:sessionId/requests/:requestId/manual-completion', async (request, reply) => {
    const params = z.object({
      sessionId: z.string().trim().min(1).max(120),
      requestId: z.string().trim().min(1).max(120),
    }).parse(request.params ?? {});
    const payload = z.object({
      type: z.enum(['prompt', 'verification']),
    }).parse(request.body ?? {});

    let item = null;

    try {
      item = await markChatConversationRequestManualCompletion(
        params.sessionId,
        params.requestId,
        payload.type,
      );
    } catch (error) {
      if (error instanceof ChatConversationManualCompletionBlockedError) {
        return reply.code(409).send({
          message: error.message,
        });
      }

      throw error;
    }

    if (!item) {
      return reply.code(404).send({
        message: '완료 처리할 요청을 찾지 못했습니다.',
      });
    }

    getActiveChatService()?.broadcastRequestUpdate(params.sessionId, item);

    return {
      ok: true,
      item,
    };
  });

  app.post('/api/chat/conversations/:sessionId/requests/:requestId/prompt-selection', async (request, reply) => {
    const params = z.object({
      sessionId: z.string().trim().min(1).max(120),
      requestId: z.string().trim().min(1).max(120),
    }).parse(request.params ?? {});
    const payload = z.object({
      parentRequestId: z.string().trim().min(1).max(120),
      promptIndex: z.number().int().min(0).max(99),
      promptTitle: z.string().trim().min(1).max(500),
      promptSignature: z.string().trim().min(1).max(8000),
      sourceMessageId: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
      selectedValues: z.array(z.string().trim().min(1).max(500)).max(50),
      freeText: z.string().max(10000).optional().nullable(),
      stepSelections: z.array(
        z.object({
          stepKey: z.string().trim().min(1).max(120),
          stepTitle: z.string().trim().max(500).optional().nullable(),
          selectedValues: z.array(z.string().trim().min(1).max(500)).max(50),
          freeText: z.string().max(10000),
          skipped: z.boolean().optional(),
        }),
      ).max(20).optional(),
      summaryText: z.string().max(10000).optional().nullable(),
      attachments: z.array(chatComposerAttachmentSchema).max(20).optional(),
    }).parse(request.body ?? {});

    if (params.requestId !== payload.parentRequestId) {
      return reply.code(400).send({
        message: 'prompt 선택 저장 대상 요청이 올바르지 않습니다.',
      });
    }

    const persisted = await persistChatConversationPromptSelection(
      params.sessionId,
      params.requestId,
      payload,
    );

    if (!persisted) {
      return reply.code(404).send({
        message: 'prompt 선택 상태를 저장할 응답 메시지를 찾지 못했습니다.',
      });
    }

    getActiveChatService()?.broadcastRequestUpdate(params.sessionId, persisted.request);
    getActiveChatService()?.broadcastMessageUpdate(params.sessionId, persisted.message);

    return {
      ok: true,
      item: persisted.request,
      message: persisted.message,
    };
  });

  app.post('/api/chat/conversations/:sessionId/requests/:requestId/prompt-selection/submit', async (request, reply) => {
    const params = z.object({
      sessionId: z.string().trim().min(1).max(120),
      requestId: z.string().trim().min(1).max(120),
    }).parse(request.params ?? {});
    const payload = z.object({
      parentRequestId: z.string().trim().min(1).max(120),
      promptIndex: z.number().int().min(0).max(99),
      promptTitle: z.string().trim().min(1).max(500),
      promptSignature: z.string().trim().min(1).max(8000),
      sourceMessageId: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
      selectedValues: z.array(z.string().trim().min(1).max(500)).max(50),
      freeText: z.string().max(10000).optional().nullable(),
      stepSelections: z.array(
        z.object({
          stepKey: z.string().trim().min(1).max(120),
          stepTitle: z.string().trim().max(500).optional().nullable(),
          selectedValues: z.array(z.string().trim().min(1).max(500)).max(50),
          freeText: z.string().max(10000),
          skipped: z.boolean().optional(),
        }),
      ).max(20).optional(),
      summaryText: z.string().max(10000).optional().nullable(),
      attachments: z.array(chatComposerAttachmentSchema).max(20).optional(),
      followupText: z.string().trim().min(1).max(20000),
      mode: z.enum(['queue', 'direct']).optional(),
      contextRef: chatPromptContextRefSchema,
    }).parse(request.body ?? {});

    if (params.requestId !== payload.parentRequestId) {
      return reply.code(400).send({
        message: 'prompt 선택 저장 대상 요청이 올바르지 않습니다.',
      });
    }

    const existingPromptRequest = await findExistingActivePromptFollowupRequest(
      params.sessionId,
      params.requestId,
      payload.followupText,
    );

    const queuedRequestId = existingPromptRequest?.requestId ?? await getActiveChatService()?.submitExternalMessage(params.sessionId, payload.followupText, {
      mode: resolvePromptFollowupMode(payload.mode),
      requestOrigin: 'prompt',
      parentRequestId: params.requestId,
      promptContextRef: payload.contextRef ?? null,
    });

    if (!queuedRequestId) {
      return reply.code(503).send({
        message: 'prompt 선택 후속 Codex 실행을 시작하지 못했습니다.',
      });
    }

    const persisted = await persistChatConversationPromptSelection(
      params.sessionId,
      params.requestId,
      payload,
    );

    if (!persisted) {
      return reply.code(404).send({
        message: '후속 Codex 실행은 접수됐지만 prompt 선택 상태를 저장할 응답 메시지를 찾지 못했습니다.',
      });
    }

    getActiveChatService()?.broadcastRequestUpdate(params.sessionId, persisted.request);
    getActiveChatService()?.broadcastMessageUpdate(params.sessionId, persisted.message);

    return {
      ok: true,
      item: persisted.request,
      message: persisted.message,
      queuedRequestId,
    };
  });

  app.patch('/api/chat/conversations/:sessionId', async (request, reply) => {
    const params = z.object({
      sessionId: z.string().trim().min(1).max(120),
    }).parse(request.params ?? {});
    const payload = z.object({
      title: z.string().trim().max(200).optional().nullable(),
      draftText: z.string().max(200000).optional().nullable(),
      requestBadgeLabel: z.string().trim().max(120).optional().nullable(),
      codexModel: z.string().trim().max(120).optional().nullable(),
      chatTypeId: z.string().trim().max(120).optional().nullable(),
      lastChatTypeId: z.string().trim().max(120).optional().nullable(),
      generalSectionName: z.string().trim().max(120).optional().nullable(),
      contextLabel: z.string().trim().max(200).optional().nullable(),
      contextDescription: z.string().trim().max(CHAT_CONTEXT_DESCRIPTION_MAX_LENGTH).optional().nullable(),
      notifyOffline: z.boolean().optional(),
    }).parse(request.body ?? {});

    const clientId = canViewAllConversations(request) ? null : getClientIdHeader(request);
    const current = await getChatConversation(params.sessionId, clientId || null);

    if (!current) {
      return reply.code(404).send({
        message: '수정할 채팅방을 찾을 수 없습니다.',
      });
    }

    const item = await updateChatConversationContext(params.sessionId, {
      title: Object.prototype.hasOwnProperty.call(payload, 'title') ? payload.title ?? null : undefined,
      draftText: Object.prototype.hasOwnProperty.call(payload, 'draftText') ? payload.draftText ?? '' : undefined,
      requestBadgeLabel:
        Object.prototype.hasOwnProperty.call(payload, 'requestBadgeLabel') ? payload.requestBadgeLabel ?? null : undefined,
      codexModel: Object.prototype.hasOwnProperty.call(payload, 'codexModel') ? payload.codexModel ?? null : undefined,
      clientId: current.clientId,
      chatTypeId: Object.prototype.hasOwnProperty.call(payload, 'chatTypeId') ? payload.chatTypeId ?? null : undefined,
      lastChatTypeId:
        Object.prototype.hasOwnProperty.call(payload, 'lastChatTypeId') ? payload.lastChatTypeId ?? null : undefined,
      generalSectionName:
        Object.prototype.hasOwnProperty.call(payload, 'generalSectionName')
          ? payload.generalSectionName ?? null
          : undefined,
      contextLabel:
        Object.prototype.hasOwnProperty.call(payload, 'contextLabel') ? payload.contextLabel ?? null : undefined,
      contextDescription:
        Object.prototype.hasOwnProperty.call(payload, 'contextDescription')
          ? payload.contextDescription ?? null
          : undefined,
      notifyOffline: Object.prototype.hasOwnProperty.call(payload, 'notifyOffline') ? payload.notifyOffline : undefined,
    });

    return {
      ok: true,
      item,
    };
  });

  app.delete('/api/chat/conversations/:sessionId', async (request, reply) => {
    const params = z.object({
      sessionId: z.string().trim().min(1).max(120),
    }).parse(request.params ?? {});

    const clientId = canViewAllConversations(request) ? null : getClientIdHeader(request);
    const current = await getChatConversation(params.sessionId, clientId || null);

    if (!current) {
      return reply.code(404).send({
        message: '삭제할 채팅방을 찾을 수 없습니다.',
      });
    }

    await getActiveChatService()?.forgetSession(params.sessionId);
    const deleted = await deleteChatConversation(params.sessionId);

    return {
      ok: true,
      deleted,
      sessionId: params.sessionId,
    };
  });

  app.post('/api/chat/conversations/:sessionId/clear', async (request, reply) => {
    const params = z.object({
      sessionId: z.string().trim().min(1).max(120),
    }).parse(request.params ?? {});

    const clientId = canViewAllConversations(request) ? null : getClientIdHeader(request);
    const current = await getChatConversation(params.sessionId, clientId || null);

    if (!current) {
      return reply.code(404).send({
        message: '초기화할 채팅방을 찾을 수 없습니다.',
      });
    }

    getActiveChatService()?.resetSessionData(params.sessionId);
    chatRuntimeService.clearSession(params.sessionId);
    const item = await clearChatConversationData(params.sessionId, clientId || null);

    if (!item) {
      return reply.code(404).send({
        message: '채팅방 데이터 초기화 후 다시 불러오지 못했습니다.',
      });
    }

    return {
      ok: true,
      item,
    };
  });
}
