/*
 * 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 * as ContextMenuActionCreators from '@app/actions/ContextMenuActionCreators';
import * as ModalActionCreators from '@app/actions/ModalActionCreators';
import {modal} from '@app/actions/ModalActionCreators';
import * as PremiumModalActionCreators from '@app/actions/PremiumModalActionCreators';
import * as ToastActionCreators from '@app/actions/ToastActionCreators';
import * as VoiceSettingsActionCreators from '@app/actions/VoiceSettingsActionCreators';
import styles from '@app/components/modals/BackgroundImageGalleryModal.module.css';
import {ConfirmModal} from '@app/components/modals/ConfirmModal';
import * as Modal from '@app/components/modals/Modal';
import {Button} from '@app/components/uikit/button/Button';
import {MenuItem} from '@app/components/uikit/context_menu/MenuItem';
import FocusRing from '@app/components/uikit/focus_ring/FocusRing';
import {Tooltip} from '@app/components/uikit/tooltip/Tooltip';
import {Logger} from '@app/lib/Logger';
import VoiceSettingsStore, {BLUR_BACKGROUND_ID, NONE_BACKGROUND_ID} from '@app/stores/VoiceSettingsStore';
import * as BackgroundImageDB from '@app/utils/BackgroundImageDB';
import {openFilePicker} from '@app/utils/FilePickerUtils';
import {LimitResolver} from '@app/utils/limits/LimitResolverAdapter';
import {shouldShowPremiumFeatures} from '@app/utils/PremiumUtils';
import {Trans, useLingui} from '@lingui/react/macro';
import type {IconProps} from '@phosphor-icons/react';
import {
	ArrowsClockwiseIcon,
	CheckIcon,
	CrownIcon,
	EyeSlashIcon,
	PlusIcon,
	SparkleIcon,
	TrashIcon,
	WarningCircleIcon,
} from '@phosphor-icons/react';
import {observer} from 'mobx-react-lite';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';

const logger = new Logger('BackgroundImageGalleryModal');

interface BackgroundImage {
	id: string;
	createdAt: number;
}

interface BuiltInBackground {
	id: string;
	type: 'none' | 'blur' | 'upload';
	name: string;
	icon: React.ComponentType<IconProps>;
	description: string;
}

type BackgroundItemType = BuiltInBackground | BackgroundImage;

const MAX_FILE_SIZE = 10 * 1024 * 1024;
const ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'video/mp4'];

interface BackgroundItemProps {
	background: BackgroundItemType;
	isSelected: boolean;
	onSelect: (background: BackgroundItemType) => void;
	onContextMenu?: (event: React.MouseEvent, background: BackgroundImage) => void;
	onDelete?: (background: BackgroundImage) => void;
}

const BackgroundItem: React.FC<BackgroundItemProps> = React.memo(
	({background, isSelected, onSelect, onContextMenu, onDelete}) => {
		const {t} = useLingui();
		const isBuiltIn = 'type' in background;
		const Icon = isBuiltIn ? background.icon : undefined;
		const [imageUrl, setImageUrl] = useState<string | null>(null);
		const [isLoading, setIsLoading] = useState(!isBuiltIn);
		const [hasError, setHasError] = useState(false);

		useEffect(() => {
			if (isBuiltIn) return;
			let objectUrl: string | null = null;
			setIsLoading(true);
			setHasError(false);
			BackgroundImageDB.getBackgroundImageURL(background.id)
				.then((url) => {
					objectUrl = url;
					setImageUrl(url);
					setIsLoading(false);
				})
				.catch((error) => {
					logger.error('Failed to load background image:', error);
					setHasError(true);
					setIsLoading(false);
				});

			return () => {
				if (objectUrl) {
					URL.revokeObjectURL(objectUrl);
				}
			};
		}, [isBuiltIn, background.id]);

		const handleClick = useCallback(() => {
			onSelect(background);
		}, [background, onSelect]);

		const handleKeyDown = useCallback(
			(e: React.KeyboardEvent) => {
				if (e.key === 'Enter' || e.key === ' ') {
					e.preventDefault();
					onSelect(background);
				}
			},
			[background, onSelect],
		);

		const handleContextMenu = useCallback(
			(e: React.MouseEvent) => {
				if (!isBuiltIn) {
					onContextMenu?.(e, background as BackgroundImage);
				}
			},
			[isBuiltIn, background, onContextMenu],
		);

		const handleDelete = useCallback(
			(e: React.MouseEvent) => {
				e.preventDefault();
				e.stopPropagation();
				if (!isBuiltIn) {
					onDelete?.(background as BackgroundImage);
				}
			},
			[isBuiltIn, background, onDelete],
		);

		const handleRetry = useCallback(() => {
			setHasError(false);
			setIsLoading(true);
			BackgroundImageDB.getBackgroundImageURL(background.id)
				.then((url) => {
					setImageUrl(url);
					setIsLoading(false);
				})
				.catch((error) => {
					logger.error('Failed to load background image:', error);
					setHasError(true);
					setIsLoading(false);
				});
		}, [background.id]);

		return (
			<div
				className={styles.backgroundItem}
				style={{
					borderColor: isSelected ? 'var(--brand-primary)' : 'var(--background-modifier-accent)',
				}}
				onClick={handleClick}
				onKeyDown={handleKeyDown}
				onContextMenu={handleContextMenu}
				role="button"
				tabIndex={0}
				aria-pressed={isSelected}
			>
				{isBuiltIn ? (
					<div className={styles.backgroundItemContent}>
						<div className={styles.backgroundItemInner}>
							{Icon && (
								<Icon size={24} weight={isSelected ? 'fill' : 'regular'} className={styles.backgroundItemIcon} />
							)}
							<div className={styles.backgroundItemText}>
								<div className={styles.backgroundItemName}>{background.name}</div>
								<div className={styles.backgroundItemDesc}>{background.description}</div>
							</div>
						</div>
					</div>
				) : (
					<>
						{isLoading ? (
							<div className={styles.loadingContainer}>
								<div className={styles.spinner} />
							</div>
						) : hasError ? (
							<div className={styles.errorContainer}>
								<WarningCircleIcon size={24} weight="fill" className={styles.errorIcon} />
								<div className={styles.errorText}>
									<Trans>Failed to load</Trans>
								</div>
								<FocusRing offset={-2}>
									<button
										type="button"
										onClick={(e) => {
											e.stopPropagation();
											handleRetry();
										}}
										className={styles.errorButton}
									>
										<Trans>Retry</Trans>
									</button>
								</FocusRing>
							</div>
						) : imageUrl ? (
							<img src={imageUrl} alt={t`Background`} className={styles.backgroundImage} />
						) : null}
						<div className={styles.imageOverlay} />
						{!isBuiltIn && onDelete && !isLoading && !hasError && (
							<Tooltip text={t`Remove background`}>
								<FocusRing offset={-2}>
									<button
										type="button"
										onClick={handleDelete}
										className={styles.deleteButton}
										aria-label={t`Remove background`}
									>
										<TrashIcon size={16} weight="bold" className={styles.deleteButtonIcon} />
									</button>
								</FocusRing>
							</Tooltip>
						)}
					</>
				)}

				{isSelected && (
					<div className={styles.selectedBadge}>
						<CheckIcon size={16} weight="bold" className={styles.selectedIcon} />
					</div>
				)}
			</div>
		);
	},
);

BackgroundItem.displayName = 'BackgroundItem';

const BackgroundImageGalleryModal: React.FC = observer(() => {
	const {t} = useLingui();
	const voiceSettings = VoiceSettingsStore;
	const {backgroundImageId, backgroundImages = []} = voiceSettings;

	const isMountedRef = useRef(true);
	const [isDragging, setIsDragging] = useState(false);
	const dragCounterRef = useRef(0);

	const maxBackgroundImages = useMemo(() => LimitResolver.resolve({key: 'max_custom_backgrounds', fallback: 1}), []);
	const canAddMoreImages = backgroundImages.length < maxBackgroundImages;
	const backgroundCount = backgroundImages.length;

	const shouldShowReplace = maxBackgroundImages === 1 && backgroundImages.length >= 1;
	const builtInBackgrounds = useMemo(
		(): ReadonlyArray<BuiltInBackground> => [
			{
				id: NONE_BACKGROUND_ID,
				type: 'none',
				name: t`No Background`,
				icon: EyeSlashIcon,
				description: t`Show your actual background`,
			},
			{
				id: BLUR_BACKGROUND_ID,
				type: 'blur',
				name: t`Blur`,
				icon: SparkleIcon,
				description: t`Blur your background`,
			},
			{
				id: 'upload',
				type: 'upload',
				name: shouldShowReplace ? t`Replace` : t`Upload`,
				icon: PlusIcon,
				description: shouldShowReplace ? t`Replace your custom background` : t`Add a custom background`,
			},
		],
		[shouldShowReplace],
	);

	const sortedImages = useMemo(
		() => [...backgroundImages].sort((a, b) => b.createdAt - a.createdAt),
		[backgroundImages],
	);

	useEffect(() => {
		return () => {
			isMountedRef.current = false;
		};
	}, []);

	const processFileUpload = useCallback(
		async (file: File | null) => {
			if (!file) return;

			try {
				if (!ALLOWED_MIME_TYPES.includes(file.type)) {
					ToastActionCreators.createToast({
						type: 'error',
						children: t`Unsupported file format. Please use JPG, PNG, GIF, WebP, or MP4.`,
					});
					return;
				}

				if (file.size > MAX_FILE_SIZE) {
					ToastActionCreators.createToast({
						type: 'error',
						children: t`Background image is too large. Please choose a file smaller than 10MB.`,
					});
					return;
				}

				const newImage: BackgroundImage = {
					id: `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
					createdAt: Date.now(),
				};

				await BackgroundImageDB.saveBackgroundImage(newImage.id, file);

				if (isMountedRef.current) {
					let updatedImages = [...backgroundImages];
					let oldImageToDelete: string | null = null;

					if (backgroundImages.length >= maxBackgroundImages) {
						const oldImage = backgroundImages[0];
						oldImageToDelete = oldImage.id;
						updatedImages = [];
					}

					updatedImages.push(newImage);

					VoiceSettingsActionCreators.update({
						backgroundImages: updatedImages,
						backgroundImageId: newImage.id,
					});

					if (oldImageToDelete) {
						BackgroundImageDB.deleteBackgroundImage(oldImageToDelete).catch((error) => {
							logger.error('Failed to delete old background image:', error);
						});
					}

					ToastActionCreators.createToast({
						type: 'success',
						children: oldImageToDelete
							? t`Background image replaced successfully.`
							: t`Background image uploaded successfully.`,
					});

					ModalActionCreators.pop();
				}
			} catch (error) {
				logger.error('File upload failed:', error);
				ToastActionCreators.createToast({
					type: 'error',
					children: t`Failed to upload background image. Please try again.`,
				});
			}
		},
		[backgroundImages, maxBackgroundImages],
	);

	const handleUploadClick = useCallback(
		(showReplaceWarning: boolean = false) => {
			if (!canAddMoreImages) {
				ToastActionCreators.createToast({
					type: 'error',
					children: t`You've reached the maximum of ${maxBackgroundImages} backgrounds. Remove one to add a new background.`,
				});
				return;
			}

			const pickAndProcess = async () => {
				const [file] = await openFilePicker({accept: ALLOWED_MIME_TYPES.join(',')});
				await processFileUpload(file ?? null);
			};

			if (showReplaceWarning && backgroundImages.length >= maxBackgroundImages) {
				ModalActionCreators.push(
					modal(() => (
						<ConfirmModal
							title={t`Replace Background?`}
							description={
								<Trans>
									You can only have one custom background on the free tier. Uploading a new one will replace your
									existing background.
								</Trans>
							}
							primaryText={t`Replace`}
							primaryVariant="primary"
							onPrimary={pickAndProcess}
						/>
					)),
				);
				return;
			}

			void pickAndProcess();
		},
		[canAddMoreImages, maxBackgroundImages, backgroundImages.length, processFileUpload],
	);

	const handleBackgroundSelect = useCallback(
		(background: BackgroundItemType) => {
			if ('type' in background) {
				if (background.type === 'upload') {
					handleUploadClick(true);
					return;
				}

				VoiceSettingsActionCreators.update({
					backgroundImageId: background.id,
				});
			} else {
				VoiceSettingsActionCreators.update({
					backgroundImageId: background.id,
				});
			}

			ModalActionCreators.pop();
		},
		[handleUploadClick],
	);

	const handleRemoveImage = useCallback(
		async (image: BackgroundImage) => {
			try {
				await BackgroundImageDB.deleteBackgroundImage(image.id);

				const updatedImages = backgroundImages.filter((img) => img.id !== image.id);

				const updates: {backgroundImages: Array<BackgroundImage>; backgroundImageId?: string} = {
					backgroundImages: updatedImages,
				};

				if (backgroundImageId === image.id) {
					updates.backgroundImageId = NONE_BACKGROUND_ID;
				}

				VoiceSettingsActionCreators.update(updates);

				ToastActionCreators.createToast({
					type: 'success',
					children: t`Background image removed.`,
				});
			} catch (error) {
				logger.error('Failed to delete background image:', error);
				ToastActionCreators.createToast({
					type: 'error',
					children: t`Failed to remove background image. Please try again.`,
				});
			}
		},
		[backgroundImageId, backgroundImages],
	);

	const handleBackgroundContextMenu = useCallback(
		(event: React.MouseEvent, image: BackgroundImage) => {
			event.preventDefault();
			event.stopPropagation();

			ContextMenuActionCreators.openFromEvent(event, ({onClose}) => (
				<div>
					<MenuItem
						danger
						onClick={() => {
							handleRemoveImage(image);
							onClose();
						}}
					>
						{t`Remove Background`}
					</MenuItem>
				</div>
			));
		},
		[handleRemoveImage],
	);

	const handleDrop = useCallback(
		async (e: React.DragEvent) => {
			e.preventDefault();
			e.stopPropagation();
			setIsDragging(false);
			dragCounterRef.current = 0;

			const file = e.dataTransfer.files?.[0];
			if (!file) return;
			await processFileUpload(file);
		},
		[processFileUpload],
	);

	const handleDragEnter = useCallback((e: React.DragEvent) => {
		e.preventDefault();
		e.stopPropagation();
		dragCounterRef.current++;
		if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
			setIsDragging(true);
		}
	}, []);

	const handleDragLeave = useCallback((e: React.DragEvent) => {
		e.preventDefault();
		e.stopPropagation();
		dragCounterRef.current--;
		if (dragCounterRef.current === 0) {
			setIsDragging(false);
		}
	}, []);

	const handleDragOver = useCallback((e: React.DragEvent) => {
		e.preventDefault();
		e.stopPropagation();
	}, []);

	return (
		<Modal.Root size="medium">
			<Modal.Header title={t`Choose Background`} />
			<Modal.Content>
				<section
					className={styles.selectionSection}
					onDrop={handleDrop}
					onDragEnter={handleDragEnter}
					onDragLeave={handleDragLeave}
					onDragOver={handleDragOver}
					aria-label={t`Background selection area with drag and drop support`}
				>
					{isDragging && (
						<div className={styles.dragOverlay}>
							<div className={styles.dragContent}>
								<PlusIcon size={48} weight="bold" className={styles.dragIcon} />
								<div className={styles.dragText}>
									<Trans>Drop to upload background</Trans>
								</div>
							</div>
						</div>
					)}
					{maxBackgroundImages === 1 ? (
						<div className={styles.freeUserContainer}>
							{sortedImages.length > 0 ? (
								<div className={styles.customBackgroundWrapper}>
									<BackgroundItem
										key={sortedImages[0].id}
										background={sortedImages[0]}
										isSelected={backgroundImageId === sortedImages[0].id}
										onSelect={handleBackgroundSelect}
										onDelete={undefined}
									/>
									<div className={styles.actionButtons}>
										<Tooltip text={t`Replace background`}>
											<FocusRing offset={-2}>
												<button
													type="button"
													onClick={(e) => {
														e.stopPropagation();
														handleUploadClick(true);
													}}
													className={styles.actionButton}
													aria-label={t`Replace background`}
												>
													<ArrowsClockwiseIcon size={16} weight="bold" className={styles.actionButtonIcon} />
												</button>
											</FocusRing>
										</Tooltip>
										<Tooltip text={t`Remove background`}>
											<FocusRing offset={-2}>
												<button
													type="button"
													onClick={(e) => {
														e.stopPropagation();
														handleRemoveImage(sortedImages[0]);
													}}
													className={styles.actionButton}
													aria-label={t`Remove background`}
												>
													<TrashIcon size={16} weight="bold" className={styles.actionButtonIcon} />
												</button>
											</FocusRing>
										</Tooltip>
									</div>
								</div>
							) : (
								<div
									className={styles.uploadPlaceholder}
									onClick={() => handleUploadClick(false)}
									onKeyDown={(e) => {
										if (e.key === 'Enter' || e.key === ' ') {
											e.preventDefault();
											handleUploadClick(false);
										}
									}}
									role="button"
									tabIndex={0}
									aria-label={t`Upload custom background`}
								>
									<div className={styles.uploadPlaceholderContent}>
										<PlusIcon size={48} weight="regular" className={styles.uploadIcon} />
										<div className={styles.uploadTextContainer}>
											<div className={styles.uploadTitle}>
												<Trans>Upload Custom Background</Trans>
											</div>
											<div className={styles.uploadHint}>
												<Trans>Click or drag and drop</Trans>
											</div>
										</div>
									</div>
								</div>
							)}
							<div className={styles.builtInGrid}>
								{builtInBackgrounds
									.filter((bg) => bg.type !== 'upload')
									.map((background) => (
										<BackgroundItem
											key={background.id}
											background={background}
											isSelected={backgroundImageId === background.id}
											onSelect={handleBackgroundSelect}
										/>
									))}
							</div>
						</div>
					) : (
						<div className={styles.premiumGrid}>
							{builtInBackgrounds.map((background) => (
								<BackgroundItem
									key={background.id}
									background={background}
									isSelected={backgroundImageId === background.id}
									onSelect={handleBackgroundSelect}
								/>
							))}

							{sortedImages.map((image) => (
								<BackgroundItem
									key={image.id}
									background={image}
									isSelected={backgroundImageId === image.id}
									onSelect={handleBackgroundSelect}
									onContextMenu={handleBackgroundContextMenu}
									onDelete={handleRemoveImage}
								/>
							))}
						</div>
					)}

					<div className={styles.statsText}>
						{backgroundCount === 1
							? t`${backgroundCount} / ${maxBackgroundImages} custom background`
							: t`${backgroundCount} / ${maxBackgroundImages} custom backgrounds`}
					</div>

					<div className={styles.infoText}>
						<Trans>Supported: JPG, PNG, GIF, WebP, MP4. Max size: 10MB.</Trans>
					</div>

					{maxBackgroundImages === 1 && shouldShowPremiumFeatures() && (
						<div className={styles.premiumUpsell}>
							<div className={styles.premiumHeader}>
								<CrownIcon weight="fill" size={18} className={styles.premiumIcon} />
								<span className={styles.premiumTitle}>
									<Trans>Unlock More Backgrounds with Plutonium</Trans>
								</span>
							</div>
							<p className={styles.premiumDesc}>
								<Trans>
									Upgrade to store up to 15 custom backgrounds and unlock HD video quality, higher frame rates, and
									more.
								</Trans>
							</p>
							<Button variant="secondary" small={true} onClick={() => PremiumModalActionCreators.open()}>
								<Trans>Get Plutonium</Trans>
							</Button>
						</div>
					)}
				</section>
			</Modal.Content>
		</Modal.Root>
	);
});

export default BackgroundImageGalleryModal;
