/*
 * Copyright (C) 2026 Fluxer Contributors
 *
 * This file is part of Fluxer.
 *
 * Fluxer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Fluxer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
 */

import {useLingui} from '@lingui/react/macro';

import {PlusCircleIcon} from '@phosphor-icons/react';

import {clsx} from 'clsx';
import {observer} from 'mobx-react-lite';
import React from 'react';

import * as ContextMenuActionCreators from '~/actions/ContextMenuActionCreators';
import * as DraftActionCreators from '~/actions/DraftActionCreators';
import * as MessageActionCreators from '~/actions/MessageActionCreators';
import * as ModalActionCreators from '~/actions/ModalActionCreators';
import {modal} from '~/actions/ModalActionCreators';
import * as PopoutActionCreators from '~/actions/PopoutActionCreators';
import * as ScheduledMessageActionCreators from '~/actions/ScheduledMessageActionCreators';

import {MAX_MESSAGE_LENGTH_NON_PREMIUM, Permissions} from '~/Constants';

import {TooManyAttachmentsModal} from '~/components/alerts/TooManyAttachmentsModal';
import {Autocomplete} from '~/components/channel/Autocomplete';
import {ChannelAttachmentArea} from '~/components/channel/ChannelAttachmentArea';
import {ChannelStickersArea} from '~/components/channel/ChannelStickersArea';
import {EditBar} from '~/components/channel/EditBar';
import {
	getMentionDescription,
	getMentionTitle,
	MentionEveryonePopout,
} from '~/components/channel/MentionEveryonePopout';
import {MessageCharacterCounter} from '~/components/channel/MessageCharacterCounter';
import {ReplyBar} from '~/components/channel/ReplyBar';
import {ScheduledMessageEditBar} from '~/components/channel/ScheduledMessageEditBar';
import {MessageInputButtonsContextMenu} from '~/components/channel/textarea/MessageInputButtonsContextMenu';
import {TextareaButton} from '~/components/channel/textarea/TextareaButton';
import {TextareaButtons} from '~/components/channel/textarea/TextareaButtons';
import {TextareaInputField} from '~/components/channel/textarea/TextareaInputField';
import {ConfirmModal} from '~/components/modals/ConfirmModal';
import {ExpressionPickerSheet} from '~/components/modals/ExpressionPickerSheet';
import {ScheduleMessageModal} from '~/components/modals/ScheduleMessageModal';
import FocusRing from '~/components/uikit/FocusRing/FocusRing';
import {openPopout} from '~/components/uikit/Popout/Popout';
import {Scroller, type ScrollerHandle} from '~/components/uikit/Scroller';

import {useTextareaAttachments} from '~/hooks/useCloudUpload';
import {doesEventMatchShortcut, MARKDOWN_FORMATTING_SHORTCUTS, useMarkdownKeybinds} from '~/hooks/useMarkdownKeybinds';
import {useMessageSubmission} from '~/hooks/useMessageSubmission';
import {useSlowmode} from '~/hooks/useSlowmode';
import {useTextareaAutocomplete} from '~/hooks/useTextareaAutocomplete';
import {useTextareaDraftAndTyping} from '~/hooks/useTextareaDraftAndTyping';
import {useTextareaEditing} from '~/hooks/useTextareaEditing';
import {useTextareaEmojiPicker} from '~/hooks/useTextareaEmojiPicker';
import {useTextareaExpressionHandlers} from '~/hooks/useTextareaExpressionHandlers';
import {useTextareaExpressionPicker} from '~/hooks/useTextareaExpressionPicker';
import {useTextareaKeyboard} from '~/hooks/useTextareaKeyboard';
import {useTextareaPaste} from '~/hooks/useTextareaPaste';
import {useTextareaSegments} from '~/hooks/useTextareaSegments';
import {type MentionConfirmationInfo, useTextareaSubmit} from '~/hooks/useTextareaSubmit';

import {CloudUpload} from '~/lib/CloudUpload';
import {ComponentDispatch} from '~/lib/ComponentDispatch';
import {safeFocus} from '~/lib/InputFocusManager';

import type {ChannelRecord} from '~/records/ChannelRecord';

import AccessibilityStore from '~/stores/AccessibilityStore';
import ChannelStickerStore from '~/stores/ChannelStickerStore';
import DeveloperOptionsStore from '~/stores/DeveloperOptionsStore';
import DraftStore from '~/stores/DraftStore';
import FeatureFlagStore from '~/stores/FeatureFlagStore';
import KeyboardModeStore from '~/stores/KeyboardModeStore';
import MessageEditMobileStore from '~/stores/MessageEditMobileStore';
import MessageEditStore from '~/stores/MessageEditStore';
import MessageReplyStore from '~/stores/MessageReplyStore';
import MessageStore from '~/stores/MessageStore';
import MobileLayoutStore from '~/stores/MobileLayoutStore';
import PermissionStore from '~/stores/PermissionStore';
import ScheduledMessageEditorStore from '~/stores/ScheduledMessageEditorStore';
import SelectedGuildStore from '~/stores/SelectedGuildStore';
import UserStore from '~/stores/UserStore';

import * as ChannelUtils from '~/utils/ChannelUtils';
import {openFilePicker} from '~/utils/FilePickerUtils';
import * as FileUploadUtils from '~/utils/FileUploadUtils';
import {normalizeMessageContent} from '~/utils/MessageRequestUtils';
import * as MessageSubmitUtils from '~/utils/MessageSubmitUtils';
import * as PlaceholderUtils from '~/utils/PlaceholderUtils';

import wrapperStyles from './textarea/InputWrapper.module.css';
import styles from './textarea/TextareaInput.module.css';

function readBorderBoxBlockSize(entry: ResizeObserverEntry): number {
	const bbs: any = (entry as any).borderBoxSize;
	if (Array.isArray(bbs) && bbs[0] && typeof bbs[0].blockSize === 'number') return bbs[0].blockSize;
	if (bbs && typeof bbs.blockSize === 'number') return bbs.blockSize;
	return (entry.target as HTMLElement).getBoundingClientRect().height;
}

const ChannelTextareaContent = observer(
	({
		channel,
		draft,
		disabled,
		canAttachFiles,
	}: {
		channel: ChannelRecord;
		draft: string | null;
		disabled: boolean;
		canAttachFiles: boolean;
	}) => {
		const {t, i18n} = useLingui();
		const [isFocused, setIsFocused] = React.useState(false);
		const [isInputAreaFocused, setIsInputAreaFocused] = React.useState(false);
		const [value, setValue] = React.useState('');
		const [showAllButtons, setShowAllButtons] = React.useState(true);
		const [containerWidth, setContainerWidth] = React.useState(0);
		const [pendingMentionConfirmation, setPendingMentionConfirmation] = React.useState<MentionConfirmationInfo | null>(
			null,
		);
		const mentionPopoutKey = React.useMemo(() => `mention-everyone-${channel.id}`, [channel.id]);
		const mentionModalKey = React.useMemo(() => `mention-everyone-modal-${channel.id}`, [channel.id]);
		const [isScheduleModalOpen, setIsScheduleModalOpen] = React.useState(false);

		const textareaRef = React.useRef<HTMLTextAreaElement>(null);
		const expressionPickerTriggerRef = React.useRef<HTMLButtonElement>(null);
		const invisibleExpressionPickerTriggerRef = React.useRef<HTMLDivElement>(null);
		const containerRef = React.useRef<HTMLDivElement>(null);
		const scrollerRef = React.useRef<ScrollerHandle>(null);
		useMarkdownKeybinds(isFocused);

		const textareaHeightRef = React.useRef<number>(0);
		const handleTextareaHeightChange = React.useCallback((height: number) => {
			textareaHeightRef.current = height;
		}, []);

		const inputBoxHeightRef = React.useRef<number | null>(null);
		const pendingLayoutDeltaRef = React.useRef(0);
		const flushScheduledRef = React.useRef(false);

		React.useLayoutEffect(() => {
			const el = containerRef.current;
			if (!el || typeof ResizeObserver === 'undefined') return;

			inputBoxHeightRef.current = null;
			pendingLayoutDeltaRef.current = 0;
			flushScheduledRef.current = false;

			const flush = () => {
				flushScheduledRef.current = false;
				const delta = pendingLayoutDeltaRef.current;
				pendingLayoutDeltaRef.current = 0;
				if (!delta) return;
				if (delta <= 0) return;

				ComponentDispatch.dispatch('LAYOUT_RESIZED', {
					channelId: channel.id,
					heightDelta: delta,
				});
			};

			const ro = new ResizeObserver((entries) => {
				const entry = entries[0];
				if (!entry) return;

				const nextHeight = Math.round(readBorderBoxBlockSize(entry));
				const prevHeight = inputBoxHeightRef.current;

				if (prevHeight == null) {
					inputBoxHeightRef.current = nextHeight;
					return;
				}

				const delta = nextHeight - prevHeight;
				if (!delta) return;

				inputBoxHeightRef.current = nextHeight;
				pendingLayoutDeltaRef.current += delta;

				if (!flushScheduledRef.current) {
					flushScheduledRef.current = true;
					queueMicrotask(flush);
				}
			});

			ro.observe(el);
			return () => ro.disconnect();
		}, [channel.id]);

		const showGiftButton = AccessibilityStore.showGiftButton;
		const showGifButton = AccessibilityStore.showGifButton;
		const showMemesButton = AccessibilityStore.showMemesButton;
		const showStickersButton = AccessibilityStore.showStickersButton;
		const showEmojiButton = AccessibilityStore.showEmojiButton;
		const showUploadButton = AccessibilityStore.showUploadButton;
		const showMessageSendButton = AccessibilityStore.showMessageSendButton;
		const editingMessageId = MessageEditStore.getEditingMessageId(channel.id);
		const editingMobileMessageId = MessageEditMobileStore.getEditingMobileMessageId(channel.id);
		const mobileLayout = MobileLayoutStore;
		const replyingMessage = MessageReplyStore.getReplyingMessage(channel.id);
		const referencedMessage = replyingMessage ? MessageStore.getMessage(channel.id, replyingMessage.messageId) : null;
		const editingMessage = editingMobileMessageId ? MessageStore.getMessage(channel.id, editingMobileMessageId) : null;
		const currentUser = UserStore.getCurrentUser();
		const maxMessageLength = currentUser?.maxMessageLength ?? MAX_MESSAGE_LENGTH_NON_PREMIUM;

		const uploadAttachments = useTextareaAttachments(channel.id);
		const {isSlowmodeActive} = useSlowmode(channel);
		const {segmentManagerRef, previousValueRef, displayToActual, insertSegment, handleTextChange, clearSegments} =
			useTextareaSegments();
		const {handleEmojiSelect} = useTextareaEmojiPicker({
			setValue,
			textareaRef,
			insertSegment,
			previousValueRef,
			channelId: channel.id,
		});
		const scheduledMessageEditorState = ScheduledMessageEditorStore.getEditingState();
		const isEditingScheduledMessage = ScheduledMessageEditorStore.isEditingChannel(channel.id);
		const editingScheduledMessage = isEditingScheduledMessage ? scheduledMessageEditorState : null;
		const selectedGuildId = SelectedGuildStore.selectedGuildId;
		const hasMessageSchedulingAccess = FeatureFlagStore.isMessageSchedulingEnabled(selectedGuildId ?? undefined);

		const {sendMessage, sendOptimisticMessage} = useMessageSubmission({
			channel,
			referencedMessage: referencedMessage ?? null,
			replyingMessage,
			clearSegments,
		});

		const handleCancelScheduledEdit = React.useCallback(() => {
			ScheduledMessageEditorStore.stopEditing();
			DraftActionCreators.deleteDraft(channel.id);
			setValue('');
			clearSegments();
		}, [channel.id, clearSegments, setValue]);

		const handleSendMessage = React.useCallback(
			(...args: Parameters<typeof sendMessage>) => {
				setValue('');
				clearSegments();
				sendMessage(...args);
			},
			[sendMessage, clearSegments],
		);

		const handleMentionConfirmationNeeded = React.useCallback((info: MentionConfirmationInfo) => {
			setPendingMentionConfirmation(info);
		}, []);

		const handleMentionConfirm = React.useCallback(() => {
			if (pendingMentionConfirmation) {
				handleSendMessage(pendingMentionConfirmation.content, false, pendingMentionConfirmation.tts);
				setPendingMentionConfirmation(null);
			}
		}, [pendingMentionConfirmation, handleSendMessage]);

		const handleMentionCancel = React.useCallback(() => {
			setPendingMentionConfirmation(null);
			textareaRef.current?.focus();
		}, []);

		React.useEffect(() => {
			if (!pendingMentionConfirmation) {
				PopoutActionCreators.close(mentionPopoutKey);
				ModalActionCreators.popWithKey(mentionModalKey);
				return;
			}

			if (mobileLayout.enabled) {
				const index = pendingMentionConfirmation.mentionType;
				const title = getMentionTitle(index, pendingMentionConfirmation.roleName);
				const description = getMentionDescription(
					index,
					pendingMentionConfirmation.memberCount,
					pendingMentionConfirmation.roleName,
				);

				ModalActionCreators.pushWithKey(
					modal(() => (
						<ConfirmModal
							title={title}
							description={description}
							primaryText={t`Continue`}
							secondaryText={t`Cancel`}
							onPrimary={() => {
								handleMentionConfirm();
							}}
							onSecondary={() => {
								handleMentionCancel();
							}}
						/>
					)),
					mentionModalKey,
				);

				return () => {
					ModalActionCreators.popWithKey(mentionModalKey);
				};
			}

			const containerElement = containerRef.current;
			if (!containerElement) {
				return;
			}

			openPopout(
				containerElement,
				{
					render: ({onClose}) => (
						<MentionEveryonePopout
							mentionType={pendingMentionConfirmation.mentionType}
							memberCount={pendingMentionConfirmation.memberCount}
							roleName={pendingMentionConfirmation.roleName}
							onConfirm={() => {
								handleMentionConfirm();
								onClose();
							}}
							onCancel={() => {
								handleMentionCancel();
								onClose();
							}}
						/>
					),
					position: 'top-start',
					offsetMainAxis: 8,
					shouldAutoUpdate: true,
					returnFocusRef: textareaRef,
					onCloseRequest: () => {
						handleMentionCancel();
						return true;
					},
				},
				mentionPopoutKey,
			);

			return () => {
				PopoutActionCreators.close(mentionPopoutKey);
			};
		}, [
			pendingMentionConfirmation,
			mentionPopoutKey,
			mentionModalKey,
			handleMentionConfirm,
			handleMentionCancel,
			textareaRef,
			mobileLayout.enabled,
		]);

		const {
			autocompleteQuery,
			autocompleteOptions,
			autocompleteType,
			selectedIndex,
			isAutocompleteAttached,
			setSelectedIndex,
			onCursorMove,
			handleSelect,
		} = useTextareaAutocomplete({
			channel,
			value,
			setValue,
			textareaRef,
			segmentManagerRef,
			previousValueRef,
		});

		React.useEffect(() => {
			ComponentDispatch.safeDispatch('TEXTAREA_AUTOCOMPLETE_CHANGED', {
				channelId: channel.id,
				open: isAutocompleteAttached,
			});
		}, [channel.id, isAutocompleteAttached]);

		const trimmedMessageContent = displayToActual(value).trim();
		const hasScheduleContent = trimmedMessageContent.length > 0 || uploadAttachments.length > 0;
		const canScheduleMessage = hasMessageSchedulingAccess && !disabled && hasScheduleContent;

		useTextareaPaste({
			channel,
			textareaRef,
			segmentManagerRef,
			setValue,
			previousValueRef,
		});

		const handleOpenScheduleModal = React.useCallback(() => {
			if (!hasMessageSchedulingAccess) {
				return;
			}
			setIsScheduleModalOpen(true);
		}, [hasMessageSchedulingAccess]);

		const handleScheduleSubmit = React.useCallback(
			async (scheduledLocalAt: string, timezone: string) => {
				const actualContent = displayToActual(value).trim();
				if (!actualContent && uploadAttachments.length === 0) {
					return;
				}

				const normalized = normalizeMessageContent(actualContent, undefined);

				if (editingScheduledMessage) {
					await ScheduledMessageActionCreators.updateScheduledMessage(i18n, {
						channelId: channel.id,
						scheduledMessageId: editingScheduledMessage.scheduledMessageId,
						scheduledLocalAt,
						timezone,
						normalized,
						payload: editingScheduledMessage.payload,
						replyMentioning: replyingMessage?.mentioning,
					});
					ScheduledMessageEditorStore.stopEditing();
				} else {
					await ScheduledMessageActionCreators.scheduleMessage(i18n, {
						channelId: channel.id,
						content: actualContent,
						scheduledLocalAt,
						timezone,
						messageReference: MessageSubmitUtils.prepareMessageReference(channel.id, referencedMessage),
						replyMentioning: replyingMessage?.mentioning,
						favoriteMemeId: undefined,
						stickers: undefined,
						tts: false,
						hasAttachments: uploadAttachments.length > 0,
					});
				}

				setValue('');
				clearSegments();
				setIsScheduleModalOpen(false);
			},
			[
				channel.id,
				clearSegments,
				displayToActual,
				editingScheduledMessage,
				referencedMessage,
				replyingMessage?.mentioning,
				setIsScheduleModalOpen,
				setValue,
				uploadAttachments.length,
				value,
			],
		);

		const handleFileButtonClick = async () => {
			const files = await openFilePicker({multiple: true});
			const result = await FileUploadUtils.handleFileUpload(channel.id, files, uploadAttachments.length);

			if (!result.success && result.error === 'too_many_attachments') {
				ModalActionCreators.push(modal(() => <TooManyAttachmentsModal />));
			}
		};

		useTextareaExpressionHandlers({
			setValue,
			textareaRef,
			insertSegment,
			previousValueRef,
			sendOptimisticMessage,
		});

		const {expressionPickerOpen, setExpressionPickerOpen, handleExpressionPickerTabToggle, selectedTab} =
			useTextareaExpressionPicker({
				channelId: channel.id,
				onEmojiSelect: handleEmojiSelect,
				expressionPickerTriggerRef,
				invisibleExpressionPickerTriggerRef,
				textareaRef,
			});

		useTextareaEditing({
			channelId: channel.id,
			editingMessageId: editingMessageId ?? null,
			editingMessage: editingMessage ?? null,
			isMobileEditMode: mobileLayout.enabled,
			replyingMessage,
			value,
			setValue,
			textareaRef,
			previousValueRef,
		});

		const hasPendingSticker = ChannelStickerStore.getPendingSticker(channel.id) !== null;
		const hasAttachments = uploadAttachments.length > 0;
		const showAttachments = hasAttachments;
		const showStickers = hasPendingSticker;
		const isComposing = !!value.trim() || hasAttachments || hasPendingSticker;
		const isOverCharacterLimit = value.length > maxMessageLength;
		const shouldShowMobileGiftButton = mobileLayout.enabled && showGiftButton && containerWidth > 540;

		const {onSubmit} = useTextareaSubmit({
			channelId: channel.id,
			guildId: channel.guildId ?? null,
			editingMessage: editingMessage ?? null,
			isMobileEditMode: mobileLayout.enabled,
			uploadAttachmentsLength: uploadAttachments.length,
			hasPendingSticker,
			value,
			setValue,
			displayToActual,
			clearSegments,
			isSlowmodeActive,
			handleSendMessage,
			onMentionConfirmationNeeded: handleMentionConfirmationNeeded,
			i18n: i18n,
		});

		const handleEscapeKey = React.useCallback(
			(event: React.KeyboardEvent<HTMLTextAreaElement>) => {
				if (event.key !== 'Escape') return;

				if (hasAttachments || hasPendingSticker || replyingMessage) {
					event.preventDefault();

					if (hasAttachments) {
						CloudUpload.clearTextarea(channel.id);
					}

					if (hasPendingSticker) {
						ChannelStickerStore.removePendingSticker(channel.id);
					}

					if (replyingMessage) {
						MessageActionCreators.stopReply(channel.id);
					}

					return;
				}

				if (isInputAreaFocused && KeyboardModeStore.keyboardModeEnabled) {
					event.preventDefault();
					KeyboardModeStore.exitKeyboardMode();
					return;
				}

				if (AccessibilityStore.escapeExitsKeyboardMode) {
					KeyboardModeStore.exitKeyboardMode();
				}
			},
			[
				channel.id,
				hasAttachments,
				hasPendingSticker,
				replyingMessage,
				isInputAreaFocused,
				KeyboardModeStore.keyboardModeEnabled,
				AccessibilityStore.escapeExitsKeyboardMode,
			],
		);

		const handleFormattingShortcut = React.useCallback(
			(event: React.KeyboardEvent<HTMLTextAreaElement>) => {
				for (const {combo: shortcutCombo, wrapper} of MARKDOWN_FORMATTING_SHORTCUTS) {
					if (!doesEventMatchShortcut(event, shortcutCombo)) {
						continue;
					}

					const textarea = textareaRef.current;
					if (!textarea) {
						return;
					}

					const selectionStart = textarea.selectionStart ?? 0;
					const selectionEnd = textarea.selectionEnd ?? 0;
					if (selectionStart === selectionEnd) {
						return;
					}

					const selectedText = value.slice(selectionStart, selectionEnd);
					const wrapperLength = wrapper.length;
					const alreadyWrappedInside =
						selectedText.length >= wrapperLength * 2 &&
						selectedText.startsWith(wrapper) &&
						selectedText.endsWith(wrapper);
					const hasPrefixWrapper =
						wrapperLength > 0 &&
						selectionStart >= wrapperLength &&
						value.slice(selectionStart - wrapperLength, selectionStart) === wrapper;
					const hasSuffixWrapper =
						wrapperLength > 0 &&
						selectionEnd + wrapperLength <= value.length &&
						value.slice(selectionEnd, selectionEnd + wrapperLength) === wrapper;

					let newValue: string;
					let newSelectionStart: number;
					let newSelectionEnd: number;

					if (alreadyWrappedInside) {
						const unwrappedText = selectedText.slice(wrapperLength, selectedText.length - wrapperLength);
						newValue = value.slice(0, selectionStart) + unwrappedText + value.slice(selectionEnd);
						newSelectionStart = selectionStart;
						newSelectionEnd = selectionStart + unwrappedText.length;
					} else if (hasPrefixWrapper && hasSuffixWrapper) {
						newValue =
							value.slice(0, selectionStart - wrapperLength) + selectedText + value.slice(selectionEnd + wrapperLength);
						newSelectionStart = selectionStart - wrapperLength;
						newSelectionEnd = selectionEnd - wrapperLength;
					} else {
						const wrappedText = `${wrapper}${selectedText}${wrapper}`;
						newValue = value.slice(0, selectionStart) + wrappedText + value.slice(selectionEnd);
						newSelectionStart = selectionStart + wrapperLength;
						newSelectionEnd = selectionEnd + wrapperLength;
					}

					handleTextChange(newValue, previousValueRef.current);
					setValue(newValue);

					const updateSelection = () => {
						textarea.setSelectionRange(newSelectionStart, newSelectionEnd);
					};

					window.requestAnimationFrame(updateSelection);

					event.preventDefault();
					event.stopPropagation();
					return;
				}
			},
			[handleTextChange, previousValueRef, setValue, textareaRef, value],
		);

		const handleTextareaKeyDown = React.useCallback(
			(event: React.KeyboardEvent<HTMLTextAreaElement>) => {
				handleFormattingShortcut(event);
				handleEscapeKey(event);
			},
			[handleFormattingShortcut, handleEscapeKey],
		);

		const handleSubmit = React.useCallback(() => {
			if (isOverCharacterLimit || isEditingScheduledMessage) {
				return;
			}
			onSubmit();
		}, [isOverCharacterLimit, onSubmit, isEditingScheduledMessage]);

		useTextareaDraftAndTyping({
			channelId: channel.id,
			value,
			setValue,
			draft,
			previousValueRef,
			isAutocompleteAttached,
			enabled: !disabled,
		});

		const {handleArrowUp} = useTextareaKeyboard({
			channelId: channel.id,
			isFocused,
			textareaRef,
			value,
			setValue,
			handleTextChange,
			previousValueRef,
			clearSegments,
			replyingMessage,
			editingMessage: editingMessage || null,
			getLastEditableMessage: () => MessageStore.getLastEditableMessage(channel.id) || null,
			enabled: !disabled,
		});

		const placeholderText = disabled
			? t`You do not have permission to send messages in this channel.`
			: channel.guildId != null
				? PlaceholderUtils.getChannelPlaceholder(channel.name || t`channel`, t`Message #`, Number.MAX_SAFE_INTEGER)
				: PlaceholderUtils.getDMPlaceholder(
						ChannelUtils.getDMDisplayName(channel),
						channel.isDM() ? t`Message @` : t`Message `,
						Number.MAX_SAFE_INTEGER,
					);

		React.useEffect(() => {
			const unsubscribe = ComponentDispatch.subscribe('FOCUS_TEXTAREA', (payload?: unknown) => {
				const {channelId, enterKeyboardMode} = (payload ?? {}) as {channelId?: string; enterKeyboardMode?: boolean};
				if (channelId && channelId !== channel.id) return;
				if (disabled) return;
				const textarea = textareaRef.current;
				if (textarea) {
					if (enterKeyboardMode) {
						KeyboardModeStore.enterKeyboardMode(true);
					} else {
						KeyboardModeStore.exitKeyboardMode();
					}
					safeFocus(textarea, true);
				}
			});
			return unsubscribe;
		}, [channel.id]);

		React.useEffect(() => {
			if (!canAttachFiles) return;
			const unsubscribe = ComponentDispatch.subscribe('TEXTAREA_UPLOAD_FILE', (payload?: unknown) => {
				const {channelId} = (payload ?? {}) as {channelId?: string};
				if (channelId && channelId !== channel.id) return;
				handleFileButtonClick();
			});
			return unsubscribe;
		}, [channel.id, canAttachFiles]);

		React.useLayoutEffect(() => {
			if (!containerRef.current) return;

			let lastWidth = -1;

			const checkButtonVisibility = () => {
				if (!containerRef.current) return;
				const containerWidthLocal = containerRef.current.offsetWidth;

				if (containerWidthLocal === lastWidth) return;
				lastWidth = containerWidthLocal;

				const shouldShowAll = containerWidthLocal > 500;
				setShowAllButtons(shouldShowAll);
				setContainerWidth(containerWidthLocal);
			};

			const resizeObserver = new ResizeObserver(checkButtonVisibility);
			resizeObserver.observe(containerRef.current);
			checkButtonVisibility();

			return () => {
				resizeObserver.disconnect();
			};
		}, [mobileLayout.enabled]);

		const handleCancelEdit = React.useCallback(() => {
			setValue('');
			clearSegments();
		}, [clearSegments]);

		const handleMessageInputButtonContextMenu = React.useCallback(
			(event: React.MouseEvent) => {
				event.preventDefault();
				event.stopPropagation();

				ContextMenuActionCreators.openFromEvent(event, () => (
					<MessageInputButtonsContextMenu canSchedule={canScheduleMessage} onSchedule={handleOpenScheduleModal} />
				));
			},
			[canScheduleMessage, handleOpenScheduleModal],
		);

		const hasStackedSections = Boolean(
			referencedMessage ||
				(editingMessage && mobileLayout.enabled) ||
				uploadAttachments.length > 0 ||
				hasPendingSticker,
		);

		const topBarContent =
			editingMessage && mobileLayout.enabled ? (
				<EditBar channel={channel} onCancel={handleCancelEdit} />
			) : (
				referencedMessage && (
					<ReplyBar
						replyingMessageObject={referencedMessage}
						shouldReplyMention={replyingMessage?.mentioning ?? false}
						setShouldReplyMention={(mentioning) => MessageActionCreators.setReplyMentioning(channel.id, mentioning)}
						channel={channel}
					/>
				)
			);

		const renderSection = (content: React.ReactNode) => <div className={wrapperStyles.stackSection}>{content}</div>;

		return (
			<>
				{topBarContent && renderSection(<div className={wrapperStyles.topBarContainer}>{topBarContent}</div>)}

				{hasMessageSchedulingAccess &&
					editingScheduledMessage &&
					renderSection(
						<ScheduledMessageEditBar
							scheduledLocalAt={editingScheduledMessage.scheduledLocalAt}
							timezone={editingScheduledMessage.timezone}
							onCancel={handleCancelScheduledEdit}
						/>,
					)}

				<FocusRing
					focusTarget={textareaRef}
					ringTarget={containerRef}
					offset={0}
					enabled={!disabled && AccessibilityStore.showTextareaFocusRing}
					ringClassName={styles.textareaFocusRing}
				>
					<div
						ref={containerRef}
						className={clsx(
							wrapperStyles.box,
							wrapperStyles.wrapperSides,
							styles.textareaOuter,
							hasStackedSections ? wrapperStyles.roundedBottom : wrapperStyles.roundedAll,
							wrapperStyles.bottomSpacing,
							disabled && wrapperStyles.disabled,
						)}
						style={{minHeight: 'var(--input-container-min-height)'}}
					>
						{showAttachments && renderSection(<ChannelAttachmentArea channelId={channel.id} />)}
						{showStickers &&
							renderSection(<ChannelStickersArea channelId={channel.id} hasAttachments={hasAttachments} />)}

						{renderSection(
							<div className={clsx(styles.mainWrapperDense, disabled && wrapperStyles.disabled)}>
								{!disabled && showUploadButton && canAttachFiles && (
									<div className={clsx(styles.uploadButtonColumn, styles.sideButtonPadding)}>
										<TextareaButton
											icon={PlusCircleIcon}
											label={t`Upload file`}
											onClick={handleFileButtonClick}
											onContextMenu={handleMessageInputButtonContextMenu}
											keybindAction="upload_file"
										/>
									</div>
								)}

								<div className={styles.contentAreaDense}>
									<Scroller ref={scrollerRef} fade={true} className={styles.scroller} key="channel-textarea-scroller">
										<div style={{display: 'flex', flexDirection: 'column'}}>
											<TextareaInputField
												channelId={channel.id}
												disabled={disabled}
												isMobile={mobileLayout.enabled}
												value={value}
												placeholder={placeholderText}
												textareaRef={textareaRef}
												isFocused={isFocused}
												isAutocompleteAttached={isAutocompleteAttached}
												autocompleteOptions={autocompleteOptions}
												selectedIndex={selectedIndex}
												onFocus={() => {
													setIsFocused(true);
													setIsInputAreaFocused(true);
												}}
												onBlur={() => {
													setIsFocused(false);
													setIsInputAreaFocused(false);
												}}
												onChange={(newValue) => {
													handleTextChange(newValue, previousValueRef.current);
													setValue(newValue);
												}}
												onHeightChange={handleTextareaHeightChange}
												onCursorMove={onCursorMove}
												onArrowUp={handleArrowUp}
												onEnter={handleSubmit}
												onAutocompleteSelect={handleSelect}
												setSelectedIndex={setSelectedIndex}
												onKeyDown={handleTextareaKeyDown}
											/>
										</div>
									</Scroller>
								</div>

								<TextareaButtons
									disabled={disabled}
									showAllButtons={showAllButtons}
									showUploadButton={showUploadButton}
									showGiftButton={showGiftButton}
									showGifButton={showGifButton}
									showMemesButton={showMemesButton}
									showStickersButton={showStickersButton}
									showEmojiButton={showEmojiButton}
									showMessageSendButton={showMessageSendButton}
									expressionPickerOpen={expressionPickerOpen}
									selectedTab={selectedTab}
									isMobile={mobileLayout.enabled}
									shouldShowMobileGiftButton={shouldShowMobileGiftButton}
									isComposing={isComposing}
									isSlowmodeActive={isSlowmodeActive}
									isOverLimit={isOverCharacterLimit}
									hasContent={!!value.trim()}
									hasAttachments={uploadAttachments.length > 0}
									expressionPickerTriggerRef={expressionPickerTriggerRef}
									invisibleExpressionPickerTriggerRef={invisibleExpressionPickerTriggerRef}
									onExpressionPickerToggle={handleExpressionPickerTabToggle}
									onSubmit={handleSubmit}
									disableSendButton={isEditingScheduledMessage}
									onContextMenu={handleMessageInputButtonContextMenu}
								/>
								{isScheduleModalOpen && hasMessageSchedulingAccess && (
									<ScheduleMessageModal
										onClose={() => setIsScheduleModalOpen(false)}
										onSubmit={handleScheduleSubmit}
										initialScheduledLocalAt={editingScheduledMessage?.scheduledLocalAt}
										initialTimezone={editingScheduledMessage?.timezone}
										title={isEditingScheduledMessage ? t`Reschedule Message` : undefined}
										submitLabel={isEditingScheduledMessage ? t`Update` : undefined}
										helpText={
											isEditingScheduledMessage
												? t`This will modify the existing scheduled message rather than sending immediately.`
												: undefined
										}
									/>
								)}
							</div>,
						)}

						<MessageCharacterCounter
							currentLength={value.length}
							maxLength={maxMessageLength}
							isPremium={currentUser?.isPremium() ?? false}
						/>

						{isAutocompleteAttached && (
							<Autocomplete
								type={autocompleteType}
								onSelect={handleSelect}
								selectedIndex={selectedIndex}
								options={autocompleteOptions}
								setSelectedIndex={setSelectedIndex}
								referenceElement={containerRef.current}
								query={autocompleteQuery}
								attached={true}
							/>
						)}
					</div>
				</FocusRing>

				{mobileLayout.enabled && (
					<ExpressionPickerSheet
						isOpen={expressionPickerOpen}
						onClose={() => setExpressionPickerOpen(false)}
						channelId={channel.id}
						onEmojiSelect={handleEmojiSelect}
					/>
				)}
			</>
		);
	},
);

export const ChannelTextarea = observer(({channel}: {channel: ChannelRecord}) => {
	const draft = DraftStore.getDraft(channel.id);
	const forceNoSendMessages = DeveloperOptionsStore.forceNoSendMessages;
	const forceNoAttachFiles = DeveloperOptionsStore.forceNoAttachFiles;

	const disabled = channel.isPrivate()
		? forceNoSendMessages
		: forceNoSendMessages || !PermissionStore.can(Permissions.SEND_MESSAGES, channel);
	const canAttachFiles = channel.isPrivate()
		? !forceNoAttachFiles
		: !forceNoAttachFiles && PermissionStore.can(Permissions.ATTACH_FILES, channel);

	return (
		<ChannelTextareaContent
			key={channel.id}
			channel={channel}
			disabled={disabled}
			canAttachFiles={canAttachFiles}
			draft={draft}
		/>
	);
});
