import { useCallback, useRef } from 'react';
import { chatGateway } from '../data/chatGateway';
import type { ChatComposerAttachment, ChatConversationRequest, ChatMessage, ChatPromptContextRef } from '../../mainChatPanel/types';
import { buildComposerFilePickKey } from '../../mainChatPanel/composerFilePickKey';
import { shouldSkipContextConfirmForSessionToday } from '../../mainChatPanel/contextConfirmPreference';

export type ComposerFilePickResult = {
  items: {
    key: string;
    fileName: string;
    status: 'uploaded' | 'failed';
    reason?: string;
  }[];
};

type PendingChatRequest = {
  sessionId: string;
  requestId: string;
  text: string;
  mode: 'queue' | 'direct';
  origin?: 'composer' | 'prompt';
  parentRequestId?: string | null;
  promptContextRef?: ChatPromptContextRef | null;
  omitPromptHistory?: boolean;
  codexModel: string;
  chatTypeId: string;
  chatTypeLabel: string;
  chatTypeDescription: string;
  chatTypeBaseDescription?: string;
  defaultContextIds?: string[];
  defaultContexts?: Array<{
    id: string;
    title: string;
    content: string;
  }>;
  customContextTitle?: string | null;
  customContextContent?: string | null;
  retryCount: number;
  failed: boolean;
};

type PendingContextConfirm = {
  requestId?: string;
  sessionId: string;
  mode: 'queue' | 'direct';
  text: string;
  origin?: 'composer' | 'prompt';
  parentRequestId?: string | null;
  promptContextRef?: ChatPromptContextRef | null;
  codexModel: string;
  chatTypeId: string;
  chatTypeLabel: string;
  chatTypeDescription: string;
  chatTypeBaseDescription?: string;
  defaultContextIds?: string[];
  defaultContexts?: Array<{
    id: string;
    title: string;
    content: string;
  }>;
  customContextTitle?: string | null;
  customContextContent?: string | null;
  includedContextCount: number;
  omittedContextCount: number;
  omitPromptHistory?: boolean;
};

type SelectedChatType = {
  id: string;
  name: string;
  description: string;
  baseDescription?: string;
  defaultContextIds?: string[];
  defaultContexts?: Array<{
    id: string;
    title: string;
    content: string;
  }>;
  customContextTitle?: string | null;
  customContextContent?: string | null;
} | null;

type RecentContextSummary = {
  includedCount: number;
  omittedCount: number;
};

type UseConversationComposerControllerOptions = {
  activeSessionId: string;
  appConfigChat: {
    maxContextMessages: number;
    maxContextChars: number;
  };
  getDraft: () => string;
  composerAttachments: ChatComposerAttachment[];
  isComposerAttachmentUploading: boolean;
  selectedCodexModel: string;
  selectedChatType: SelectedChatType;
  socketRef: { current: WebSocket | null };
  composerRef: { current: { focus: (options?: { cursor?: 'start' | 'end' | 'all' }) => void } | null };
  messagesRef: { current: ChatMessage[] };
  pendingRequestsRef: { current: PendingChatRequest[] };
  promptRequestIdsRef?: { current: Set<string> };
  shouldStickToBottomRef: { current: boolean };
  setDraft: (value: string) => void;
  setComposerAttachments: React.Dispatch<React.SetStateAction<ChatComposerAttachment[]>>;
  setIsComposerAttachmentUploading: (value: boolean) => void;
  setMessages: React.Dispatch<React.SetStateAction<ChatMessage[]>>;
  setActiveSystemStatus: (value: string | null) => void;
  setIsSystemStatusPending: (value: boolean) => void;
  setShowScrollToBottom: (value: boolean) => void;
  setPendingContextConfirm: (value: PendingContextConfirm | null) => void;
  upsertRequestItem: (request: ChatConversationRequest) => void;
  syncConversationPreviewForRequest: (
    sessionId: string,
    text: string,
    requestedAt?: string,
    options?: {
      requestId?: string;
      requestOrigin?: 'composer' | 'prompt';
      mode?: 'queue' | 'direct';
      queueSize?: number;
      jobMessage?: string | null;
    },
  ) => void;
  updatePendingMessageStatus: (requestId: string, status: 'retrying' | 'failed' | null, retryCount?: number) => void;
  createLocalMessage: (text: string) => ChatMessage;
  createChatMessage: (author: 'user' | 'codex' | 'system', text: string, requestId?: string | null) => ChatMessage;
  createActivityLogPlaceholder: (requestId: string, lines: string[]) => ChatMessage | null;
  buildOutgoingMessageText: (text: string, attachments: ChatComposerAttachment[]) => string;
  summarizeRecentContext: (messages: ChatMessage[], maxMessages: number, maxChars: number) => RecentContextSummary;
  mergeComposerAttachments: (
    previous: ChatComposerAttachment[],
    next: ChatComposerAttachment[],
  ) => ChatComposerAttachment[];
  ensureSessionReady?: (sessionId: string) => Promise<boolean>;
  sendChatRequest: (socket: WebSocket, request: PendingChatRequest) => void;
  scrollViewportToBottom: () => void;
  releaseAutoScrollSuspension: () => void;
};

type SendMessageOptions = {
  sessionId?: string;
  mode: 'queue' | 'direct';
  draftText?: string;
};

export type SendMessageResult = 'sent' | 'pending' | 'blocked';
const COMPOSER_SUBMISSION_DEDUP_WINDOW_MS = 1200;

type RecentComposerSubmission = {
  key: string;
  submittedAt: number;
};

function createClientRequestId() {
  if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
    return `client-${crypto.randomUUID()}`;
  }

  return `client-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
}

export function useConversationComposerController({
  activeSessionId,
  appConfigChat,
  getDraft,
  composerAttachments,
  isComposerAttachmentUploading,
  selectedCodexModel,
  selectedChatType,
  socketRef,
  composerRef,
  messagesRef,
  pendingRequestsRef,
  promptRequestIdsRef,
  shouldStickToBottomRef,
  setDraft,
  setComposerAttachments,
  setIsComposerAttachmentUploading,
  setMessages,
  setActiveSystemStatus,
  setIsSystemStatusPending,
  setShowScrollToBottom,
  setPendingContextConfirm,
  upsertRequestItem,
  syncConversationPreviewForRequest,
  updatePendingMessageStatus,
  createLocalMessage,
  createChatMessage,
  createActivityLogPlaceholder,
  buildOutgoingMessageText,
  summarizeRecentContext,
  mergeComposerAttachments,
  ensureSessionReady,
  sendChatRequest,
  scrollViewportToBottom,
  releaseAutoScrollSuspension,
}: UseConversationComposerControllerOptions) {
  const composerUploadQueueRef = useRef(Promise.resolve<ComposerFilePickResult>({ items: [] }));
  const activeComposerUploadCountRef = useRef(0);
  const latestComposerUploadAttemptByKeyRef = useRef(new Map<string, string>());
  const activeComposerSubmissionKeyRef = useRef<string | null>(null);
  const recentComposerSubmissionRef = useRef<RecentComposerSubmission | null>(null);

  const isSocketOpen = useCallback(() => {
    return Boolean(socketRef.current && socketRef.current.readyState === WebSocket.OPEN);
  }, [socketRef]);

  const buildComposerSubmissionKey = useCallback(
    ({
      sessionId,
      mode,
      text,
      codexModel,
      chatTypeId,
      parentRequestId,
      omitPromptHistory,
    }: {
      sessionId: string;
      mode: 'queue' | 'direct';
      text: string;
      codexModel: string;
      chatTypeId: string;
      parentRequestId?: string | null;
      omitPromptHistory?: boolean;
    }) =>
      JSON.stringify({
        sessionId: sessionId.trim(),
        mode,
        text: text.trim(),
        codexModel: codexModel.trim(),
        chatTypeId: chatTypeId.trim(),
        parentRequestId: parentRequestId?.trim() || null,
        omitPromptHistory: omitPromptHistory === true,
      }),
    [],
  );

  const handleComposerFilesPicked = useCallback(
    async (files: File[]): Promise<ComposerFilePickResult> => {
      if (files.length === 0) {
        return { items: [] };
      }

      const batchAttemptId = `composer-upload-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
      const fileKeys = files.map((file) => buildComposerFilePickKey(file));

      fileKeys.forEach((key) => {
        latestComposerUploadAttemptByKeyRef.current.set(key, batchAttemptId);
      });

      const uploadBatch = async (): Promise<ComposerFilePickResult> => {
        activeComposerUploadCountRef.current += 1;

        if (activeComposerUploadCountRef.current === 1) {
          setIsComposerAttachmentUploading(true);
        }

        try {
          const uploadResults = await Promise.allSettled(
            files.map((file) => chatGateway.uploadComposerFile(activeSessionId, file)),
          );
          const uploadedItems: ChatComposerAttachment[] = [];
          const failedItems: Array<{ fileName: string; reason: string }> = [];

          uploadResults.forEach((result, index) => {
            const fileKey = fileKeys[index];

            if (!fileKey || latestComposerUploadAttemptByKeyRef.current.get(fileKey) !== batchAttemptId) {
              return;
            }

            if (result.status === 'fulfilled') {
              uploadedItems.push(result.value);
              return;
            }

            const fileName = files[index]?.name || `파일 ${index + 1}`;
            const reason =
              result.reason instanceof Error && result.reason.message.trim()
                ? result.reason.message.trim()
                : '업로드 실패';
            failedItems.push({ fileName, reason });
          });

          if (uploadedItems.length > 0) {
            setComposerAttachments((previous) => mergeComposerAttachments(previous, uploadedItems));
            shouldStickToBottomRef.current = true;
            setShowScrollToBottom(false);
          }

          if (failedItems.length > 0) {
            setMessages((previous) => [
              ...previous.slice(-39),
              createLocalMessage(
                ['파일 업로드에 실패했습니다:', ...failedItems.map((item) => `- ${item.fileName}: ${item.reason}`)].join('\n'),
              ),
            ]);
          }

          return {
            items: uploadResults.map((result, index) => ({
              key: buildComposerFilePickKey(files[index] as File),
              fileName: files[index]?.name || `파일 ${index + 1}`,
              status: result.status === 'fulfilled' ? 'uploaded' : 'failed',
              reason:
                result.status === 'fulfilled'
                  ? undefined
                  : result.reason instanceof Error && result.reason.message.trim()
                    ? result.reason.message.trim()
                    : '업로드 실패',
            })),
          };
        } finally {
          activeComposerUploadCountRef.current = Math.max(0, activeComposerUploadCountRef.current - 1);

          if (activeComposerUploadCountRef.current === 0) {
            setIsComposerAttachmentUploading(false);
          }
        }
      };

      const queuedUpload = composerUploadQueueRef.current.then(uploadBatch, uploadBatch);
      composerUploadQueueRef.current = queuedUpload.catch(() => ({ items: [] }));
      return queuedUpload;
    },
    [
      activeSessionId,
      composerUploadQueueRef,
      createLocalMessage,
      latestComposerUploadAttemptByKeyRef,
      mergeComposerAttachments,
      setComposerAttachments,
      setIsComposerAttachmentUploading,
      setMessages,
      setShowScrollToBottom,
      shouldStickToBottomRef,
    ],
  );

  const focusComposerAfterSend = useCallback(() => {
    window.setTimeout(() => {
      composerRef.current?.focus({ cursor: 'end' });
    }, 0);
  }, [composerRef]);

  const scheduleViewportBottomSyncAfterSend = useCallback(() => {
    releaseAutoScrollSuspension();
    shouldStickToBottomRef.current = true;
    setShowScrollToBottom(false);

    window.requestAnimationFrame(() => {
      scrollViewportToBottom();

      window.requestAnimationFrame(() => {
        scrollViewportToBottom();
      });
    });
  }, [releaseAutoScrollSuspension, scrollViewportToBottom, setShowScrollToBottom, shouldStickToBottomRef]);

  const handleExecuteSendError = useCallback(
    (error: unknown) => {
      const reason =
        error instanceof Error && error.message.trim()
          ? error.message.trim()
          : '요청 전송 중 오류가 발생했습니다.';
      setActiveSystemStatus(null);
      setIsSystemStatusPending(false);
      setMessages((previous) => [...previous.slice(-39), createLocalMessage(reason)]);
    },
    [createLocalMessage, setActiveSystemStatus, setIsSystemStatusPending, setMessages],
  );

  const executeSendMessage = useCallback(
    async (request: PendingContextConfirm) => {
      const {
        requestId: requestedRequestId,
        sessionId,
        mode,
        text,
        origin,
        parentRequestId,
        promptContextRef,
        chatTypeId,
        codexModel,
        chatTypeLabel,
        chatTypeDescription,
        chatTypeBaseDescription,
        defaultContextIds,
        defaultContexts,
        customContextTitle,
        customContextContent,
        omitPromptHistory,
      } = request;
      const requestChatTypeId = chatTypeId.trim();
      const requestChatTypeLabel = chatTypeLabel.trim() || requestChatTypeId || '기본 요청';
      const targetSessionId = sessionId.trim() || activeSessionId.trim();
      const submissionKey = buildComposerSubmissionKey({
        mode,
        text,
        codexModel,
        chatTypeId: requestChatTypeId,
        parentRequestId,
        omitPromptHistory,
        sessionId: targetSessionId,
      });

      if (origin !== 'prompt' && activeComposerSubmissionKeyRef.current === submissionKey) {
        return false;
      }

      if (origin !== 'prompt') {
        const recentComposerSubmission = recentComposerSubmissionRef.current;
        if (
          recentComposerSubmission &&
          recentComposerSubmission.key === submissionKey &&
          Date.now() - recentComposerSubmission.submittedAt < COMPOSER_SUBMISSION_DEDUP_WINDOW_MS
        ) {
          return false;
        }
      }

      if (origin !== 'prompt') {
        activeComposerSubmissionKeyRef.current = submissionKey;
        recentComposerSubmissionRef.current = {
          key: submissionKey,
          submittedAt: Date.now(),
        };
      }

      const shouldOptimisticallyClearComposer = origin !== 'prompt';
      const previousDraft = shouldOptimisticallyClearComposer ? getDraft() : '';
      const previousAttachments = shouldOptimisticallyClearComposer ? composerAttachments : [];
      let composerRestoreNeeded = shouldOptimisticallyClearComposer;

      const restoreComposerOnFailure = () => {
        if (!composerRestoreNeeded) {
          return;
        }

        composerRestoreNeeded = false;

        if (!getDraft().trim()) {
          setDraft(previousDraft);
        }

        setComposerAttachments((current) => {
          if (current.length > 0 || previousAttachments.length === 0) {
            return current;
          }

          return previousAttachments;
        });
      };

      try {
        if (shouldOptimisticallyClearComposer) {
          setDraft('');
          setComposerAttachments([]);
          focusComposerAfterSend();
        }

        if (!targetSessionId) {
          restoreComposerOnFailure();
          setActiveSystemStatus(null);
          setIsSystemStatusPending(false);
          return false;
        }

        if (ensureSessionReady) {
          setActiveSystemStatus('새 채팅방 준비 중...');
          setIsSystemStatusPending(true);
          const isSessionReady = await ensureSessionReady(targetSessionId);

          if (!isSessionReady) {
            restoreComposerOnFailure();
            setActiveSystemStatus(null);
            setIsSystemStatusPending(false);
            return false;
          }
        }

        if (!isSocketOpen()) {
          restoreComposerOnFailure();
          setActiveSystemStatus(null);
          setIsSystemStatusPending(false);
          setMessages((previous) => [
            ...previous.slice(-39),
            createLocalMessage('워크서버 연결이 아직 준비되지 않았습니다. 연결이 복구된 뒤 다시 전송해 주세요.'),
          ]);
          return false;
        }
 
        const requestId = requestedRequestId?.trim() || createClientRequestId();
        const outgoingRequest: PendingChatRequest = {
          sessionId: targetSessionId,
          requestId,
          text,
          mode,
          origin: origin ?? 'composer',
          parentRequestId: parentRequestId?.trim() || null,
          promptContextRef: promptContextRef ?? null,
          omitPromptHistory: omitPromptHistory === true,
          codexModel,
          chatTypeId: requestChatTypeId,
          chatTypeLabel: requestChatTypeLabel,
          chatTypeDescription,
          chatTypeBaseDescription,
          defaultContextIds,
          defaultContexts,
          customContextTitle,
          customContextContent,
          retryCount: 0,
          failed: false,
        };

        if (origin === 'prompt') {
          promptRequestIdsRef?.current.add(requestId);
        }

        if (mode === 'queue') {
          const queuedAt = new Date().toISOString();
          const optimisticUserMessage: ChatMessage = {
            ...createChatMessage('user', text, requestId),
            deliveryStatus: null,
            retryCount: 0,
          };
          const optimisticActivityMessage = createActivityLogPlaceholder(requestId, [
            '# 상태: 요청을 접수했습니다.',
            '# 진행: 순서를 기다리기 전에 요청 내용을 정리하고 있습니다.',
          ]);
          upsertRequestItem({
            sessionId: targetSessionId,
            requestId,
            chatTypeId: requestChatTypeId,
            chatTypeLabel: requestChatTypeLabel,
            requestOrigin: origin ?? 'composer',
            parentRequestId: parentRequestId?.trim() || null,
            promptContextRef: promptContextRef ?? null,
            status: 'queued',
            statusMessage: '대기열 등록',
            userMessageId: optimisticUserMessage.id,
            userText: text,
            responseMessageId: null,
            responseText: '',
            hasResponse: false,
            canDelete: false,
            createdAt: queuedAt,
            updatedAt: queuedAt,
            answeredAt: null,
            terminalAt: null,
          });
          syncConversationPreviewForRequest(targetSessionId, text, queuedAt, {
            requestId,
            requestOrigin: origin ?? 'composer',
            mode: 'queue',
            queueSize: 1,
            jobMessage: '대기열 등록 중',
          });

          shouldStickToBottomRef.current = true;
          setShowScrollToBottom(false);
          setMessages((previous) => {
            const nextMessages = [...previous, optimisticUserMessage];
            return optimisticActivityMessage ? [...nextMessages, optimisticActivityMessage] : nextMessages;
          });
          scheduleViewportBottomSyncAfterSend();
          setActiveSystemStatus('대기열 등록 중...');
          setIsSystemStatusPending(true);
        } else {
          const optimisticUserMessage: ChatMessage = {
            ...createChatMessage('user', text, requestId),
            deliveryStatus: null,
            retryCount: 0,
          };
          const optimisticActivityMessage = createActivityLogPlaceholder(requestId, [
            '# 상태: 즉시 요청을 접수했습니다.',
            '# 진행: 요청 내용을 정리한 뒤 답변을 준비합니다.',
          ]);
          upsertRequestItem({
            sessionId: targetSessionId,
            requestId,
            chatTypeId: requestChatTypeId,
            chatTypeLabel: requestChatTypeLabel,
            requestOrigin: origin ?? 'composer',
            parentRequestId: parentRequestId?.trim() || null,
            promptContextRef: promptContextRef ?? null,
            status: 'accepted',
            statusMessage: '요청을 접수했습니다.',
            userMessageId: optimisticUserMessage.id,
            userText: text,
            responseMessageId: null,
            responseText: '',
            hasResponse: false,
            canDelete: true,
            createdAt: new Date().toISOString(),
            updatedAt: new Date().toISOString(),
            answeredAt: null,
            terminalAt: null,
          });
          syncConversationPreviewForRequest(targetSessionId, text, new Date().toISOString(), {
            requestId,
            requestOrigin: origin ?? 'composer',
            mode: 'direct',
            queueSize: 0,
            jobMessage: '즉시 요청 실행 대기 중',
          });

          shouldStickToBottomRef.current = true;
          setShowScrollToBottom(false);
          setMessages((previous) => {
            const nextMessages = [...previous, optimisticUserMessage];
            return optimisticActivityMessage ? [...nextMessages, optimisticActivityMessage] : nextMessages;
          });
          scheduleViewportBottomSyncAfterSend();
          setActiveSystemStatus('즉시 응답 준비 중...');
          setIsSystemStatusPending(true);
        }

        if (!socketRef.current || socketRef.current.readyState !== WebSocket.OPEN) {
          setActiveSystemStatus('전송 재시도 중...');
          pendingRequestsRef.current = [
            ...pendingRequestsRef.current.filter((pending) => pending.requestId !== requestId),
            outgoingRequest,
          ];
          if (mode === 'direct') {
            updatePendingMessageStatus(requestId, 'retrying', 0);
          }
          composerRestoreNeeded = false;
          return true;
        }

        try {
          sendChatRequest(socketRef.current, outgoingRequest);
        } catch {
          setActiveSystemStatus('전송 재시도 중...');
          pendingRequestsRef.current = [
            ...pendingRequestsRef.current.filter((pending) => pending.requestId !== requestId),
            outgoingRequest,
          ];
          if (mode === 'direct') {
            updatePendingMessageStatus(requestId, 'retrying', 0);
          }
        }

        composerRestoreNeeded = false;
        return true;
      } catch (error) {
        restoreComposerOnFailure();
        throw error;
      } finally {
        if (origin !== 'prompt' && activeComposerSubmissionKeyRef.current === submissionKey) {
          activeComposerSubmissionKeyRef.current = null;
        }
      }
    },
    [
      activeSessionId,
      buildComposerSubmissionKey,
      composerAttachments,
      createActivityLogPlaceholder,
      createChatMessage,
      createLocalMessage,
      ensureSessionReady,
      focusComposerAfterSend,
      getDraft,
      isSocketOpen,
      pendingRequestsRef,
      promptRequestIdsRef,
      scheduleViewportBottomSyncAfterSend,
      sendChatRequest,
      setActiveSystemStatus,
      setComposerAttachments,
      setDraft,
      setIsSystemStatusPending,
      setMessages,
      setShowScrollToBottom,
      shouldStickToBottomRef,
      socketRef,
      syncConversationPreviewForRequest,
      updatePendingMessageStatus,
      upsertRequestItem,
    ],
  );

  const sendMessage = useCallback(
    ({ sessionId, mode, draftText }: SendMessageOptions): SendMessageResult => {
      if (isComposerAttachmentUploading) {
        return 'blocked';
      }

      const trimmed = buildOutgoingMessageText(draftText ?? getDraft(), composerAttachments).trim();

      if (!trimmed) {
        return 'blocked';
      }

      if (!selectedChatType) {
        setMessages((previous) => [
          ...previous.slice(-39),
          createLocalMessage('현재 사용자에게 허용된 컨텍스트가 없습니다. 관리 페이지에서 컨텍스트 권한을 확인하세요.'),
        ]);
        return 'blocked';
      }

      if (!isSocketOpen()) {
        setMessages((previous) => [
          ...previous.slice(-39),
          createLocalMessage('워크서버 연결이 아직 준비되지 않았습니다. 연결이 복구된 뒤 다시 전송해 주세요.'),
        ]);
        return 'blocked';
      }

      const recentContext = summarizeRecentContext(
        messagesRef.current,
        appConfigChat.maxContextMessages,
        appConfigChat.maxContextChars,
      );

      if (recentContext.omittedCount > 0) {
        const targetSessionId = sessionId?.trim() || activeSessionId.trim();
        const nextRequest = {
          sessionId: targetSessionId,
          mode,
          text: trimmed,
          codexModel: selectedCodexModel,
          chatTypeId: selectedChatType.id,
          chatTypeLabel: selectedChatType.name,
          chatTypeDescription: selectedChatType.description,
          chatTypeBaseDescription: selectedChatType.baseDescription,
          defaultContextIds: selectedChatType.defaultContextIds,
          defaultContexts: selectedChatType.defaultContexts,
          customContextTitle: selectedChatType.customContextTitle,
          customContextContent: selectedChatType.customContextContent,
          includedContextCount: recentContext.includedCount,
          omittedContextCount: recentContext.omittedCount,
        } satisfies PendingContextConfirm;

        if (shouldSkipContextConfirmForSessionToday(targetSessionId)) {
          void executeSendMessage(nextRequest).catch(handleExecuteSendError);
          return 'sent';
        }

        setPendingContextConfirm({
          sessionId: targetSessionId,
          mode,
          text: trimmed,
          codexModel: selectedCodexModel,
          chatTypeId: selectedChatType.id,
          chatTypeLabel: selectedChatType.name,
          chatTypeDescription: selectedChatType.description,
          chatTypeBaseDescription: selectedChatType.baseDescription,
          defaultContextIds: selectedChatType.defaultContextIds,
          defaultContexts: selectedChatType.defaultContexts,
          customContextTitle: selectedChatType.customContextTitle,
          customContextContent: selectedChatType.customContextContent,
          includedContextCount: recentContext.includedCount,
          omittedContextCount: recentContext.omittedCount,
        });
        return 'pending';
      }

      void executeSendMessage({
        sessionId: sessionId?.trim() || activeSessionId.trim(),
        mode,
        text: trimmed,
        codexModel: selectedCodexModel,
        chatTypeId: selectedChatType.id,
        chatTypeLabel: selectedChatType.name,
        chatTypeDescription: selectedChatType.description,
        chatTypeBaseDescription: selectedChatType.baseDescription,
        defaultContextIds: selectedChatType.defaultContextIds,
        defaultContexts: selectedChatType.defaultContexts,
        customContextTitle: selectedChatType.customContextTitle,
        customContextContent: selectedChatType.customContextContent,
        includedContextCount: 0,
        omittedContextCount: 0,
      }).catch(handleExecuteSendError);
      return 'sent';
    },
    [
      appConfigChat.maxContextChars,
      appConfigChat.maxContextMessages,
      buildOutgoingMessageText,
      composerAttachments,
      createLocalMessage,
      getDraft,
      handleExecuteSendError,
      executeSendMessage,
      isSocketOpen,
      isComposerAttachmentUploading,
      selectedCodexModel,
      activeSessionId,
      messagesRef,
      selectedChatType,
      setMessages,
      setPendingContextConfirm,
      summarizeRecentContext,
    ],
  );

  const handleSend = useCallback(() => {
    sendMessage({ mode: 'queue' });
  }, [sendMessage]);

  const handleSendImmediate = useCallback(() => {
    sendMessage({ mode: 'direct' });
  }, [sendMessage]);

  return {
    executeSendMessage,
    handleComposerFilesPicked,
    handleSend,
    handleSendImmediate,
    sendMessage,
  };
}
