import { type RefObject, useState } from 'react';
import type { Gif } from '#/lib/gif';
import { useBreakpoints } from '#/lib/hooks/use-breakpoints';
import { type ComposerImage, createComposerImage } from '#/lib/media/composer-image';
import type { VideoAsset } from '#/lib/media/video/types';
import { CharProgress } from '#/features/composer/char-progress/CharProgress';
import * as EmojiPicker from '#/components/EmojiPicker';
import * as Toast from '#/components/Toast';
import EmojiSmileIcon from '#/icons/central/EmojiSmile_round_outlined_radius1_stroke2.svg';
import PlusIcon from '#/icons/central/PlusLarge_round_outlined_radius1_stroke2.svg';
import { m } from '#/paraglide/messages';
import * as styles from './ComposerFooter.css';
import { ComposerToolbarButton } from './ComposerToolbarButton';
import { createAddImagesWithCap } from './gallery-cap';
import { SelectGifBtn } from './photos/SelectGifBtn';
import { PostLanguageSelect } from './select-language/PostLanguageSelect';
import { type AssetType, SelectMediaButton, type SelectMediaButtonProps } from './SelectMediaButton';
import { MAX_GALLERY_IMAGES, type PostAction, type PostDraft } from './state/composer';
import type { TextInputRef } from './text-input/TextInput.types';
import { VideoUploadToolbar } from './VideoUploadToolbar';
export function ComposerFooter({
post,
dispatch,
showAddButton,
onError,
onSelectVideo,
onAddPost,
currentLanguages,
onSelectLanguage,
languageNudgeAt,
textInputRef,
}: {
post: PostDraft;
dispatch: (action: PostAction) => void;
showAddButton: boolean;
onError: (error: string) => void;
onSelectVideo: (postId: string, asset: VideoAsset) => void;
onAddPost: () => void;
currentLanguages: string[];
onSelectLanguage?: (language: string) => void;
languageNudgeAt: number;
textInputRef: RefObject<TextInputRef | null>;
}) {
const { gtPhone } = useBreakpoints();
const emojiPickerHandle = EmojiPicker.useEmojiPickerHandle();
/*
* Once we've allowed a certain type of asset to be selected, we don't allow
* other types of media to be selected.
*/
const [selectedAssetsType, setSelectedAssetsType] = useState<AssetType | undefined>(undefined);
const media = post.embed.media;
const images = media?.type === 'images' || media?.type === 'gallery' ? media.images : [];
const video = media?.type === 'video' ? media.video : null;
const isMaxImages = images.length >= MAX_GALLERY_IMAGES;
const isMaxVideos = !!video;
let selectedAssetsCount = 0;
let isMediaSelectionDisabled = false;
if (media?.type === 'images' || media?.type === 'gallery') {
isMediaSelectionDisabled = isMaxImages;
selectedAssetsCount = images.length;
} else if (media?.type === 'video') {
isMediaSelectionDisabled = isMaxVideos;
selectedAssetsCount = 1;
} else {
isMediaSelectionDisabled = !!media;
}
const onImageAdd = createAddImagesWithCap(images.length, dispatch);
const onSelectGif = (gif: Gif) => {
dispatch({ type: 'embedAddGif', gif });
};
/*
* Reset if the user clears any selected media
*/
if (selectedAssetsType !== undefined && !media) {
setSelectedAssetsType(undefined);
}
const onSelectAssets: SelectMediaButtonProps['onSelectAssets'] = async ({
type,
images: assetImages,
video: assetVideo,
errors,
}) => {
setSelectedAssetsType(type);
if (type === 'image' && assetImages.length) {
const results = await Promise.allSettled(assetImages.map((image) => createComposerImage(image)));
const selectedImages: ComposerImage[] = [];
let failed = 0;
for (const [index, result] of results.entries()) {
if (result.status === 'fulfilled') {
selectedImages.push(result.value);
} else {
failed++;
const file = assetImages[index]!;
console.error('createComposerImage failed', file.type, file.size, result.reason);
}
}
if (selectedImages.length) {
onImageAdd(selectedImages);
}
if (failed > 0) {
onError(m['view.composer.gallery.error.notAdded']({ failed }));
}
} else if ((type === 'video' || type === 'gif') && assetVideo) {
onSelectVideo(post.id, assetVideo);
}
errors.map((error) => {
Toast.show(error, {
type: 'warning',
});
});
};
return (
<div className={styles.footer}>
{video && video.status !== 'done' ? (
<VideoUploadToolbar state={video} />
) : (
<div className={styles.left}>
<SelectMediaButton
disabled={isMediaSelectionDisabled}
allowedAssetTypes={selectedAssetsType}
selectedAssetsCount={selectedAssetsCount}
onSelectAssets={onSelectAssets}
/>
<SelectGifBtn onSelectGif={onSelectGif} disabled={!!media} />
{gtPhone ? (
<>
<EmojiPicker.Trigger
handle={emojiPickerHandle}
render={
<ComposerToolbarButton label={m['common.a11y.openEmojiPicker']()} icon={EmojiSmileIcon} />
}
/>
<EmojiPicker.Root handle={emojiPickerHandle} nextFocusRef={textInputRef}>
<EmojiPicker.Picker />
</EmojiPicker.Root>
</>
) : null}
</div>
)}
<div className={styles.right}>
{showAddButton && (
<ComposerToolbarButton
label={m['view.composer.thread.action.addPostToThread']()}
onClick={onAddPost}
icon={PlusIcon}
/>
)}
<PostLanguageSelect
currentLanguages={currentLanguages}
onSelectLanguage={onSelectLanguage}
nudgeAt={languageNudgeAt}
/>
<CharProgress count={post.shortenedGraphemeLength} className={styles.charProgress} />
</div>
</div>
);
}