import { useMutation } from "@tanstack/react-query";
import * as Crypto from "expo-crypto";
import { File } from "expo-file-system";
import * as VideoThumbnails from "expo-video-thumbnails";
import type { LocalMessageV2 } from "@/db/schema";
import { chatControllerSendMessage } from "@/lib/api/chat/chat";
import type { AttachmentMeta } from "@/lib/api/famchest.schemas";
import { handleApiNewMessages, handleSentMessage } from "@/lib/features/chat";
import { useMyProfile } from "@/lib/queries/useMyProfile";
import { uploadFile } from "@/lib/supabase/calls";
import { getFileNameFromUri } from "@/lib/utils/getFilenameFromUri";
import { logger } from "@/lib/utils/logger";
import type { DocumentPickerAsset } from "expo-document-picker";
import type { ImagePickerAsset } from "expo-image-picker";
export type AudioAsset = {
uri: string;
mimeType?: string;
size?: number;
duration?: number; // Optional: if we can get duration
};
export type SendMessageParams = {
conversationId: string;
message: string;
} & (
| {
type: "text";
file?: undefined;
}
| {
type: "image" | "video";
file: ImagePickerAsset;
}
| {
type: "file";
file: DocumentPickerAsset;
}
| {
type: "recording";
file: AudioAsset;
}
);
export const useSendMessage = () => {
const { data: profile } = useMyProfile();
return useMutation({
mutationKey: ["send_message"],
mutationFn: async (params: SendMessageParams) => {
const uniqueId = Crypto.randomUUID();
const { type, file } = params;
let attachmentPath: string | undefined;
let attachmentMeta: AttachmentMeta = {};
if (type !== "text" && file) {
const originalFilename =
("name" in file && file?.name) || getFileNameFromUri(file?.uri);
const fileInfo = new File(file.uri).info();
if (!fileInfo.exists || !fileInfo.uri) {
throw new Error("File not found");
}
if (type === "video") {
const { uri: thumbnailUrl } = await VideoThumbnails.getThumbnailAsync(
file.uri,
{ time: 0 },
);
const { path: thumbnailPath } = await uploadFile({
uri: thumbnailUrl,
uploadOptions: {
contentType: file.mimeType,
metadata: {
originalFilename,
},
},
});
attachmentMeta.thumbnailPath = thumbnailPath;
}
const uploadResult = await uploadFile({
uri: file.uri,
uploadOptions: {
contentType: file.mimeType,
metadata: {
originalFilename,
},
},
});
attachmentPath = uploadResult.path;
attachmentMeta = {
...attachmentMeta,
mimeType: file.mimeType,
size: fileInfo.exists ? fileInfo.size : 0,
originalFilename,
width: "width" in file ? file.width : undefined,
height: "height" in file ? file.height : undefined,
audioLength:
type === "recording" && params.file && "duration" in params.file
? params.file.duration
: undefined,
};
}
// Create a local message with pending status
const localMessage: LocalMessageV2 = {
uniqueId,
conversationId: params.conversationId,
type,
content: params.message,
attachmentPath: attachmentPath || null,
senderProfileId: profile?.id || "",
createdAt: new Date(),
updatedAt: new Date(),
isSynced: false,
isFailed: false,
id: null,
attachmentMeta,
};
// Store the local message in SQLite
try {
handleSentMessage(localMessage);
// Send message to server
const serverMessage = await chatControllerSendMessage(
params.conversationId,
{
content: params.message,
uniqueId,
type,
attachmentPath,
attachmentMeta,
},
);
handleApiNewMessages([serverMessage]);
return serverMessage;
} catch (error) {
// Handle error but keep the message in local state
// You may want to mark it as failed to be retried later
logger.error("Failed to send message:", error);
throw error;
}
},
onError: (error) => {
logger.error("Failed to send message:", error);
},
});
};