import {
UserType,
FileType,
LinkType,
PollType,
RoomType,
StickerType,
MessageType,
ChannelType,
RoomKindEnum,
AttachmentType,
LinkPreviewType,
MessagePackType,
MessageKindEnum,
AttachmentKindEnum,
MessageMetadataType,
} from "@openchat/types"
import {
useMembersBy,
useAccountId,
useChannelsBy,
useRoomMetadata,
useUserProfiles,
useCurrentRoomUISettings,
CurrentRoomUISettingsModel,
useMessagePack,
} from "@openchat/ui"
import {
View,
Image,
Keyboard,
TextInput,
Pressable,
TouchableOpacity,
useWindowDimensions,
NativeSyntheticEvent,
TextInputKeyPressEventData,
} from "react-native"
import Animated, {
Easing,
withTiming,
ReduceMotion,
useSharedValue,
useAnimatedStyle,
} from "react-native-reanimated"
import { SvgXml } from "react-native-svg"
import { useStyles } from "./message-input.style"
import TrackPlayer from "react-native-track-player"
import { AppColors } from "../../../app/app.colors"
import { UserProfileUtils } from "@openchat/models"
import React, { useEffect, useRef, useState } from "react"
import { launchImageLibrary } from "react-native-image-picker"
import { Icon } from "../../../ui-components/icon/icon.component"
import { useModal } from "../../../ui-components/modal/modal.hook"
import { useSafeAreaInsets } from "react-native-safe-area-context"
import { AppController, AppStore } from "../../../app/app.globals"
import { EmojiBox } from "../../emoji/emoji-box/emoji-box.component"
import { CryptoUtils, DateUtils, RandomUtils } from "@openchat/core"
import { SVGIcons } from "../../../ui-components/svg-icons/svg-icons"
import { ViewIf } from "../../../ui-components/view-if/view-if.component"
import { Tooltip } from "../../../ui-components/tooltip/tooltip.provider"
import { StyledText } from "../../../ui-components/styled-text/styled-text"
import { MessageTextAreaUtils } from "../message-textarea/message-textarea.utils"
import { EmotionCategoryType } from "../../emoji/emoji-type/emotion-category.types"
import { MessageInputQuote } from "../message-input-quote/message-input-quote.component"
import { MessageInputPopup } from "../message-input-popup/message-input-popup.component"
import { MessageInputAudio } from "../message-input-audio/message-input-audio.component"
import { MessageInputMention } from "../message-input-mention/message-input-mention.component"
import { MessageInputEditable } from "../message-input-editable/message-input-editable.component"
import { MessageInputDecorator } from "../message-input-decorator/message-input-decorator.component"
import { MessageInputPreviewContainer } from "../message-input-preview-container/message-input-preview-container.component"

export function MessageInput({
room,
scrollToMessage,
newMessagePacks,
updateMessagePacks,
}: {
room: RoomType
scrollToMessage: (messageId: string) => void
newMessagePacks: (messagePack: MessagePackType) => void
updateMessagePacks: (messagePack: MessagePackType) => void
}) {
// ---------------------------------------------------------------------------
// variables
// ---------------------------------------------------------------------------

const styles = useStyles()
const modal = useModal()
const accountId = useAccountId()
const insets = useSafeAreaInsets()
const dimensions = useWindowDimensions()
const currentRoomUISettings = useCurrentRoomUISettings(room.id)
const quoteMessage = currentRoomUISettings.quotedMessage
const editMessage = currentRoomUISettings.editMessage
const screenHeight = dimensions.height

const { channel, communityId } = useRoomMetadata(room.id)

const members = useMembersBy({ communityId: communityId })
const userProfiles = useUserProfiles({
in: members.map((member) => member.identityId),
})
const channels = useChannelsBy({ communityId: communityId })
const [filteredUsers, setFilteredUsers] = useState<UserType[]>([])
const [filteredChannels, setFilteredChannels] = useState<ChannelType[]>([])
const [newInputHeight, setNewInputHeight] = useState(0)
const [isShowEmojiBox, setIsShowEmojiBox] = useState(false)
const [maxShowEmoji, setMaxShowEmoji] = useState(false)
const [expanded, setExpanded] = useState(false)
const [attachmentKind, setAttachmentKind] = useState<AttachmentKindEnum>()
const [textMessage, setTextMessage] = useState<string>("")
const [editTextMessage, setEditTextMessage] = useState<string>("")

const [editMessageInput, setEditMessageInput] = useState<MessageType | null>()
const [files, setFiles] = useState<FileType[]>([])
const [messageLinks, setMessageLinks] = useState<LinkPreviewType[]>([])
const [linkIgnoreList, setLinkIgnoreList] = useState([""])
const [emotionCategory, setEmotionCategory] =
useState<EmotionCategoryType>("emoji")
const [currentType, setCurrentType] = useState<
"emoji" | "keyboard" | "sticker" | "gif"
>("keyboard")
const [deviceState, setDeviceState] = useState({
camera: false,
microphone: false,
})
const [deviceActiveState, setDeviceActiveState] = useState({
cameraActive: false,
microphoneActive: false,
})
const [selection, setSelection] = useState({
start: 0,
end: 0,
})
const [showTools, setShowTools] = useState(false)
const [selectedToolbarIcon, setSelectedToolbarIcon] = useState<
"bold" | "italic" | "strikethrough" | "code" | "bulletList" | "orderedList"
>("bold")
const [toggleMarkdownList, setToggleMarkdownList] = useState("")
const [slowModeTime, setSlowModeTime] = useState(0)

const inputRef = useRef<TextInput | null>(null)
const inputWrapperRef = useRef<View>(null)
const slowModeInterval = useRef<NodeJS.Timeout>()

// ---------------------------------------------------------------------------
// animations
// ---------------------------------------------------------------------------

const inputHeight = useSharedValue(newInputHeight)

const animatedInputStyles = useAnimatedStyle(() => ({
height: maxShowEmoji ? inputHeight.value : "auto",
}))
const animatedEmojiStyles = useAnimatedStyle(() =>
maxShowEmoji
? { height: "auto" }
: isShowEmojiBox
? {
height: 320,
}
: {},
)

// ---------------------------------------------------------------------------
// effects
// ---------------------------------------------------------------------------

useEffect(() => {
if (channel && channel.slowMode) {
const lastMessageSendTime = room.lastMessageSendTime
const slowMode = channel ? channel.slowMode : 0

slowModeInterval.current = setInterval(() => {
const timeLeft =
slowMode - Math.floor((Date.now() - lastMessageSendTime) / 1000)

if (timeLeft < 0) {
clearInterval(slowModeInterval.current)
setSlowModeTime(timeLeft)
} else {
setSlowModeTime(timeLeft)
}
}, 1000)
}
return () => {
clearInterval(slowModeInterval.current)
}
}, [room.lastMessageSendTime])

useEffect(() => {
loadDraft()
}, [room.id])

useEffect(() => {
setEditMessageInput(editMessage)

if (editMessage) setEditTextMessage(editMessage.text)
}, [editMessage])

useEffect(() => {
if (!files.length) return

onMessageTyping(textMessage)
}, [files.length])

// ---------------------------------------------------------------------------
// functions
// ---------------------------------------------------------------------------

function goDown() {
inputHeight.value = withTiming(newInputHeight, {
duration: 0,
easing: Easing.inOut(Easing.quad),
reduceMotion: ReduceMotion.System,
})

setMaxShowEmoji(false)
setExpanded(false)
}

function goUp() {
if (!inputWrapperRef.current) return

inputWrapperRef.current.measure((x, y, width, height, pageX, pageY) => {
setNewInputHeight(height)
})

setTimeout(() => setMaxShowEmoji(true), 0)

inputHeight.value = withTiming(
screenHeight - insets.top - insets.bottom - 20,
{
duration: 0,
easing: Easing.inOut(Easing.quad),
reduceMotion: ReduceMotion.System,
},
)

setExpanded(true)
}

async function sendMessage(
kind: MessageKindEnum,
messageText: string,
mediaFile: FileType[],
sticker?: { sticker: StickerType; stickerPackId: string },
user?: {
id: string
name: string
photo: FileType | null
},
) {
if (!accountId || !currentRoomUISettings) return

if (channel && channel.slowMode) {
if (Date.now() - room.lastMessageSendTime > channel.slowMode * 1000) {
await AppController.room.update({
id: room.id,
lastMessageSendTime: Date.now(),
})
} else {
return
}
}

let attachments: AttachmentType[] = []

for (let file of mediaFile) {
if (!attachmentKind) return
const attachment: AttachmentType = {
id: RandomUtils.uuid(),
createdAt: Date.now(),
kind: attachmentKind,
name: file.name,
size: file.size,
roomId: room.id,
messageId: null,
senderId: accountId,
mimeType:
kind === MessageKindEnum.audio
? "audio-record"
: kind === MessageKindEnum.video
? "video-record"
: file.mimeType.startsWith("application")
? file.mimeType.split("/")[1]
: file.mimeType.split("/")[0],
preview: file.data,
fileId: file.id,
}

attachments.push(attachment)
}

let links: LinkType[] = []
for (let messageLink of messageLinks) {
const link: LinkType = {
id: RandomUtils.uuid(),
createdAt: Date.now(),
roomId: room.id,
...messageLink,
}

links.push(link)
}

const metadata: MessageMetadataType = {
id: RandomUtils.uuid(),
replyCount: 0,
}

const message: MessageType = {
id: RandomUtils.uuid(),
createdAt: Date.now(),
text: messageText,
roomId: room.id,
isEdited: false,
isDeleted: false,
isDelivered: false,
senderId: accountId,
reactions: [],
forward: null,
regular: {
kind: kind,
quote: quoteMessage ? quoteMessage : null,
sharedUser: user ? user : null,
sticker: sticker ? sticker : null,
},
question: null,
answer:
room.kind === RoomKindEnum.QUESTION_ANSWERS
? {
negativeVotes: [],
positiveVotes: [],
}
: null,
news: null,
issue: null,
board: null,
}

newMessagePacks({
message,
metadata,
links,
attachments,
files: [],
poll: undefined,
})

await AppController.message.insert([
{
links,
message,
metadata,
attachments,
files: mediaFile,
poll: undefined,
},
])

AppStore.for(CurrentRoomUISettingsModel).set([
{
...currentRoomUISettings,
quotedMessage: undefined,
},
])
MessageTextAreaUtils.setDraft(room.id, null, room.draftMessage)

setTextMessage("")

await TrackPlayer.add({
id: "send",
url: require("../../../../public/sounds/message_send.wav"),
title: "Track Title",
artist: "Track Artist",
})

// Start playing it
await TrackPlayer.play()
removeAttachedData()
}

async function onMessageUpdate(
editedMessage: MessageType,
value: string,
files: FileType[],
poll?: PollType,
) {
let links: LinkType[] = []
for (let messageLink of messageLinks) {
const link: LinkType = {
id: RandomUtils.uuid(),
createdAt: Date.now(),
roomId: room.id,
...messageLink,
}

links.push(link)
}

let attachments: AttachmentType[] = []

for (let file of files) {
if (!attachmentKind || !accountId) return
const attachment: AttachmentType = {
id: file.id,
createdAt: editedMessage.createdAt,
kind: attachmentKind,
name: file.name,
size: file.size,
roomId: room.id,
messageId: null,
senderId: accountId,
mimeType: file.mimeType,
preview: file.data,
fileId: file.id,
}

attachments.push(attachment)
}

const metadata: MessageMetadataType = {
id: RandomUtils.uuid(),
replyCount: 0,
}

updateMessagePacks({
message: {
...editedMessage,
text: value,
isEdited: true,
},
files,
attachments,
links,
metadata,
poll: undefined,
})

await AppController.message.update({
roomId: editedMessage.roomId,
messageId: editedMessage.id,
message: {
...editedMessage,
text: value,
isEdited: true,
},
files,
attachments,
links,
})

AppStore.for(CurrentRoomUISettingsModel).set([
{
...currentRoomUISettings,
editMessage: undefined,
},
])
setEditTextMessage("")
removeAttachedData()
}

function removeAttachedData() {
setEditMessageInput(undefined)
setFiles([])
setMessageLinks([])
}

async function shareProfile(
users: {
id: string
name: string
photo: FileType | null
}[],
) {
for (let user of users) {
sendMessage(MessageKindEnum.sharedUser, "", [], undefined, user)
}
}

function onKeyPress(e: NativeSyntheticEvent<TextInputKeyPressEventData>) {
if (e.nativeEvent.key === "Enter") {
if (
selectedToolbarIcon === "bulletList" ||
selectedToolbarIcon === "orderedList"
) {
setToggleMarkdownList(selectedToolbarIcon)
}
}
}

async function loadDraft() {
if (!room.draftMessage) return
setTextMessage(room.draftMessage.text)
setFiles(room.draftMessage.files)
}

async function onMessageTyping(value: string) {
const afterAt = /\s@([^@]*)$/
const afterHash = /\s#([^#]*)$/

const resultAfterAt = value.match(afterAt)
const resultAfterHash = value.match(afterHash)

if (
resultAfterAt &&
(!resultAfterHash || resultAfterHash[1].length > resultAfterAt[1].length)
) {
const textAfterAt = resultAfterAt[1]

const users = members.map((member) => {
return UserProfileUtils.unify(
UserProfileUtils.toPartialFromMember(member),
userProfiles.find(
(userProfile) => userProfile.id === member.identityId,
),
)
})

setFilteredUsers(users.filter((user) => user.name.includes(textAfterAt)))
} else {
if (resultAfterHash) {
const textAfterHash = resultAfterHash[1]

setFilteredChannels(
channels.filter((channel) => channel.name.includes(textAfterHash)),
)
} else {
setFilteredUsers([])
setFilteredChannels([])
}
}

setTextMessage(value)
if (value.trim() !== "" || files.length !== 0) {
MessageTextAreaUtils.setDraft(
room.id,
{
text: value,
files,
},
room.draftMessage,
)
} else {
MessageTextAreaUtils.setDraft(room.id, null, room.draftMessage)
}
MessageTextAreaUtils.extractMessageLinks({
text: value,
ignoreList: linkIgnoreList,
onLinkExist: (links) => {
setMessageLinks(links)
},
onLinkIgnore: (links) => {
setLinkIgnoreList(links)
},
})
await AppController.message.typing(room.id)
}

async function onGallerySelect() {
const files = await launchImageLibrary({
mediaType: "mixed",
includeBase64: true,
includeExtra: true,
selectionLimit: 50,
maxHeight: 110,
})

if (!files.assets) return

for (let selectedFile of files.assets) {
if (!selectedFile.uri) return

const file = await fetch(selectedFile.uri).then((response) => {
return response.blob()
})

const reader = new FileReader()

reader.onload = () => {
const dataUrl = reader.result as string

setFiles([
{
id: CryptoUtils.sha256(dataUrl),
name: selectedFile.fileName ? selectedFile.fileName : "",
size: file.size,
mimeType: file.type,
data: dataUrl,
duration: null,
},
])
}
reader.readAsDataURL(file)
}
}

function openMessageInputPopup() {
modal.open({
component: (
<MessageInputPopup
roomId={room.id}
newMessagePacks={newMessagePacks}
isShowFileAndProfile={true}
onProfileSelect={shareProfile}
onFilesSelected={(files, kind) => {
setAttachmentKind(kind)
setFiles((prev) => [...prev, ...files])
}}
/>
),

height: 180,
})
}

// ---------------------------------------------------------------------------
return (
<>
{maxShowEmoji && <View style={styles.maxShowInputAnimatedStyle} />}

<Animated.View
ref={inputWrapperRef}
style={[
animatedInputStyles,
styles.inputContainer,
{
position: maxShowEmoji ? "absolute" : "relative",
},
deviceActiveState.cameraActive
? { height: screenHeight - insets.bottom }
: {},
]}
>
{/* --------------------------------------------------------------------------- */}
{/* MENTION */}
{/* --------------------------------------------------------------------------- */}

{(filteredUsers.length > 0 || filteredChannels.length > 0) && (
<MessageInputMention
filteredChannels={filteredChannels}
filteredUsers={filteredUsers}
mentionPress={(type: string, name: string) => {
const afterAt = /@([^@]*)$/
const afterHash = /#([^#]*)$/

const resultAfterAt = textMessage.match(afterAt)
const resultAfterHash = textMessage.match(afterHash)

let textAfterMention = ""

if (resultAfterAt && type === "member") {
textAfterMention = resultAfterAt[1]
}
if (resultAfterHash && type === "channel") {
textAfterMention = resultAfterHash[1]
}

onMessageTyping(
textMessage.slice(
0,
textMessage.length - textAfterMention.length,
) +
name +
" ",
)
}}
/>
)}

<MessageInputPreviewContainer
files={files}
setFiles={(files) => setFiles(files)}
links={messageLinks}
onRemove={() => {
removeAttachedData(), setTextMessage(""), setEditTextMessage("")
}}
/>

{quoteMessage && (
<Pressable onPress={() => scrollToMessage(quoteMessage.id)}>
<MessageInputQuote
room={room}
messageQuote={quoteMessage}
onRemoveQuotePress={() => {
AppStore.for(CurrentRoomUISettingsModel).set([
{
...currentRoomUISettings,
quotedMessage: undefined,
},
])
}}
/>
</Pressable>
)}

{editMessageInput && (
<Pressable onPress={() => scrollToMessage(editMessageInput.id)}>
<MessageInputEditable
message={editMessageInput}
room={room}
onRemoveMessageEditPress={() => {
AppStore.for(CurrentRoomUISettingsModel).set([
{
...currentRoomUISettings,
editMessage: undefined,
},
])
removeAttachedData()
setEditTextMessage("")
}}
/>
</Pressable>
)}

<View
style={[
styles.messageInput,
quoteMessage
? {
borderTopRightRadius: 0,
borderTopLeftRadius: 0,
borderTopWidth: 0,
}
: {},
maxShowEmoji
? { borderTopLeftRadius: 10, borderTopRightRadius: 10 }
: {},
{
paddingLeft: insets.left,
paddingRight: insets.right,
},
]}
>
<View style={[styles.iconsInput]}>
{/* --------------------------------------------------------------------------- */}
{/* MESSAGE INPUT */}
{/* --------------------------------------------------------------------------- */}

<ViewIf
if={
!deviceActiveState.microphoneActive &&
!deviceActiveState.cameraActive
}
>
<View style={styles.msgInputWrap}>
{/* --------------------------------------------------------------------------- */}
{/* LEFT ICONS OF INPUT */}
{/* --------------------------------------------------------------------------- */}

{!editMessageInput ? (
<TouchableOpacity
style={styles.leftIconWrapper}
onPress={openMessageInputPopup}
>
<Icon name="fal fa-plus" size={18} bald />
</TouchableOpacity>
) : (
<TouchableOpacity
style={styles.leftIconWrapper}
onPress={onGallerySelect}
>
<Icon name="fal fa-icons" size={18} bald />
</TouchableOpacity>
)}

<View
style={[
showTools || (currentType === "emoji" && !showTools)
? styles.inputWrapperWithDecorator
: styles.inputWrapper,
]}
>
<TextInput
ref={inputRef}
value={editMessage ? editTextMessage : textMessage}
onChangeText={(text) => {
editMessageInput
? setEditTextMessage(text)
: onMessageTyping(text)
}}
placeholder="Write a message..."
onSelectionChange={(syntheticEvent) => {
const { nativeEvent } = syntheticEvent
const { selection } = nativeEvent
setSelection(selection)
}}
onBlur={() => {
if (!textMessage && !isShowEmojiBox)
setCurrentType("keyboard")
else setCurrentType(emotionCategory)
}}
onPressIn={() => {}}
onFocus={() => {
goDown()
setCurrentType(emotionCategory)
setIsShowEmojiBox(false)
}}
placeholderTextColor={AppColors.placeholderText}
onKeyPress={onKeyPress}
textAlignVertical="top"
multiline
style={[
styles.inputMsg,
showTools || (currentType === "emoji" && !showTools)
? {}
: { flex: 1 },
]}
/>

{/* --------------------------------------------------------------------------- */}
{/* KEYWORD ICON */}
{/* --------------------------------------------------------------------------- */}

{currentType === "keyboard" && (
<TouchableOpacity
onPress={() => {
setCurrentType(emotionCategory)
setIsShowEmojiBox(false)
}}
>
<Icon name="fal fa-note-sticky" height={18} width={16} />
</TouchableOpacity>
)}

{currentType === "emoji" && !showTools && (
<View style={styles.keywordIconWrap}>
<TouchableOpacity
style={styles.keywordIconAaWrap}
onPress={() => setShowTools(true)}
>
<StyledText style={styles.keywordIconAa}>Aa</StyledText>
</TouchableOpacity>

<TouchableOpacity
onPress={() => {
setIsShowEmojiBox(true)
Keyboard.dismiss()
}}
>
<Icon
bald={true}
name="fal fa-face-smile"
height={16}
width={16}
/>
</TouchableOpacity>

<TouchableOpacity>
<Icon
bald={true}
name="far fa-at"
height={18}
width={17}
/>
</TouchableOpacity>
</View>
)}

async function onMessageUpdate(
editedMessage: MessageType,
value: string,
files: FileType[],
poll?: PollType,
) {
let links: LinkType[] = []
for (let messageLink of messageLinks) {
const link: LinkType = {
id: RandomUtils.uuid(),
createdAt: Date.now(),
roomId: room.id,
...messageLink,
}

links.push(link)
}

let attachments: AttachmentType[] = []

for (let file of files) {
if (!attachmentKind || !accountId) return
const attachment: AttachmentType = {
id: file.id,
createdAt: editedMessage.createdAt,
kind: attachmentKind,
name: file.name,
size: file.size,
roomId: room.id,
messageId: null,
senderId: accountId,
mimeType: file.mimeType,
preview: file.data,
fileId: file.id,
}

attachments.push(attachment)
}

const metadata: MessageMetadataType = {
id: RandomUtils.uuid(),
replyCount: 0,
}

updateMessagePacks({
message: {
...editedMessage,
text: value,
isEdited: true,
},
files,
attachments,
links,
metadata,
poll: undefined,
})

await AppController.message.update({
roomId: editedMessage.roomId,
messageId: editedMessage.id,
message: {
...editedMessage,
text: value,
isEdited: true,
},
files,
attachments,
links,
})

AppStore.for(CurrentRoomUISettingsModel).set([
{
...currentRoomUISettings,
editMessage: undefined,
},
])
setEditTextMessage("")
removeAttachedData()
} how to fix files and attachments, when add text to files and attachments, it should add not update in place of file or attachment. How to do it ?