/*
 * 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 MediaViewerActionCreators from '@app/actions/MediaViewerActionCreators';
import {AltTextBadge} from '@app/components/channel/embeds/AltTextBadge';
import {deriveDefaultNameFromMessage} from '@app/components/channel/embeds/EmbedUtils';
import styles from '@app/components/channel/embeds/media/EmbedImage.module.css';
import {getMediaButtonVisibility} from '@app/components/channel/embeds/media/MediaButtonUtils';
import {MediaContainer} from '@app/components/channel/embeds/media/MediaContainer';
import type {BaseMediaProps} from '@app/components/channel/embeds/media/MediaTypes';
import {NSFWBlurOverlay} from '@app/components/channel/embeds/NSFWBlurOverlay';
import {MediaActionBottomSheet} from '@app/components/channel/MediaActionBottomSheet';
import {useMaybeMessageViewContext} from '@app/components/channel/MessageViewContext';
import {MediaContextMenu} from '@app/components/uikit/context_menu/MediaContextMenu';
import {useDeleteAttachment} from '@app/hooks/useDeleteAttachment';
import {useMediaFavorite} from '@app/hooks/useMediaFavorite';
import {useMediaLoading} from '@app/hooks/useMediaLoading';
import {useNSFWMedia} from '@app/hooks/useNSFWMedia';
import type {MessageRecord} from '@app/records/MessageRecord';
import {createCalculator} from '@app/utils/DimensionUtils';
import {createSaveHandler} from '@app/utils/FileDownloadUtils';
import {attachmentsToViewerItems, findViewerItemIndex} from '@app/utils/MediaViewerItemUtils';
import type {MessageAttachment} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
import {useLingui} from '@lingui/react/macro';
import {clsx} from 'clsx';
import {observer} from 'mobx-react-lite';
import {type FC, useCallback, useState} from 'react';

const IMAGE_CONFIG = {
	MAX_WIDTH: 400,
} as const;

const imageCalculator = createCalculator({
	maxWidth: IMAGE_CONFIG.MAX_WIDTH,
	responsive: true,
});

interface ImagePreviewHandlerProps {
	src: string;
	originalSrc: string;
	naturalWidth: number;
	naturalHeight: number;
	contentHash?: string | null;
	embedIndex?: number;
	handlePress?: (event: React.MouseEvent | React.KeyboardEvent) => void;
	channelId?: string;
	messageId?: string;
	attachmentId?: string;
	message?: MessageRecord;
	animated?: boolean;
	mediaAttachments?: ReadonlyArray<MessageAttachment>;
	children: React.ReactNode;
}

type EmbedImageProps = React.ImgHTMLAttributes<HTMLImageElement> &
	BaseMediaProps & {
		src: string;
		originalSrc: string;
		naturalWidth: number;
		naturalHeight: number;
		width: number;
		height: number;
		placeholder?: string;
		constrain?: boolean;
		isInline?: boolean;
		handlePress?: (event: React.MouseEvent | React.KeyboardEvent) => void;
		alt?: string;
		mediaAttachments?: ReadonlyArray<MessageAttachment>;
		isPreview?: boolean;
		animated?: boolean;
	};

const ImagePreviewHandler: FC<ImagePreviewHandlerProps> = observer(
	({
		src,
		originalSrc,
		naturalWidth,
		naturalHeight,
		contentHash,
		embedIndex,
		handlePress,
		channelId,
		messageId,
		attachmentId,
		message,
		animated,
		mediaAttachments = [],
		children,
	}) => {
		const {t} = useLingui();
		const openImagePreview = useCallback(
			(event: React.MouseEvent | React.KeyboardEvent) => {
				if (event.type === 'click' && (event as React.MouseEvent).button !== 0) {
					return;
				}

				if (event.type === 'keydown') {
					const keyEvent = event as React.KeyboardEvent;
					if (keyEvent.key !== 'Enter' && keyEvent.key !== ' ') {
						return;
					}
				}

				if (handlePress) {
					event.preventDefault();
					event.stopPropagation();
					handlePress(event);
					return;
				}

				event.preventDefault();
				event.stopPropagation();

				if (mediaAttachments.length > 0) {
					const items = attachmentsToViewerItems(mediaAttachments);
					const currentIndex = findViewerItemIndex(items, attachmentId);

					MediaViewerActionCreators.openMediaViewer(items, currentIndex, {
						channelId,
						messageId,
						message,
					});
				} else {
					MediaViewerActionCreators.openMediaViewer(
						[
							{
								src,
								originalSrc: originalSrc ?? '',
								naturalWidth,
								naturalHeight,
								type: 'image' as const,
								contentHash,
								embedIndex,
								expiresAt: undefined,
								expired: undefined,
								animated,
							},
						],
						0,
						{
							channelId,
							messageId,
							message,
						},
					);
				}
			},
			[
				src,
				originalSrc,
				naturalWidth,
				naturalHeight,
				contentHash,
				embedIndex,
				handlePress,
				channelId,
				messageId,
				attachmentId,
				message,
				mediaAttachments,
			],
		);

		return (
			<button
				type="button"
				className={styles.imagePreviewHandler}
				aria-label={t`Open image in full view`}
				onClick={openImagePreview}
				onKeyDown={openImagePreview}
			>
				{children}
			</button>
		);
	},
);

export const EmbedImage: FC<EmbedImageProps> = observer(
	({
		src,
		originalSrc,
		naturalWidth,
		naturalHeight,
		width,
		height,
		placeholder,
		constrain,
		className,
		isInline,
		handlePress,
		alt = '',
		nsfw,
		channelId,
		messageId,
		attachmentId,
		embedIndex,
		message,
		contentHash,
		onDelete,
		mediaAttachments = [],
		isPreview,
		animated = false,
	}) => {
		const {t} = useLingui();
		const messageViewContext = useMaybeMessageViewContext();
		const {loaded, error, thumbHashURL} = useMediaLoading(src, placeholder);
		const {shouldBlur, gateReason} = useNSFWMedia(nsfw, channelId);

		const defaultName = deriveDefaultNameFromMessage({
			message,
			attachmentId,
			embedIndex,
			url: originalSrc,
			proxyUrl: src,
		});

		const {
			isFavorited,
			toggleFavorite: handleFavoriteClick,
			canFavorite,
		} = useMediaFavorite({
			channelId,
			messageId,
			attachmentId,
			embedIndex,
			defaultName: alt || defaultName,
			defaultAltText: alt,
			contentHash,
		});

		const {style: containerStyle, dimensions} = imageCalculator.calculate(
			{width, height},
			{
				preserve: constrain,
				responsive: !isInline,
				aspectRatio: true,
			},
		);
		const resolvedContainerStyle: React.CSSProperties = {
			...containerStyle,
			width: `${dimensions.width}px`,
			maxWidth: '100%',
		};

		const shouldRenderPlaceholder = error || !loaded;

		const handleDownloadClick = useCallback(
			(e: React.MouseEvent) => {
				e.stopPropagation();
				createSaveHandler(originalSrc, 'image')();
			},
			[originalSrc],
		);

		const handleDeleteClick = useDeleteAttachment(message, attachmentId);

		const [mediaSheetOpen, setMediaSheetOpen] = useState(false);

		const handleContextMenu = useCallback(
			(e: React.MouseEvent) => {
				if (!message || isPreview) return;

				e.preventDefault();
				e.stopPropagation();

				ContextMenuActionCreators.openFromEvent(e, ({onClose}) => (
					<MediaContextMenu
						message={message}
						originalSrc={originalSrc}
						proxyURL={src}
						type="image"
						contentHash={contentHash}
						attachmentId={attachmentId}
						embedIndex={embedIndex}
						defaultName={alt || defaultName}
						defaultAltText={alt}
						onClose={onClose}
						onDelete={onDelete || (() => {})}
					/>
				));
			},
			[message, src, originalSrc, contentHash, attachmentId, embedIndex, alt, defaultName, onDelete, isPreview],
		);

		const handleLongPress = useCallback(() => {
			if (!message || isPreview) return;
			setMediaSheetOpen(true);
		}, [message, isPreview]);

		const handleCloseMediaSheet = useCallback(() => {
			setMediaSheetOpen(false);
		}, []);

		if (shouldBlur) {
			return (
				<div className={styles.blurContainer}>
					<div className={clsx(styles.rowContainer, isInline && styles.justifyEnd)}>
						<div className={styles.innerContainer}>
							<div className={styles.imageWrapper} style={resolvedContainerStyle}>
								<div className={styles.imageContainer}>
									{thumbHashURL && (
										<div className={styles.thumbHashContainer}>
											<img
												src={thumbHashURL}
												className={styles.thumbHashImage}
												alt=""
												loading="lazy"
												style={{filter: 'blur(40px)'}}
											/>
										</div>
									)}
								</div>
							</div>
							<NSFWBlurOverlay reason={gateReason} />
						</div>
					</div>
				</div>
			);
		}

		const {showFavoriteButton, showDownloadButton, showDeleteButton} = getMediaButtonVisibility(
			canFavorite,
			isPreview ? undefined : message,
			attachmentId,
			{disableDelete: !!isPreview},
		);

		return (
			<>
				<div className={styles.container}>
					<div className={clsx(styles.rowContainer, isInline && styles.justifyEnd)}>
						<MediaContainer
							className={clsx(styles.mediaContainer, styles.cursorPointer)}
							style={resolvedContainerStyle}
							showFavoriteButton={showFavoriteButton}
							isFavorited={isFavorited}
							onFavoriteClick={handleFavoriteClick}
							showDownloadButton={showDownloadButton}
							onDownloadClick={handleDownloadClick}
							showDeleteButton={showDeleteButton}
							onDeleteClick={handleDeleteClick}
							onContextMenu={handleContextMenu}
							onLongPress={handleLongPress}
							renderedWidth={dimensions.width}
							renderedHeight={dimensions.height}
						>
							<ImagePreviewHandler
								src={src}
								originalSrc={originalSrc}
								naturalWidth={naturalWidth}
								naturalHeight={naturalHeight}
								contentHash={contentHash}
								embedIndex={embedIndex}
								handlePress={handlePress}
								channelId={channelId}
								messageId={messageId}
								attachmentId={attachmentId}
								message={message}
								mediaAttachments={mediaAttachments}
								animated={animated}
							>
								<div className={styles.imageInnerContainer}>
									{shouldRenderPlaceholder && thumbHashURL && (
										<div className={styles.thumbHashContainer}>
											<img
												src={thumbHashURL}
												className={styles.thumbHashImage}
												alt={alt ? t`Loading: ${alt}` : t`Loading image`}
												loading="lazy"
											/>
										</div>
									)}
									<img
										alt={alt || t`Image`}
										src={src}
										className={clsx(
											styles.imageElement,
											shouldRenderPlaceholder ? styles.opacityHidden : styles.opacityVisible,
											className,
										)}
										loading="lazy"
										tabIndex={-1}
									/>
								</div>
							</ImagePreviewHandler>
							<AltTextBadge altText={alt} onPopoutToggle={messageViewContext?.onPopoutToggle} />
						</MediaContainer>
					</div>
				</div>
				{message && (
					<MediaActionBottomSheet
						isOpen={mediaSheetOpen}
						onClose={handleCloseMediaSheet}
						message={message}
						originalSrc={originalSrc}
						proxyURL={src}
						type={animated ? 'gif' : 'image'}
						contentHash={contentHash}
						attachmentId={attachmentId}
						embedIndex={embedIndex}
						defaultName={alt || defaultName}
						defaultAltText={alt}
						handleDelete={onDelete}
					/>
				)}
			</>
		);
	},
);
