/*
 * 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 {FloatingPortal} from '@floating-ui/react';
import {Trans} from '@lingui/react/macro';
import {PencilIcon, SmileyIcon} from '@phosphor-icons/react';
import {clsx} from 'clsx';
import {AnimatePresence, motion} from 'framer-motion';
import {observer} from 'mobx-react-lite';
import React from 'react';
import {EmojiAttributionSubtext, getEmojiAttribution} from '~/components/emojis/EmojiAttributionSubtext';
import {EmojiTooltipContent} from '~/components/uikit/EmojiTooltipContent/EmojiTooltipContent';
import FocusRing from '~/components/uikit/FocusRing/FocusRing';
import {useTooltipPortalRoot} from '~/components/uikit/Tooltip';
import {Tooltip} from '~/components/uikit/Tooltip/Tooltip';
import {useMergeRefs} from '~/hooks/useMergeRefs';
import {useReactionTooltip} from '~/hooks/useReactionTooltip';
import {type CustomStatus, getCustomStatusText, normalizeCustomStatus} from '~/lib/customStatus';
import UnicodeEmojis from '~/lib/UnicodeEmojis';
import EmojiStore from '~/stores/EmojiStore';
import GuildStore from '~/stores/GuildStore';
import MobileLayoutStore from '~/stores/MobileLayoutStore';
import PresenceStore from '~/stores/PresenceStore';
import * as AvatarUtils from '~/utils/AvatarUtils';
import {getEmojiURL, shouldUseNativeEmoji} from '~/utils/EmojiUtils';
import styles from './CustomStatusDisplay.module.css';

const useTextOverflow = (
	containerRef: React.RefObject<HTMLElement | null>,
	content: string | null,
	checkVertical = false,
) => {
	const [isOverflowing, setIsOverflowing] = React.useState(false);

	React.useLayoutEffect(() => {
		const el = containerRef.current;
		if (!el || !content) {
			setIsOverflowing(false);
			return;
		}

		const checkOverflow = () => {
			if (el.scrollWidth > el.clientWidth || (checkVertical && el.scrollHeight > el.clientHeight)) {
				setIsOverflowing(true);
				return;
			}

			const range = document.createRange();
			range.selectNodeContents(el);
			const contentWidth = range.getBoundingClientRect().width;
			const containerWidth = el.getBoundingClientRect().width;
			setIsOverflowing(Math.ceil(contentWidth) > Math.ceil(containerWidth));
		};

		const frameId = requestAnimationFrame(checkOverflow);

		const resizeObserver = new ResizeObserver(checkOverflow);
		resizeObserver.observe(el);

		return () => {
			cancelAnimationFrame(frameId);
			resizeObserver.disconnect();
		};
	}, [containerRef, content, checkVertical]);

	return isOverflowing;
};

export interface EmojiPressData {
	id: string | null;
	name: string;
	animated: boolean;
}

interface CustomStatusDisplayProps {
	className?: string;
	emojiClassName?: string;
	customStatus?: CustomStatus | null;
	userId?: string;
	showText?: boolean;
	showTooltip?: boolean;
	allowJumboEmoji?: boolean;
	maxLines?: number;
	isEditable?: boolean;
	onEdit?: () => void;
	onEmojiPress?: (emoji: EmojiPressData) => void;
	constrained?: boolean;
	showPlaceholder?: boolean;
	animateOnParentHover?: boolean;
	alwaysAnimate?: boolean;
}

interface ClampedStyle extends React.CSSProperties {
	'--max-lines'?: number;
}

const sanitizeText = (text: string): string => {
	return text.replace(/[\r\n]+/g, ' ').trim();
};

const getTooltipEmojiUrl = (status: CustomStatus): string | null => {
	if (status.emojiId) {
		const emoji = EmojiStore.getEmojiById(status.emojiId);
		const isAnimated = emoji?.animated ?? status.emojiAnimated ?? false;
		return `${AvatarUtils.getEmojiURL({id: status.emojiId, animated: isAnimated})}?size=96&quality=lossless`;
	}
	if (status.emojiName && !shouldUseNativeEmoji) {
		return getEmojiURL(status.emojiName);
	}
	return null;
};

interface StatusEmojiWithTooltipProps {
	status: CustomStatus;
	children: React.ReactNode;
	onClick?: () => void;
	isButton?: boolean;
}

const StatusEmojiWithTooltip = observer(
	({status, children, onClick, isButton = false}: StatusEmojiWithTooltipProps) => {
		const tooltipPortalRoot = useTooltipPortalRoot();
		const {targetRef, tooltipRef, state, updatePosition, handlers, tooltipHandlers} = useReactionTooltip(500);
		const emoji = status.emojiId ? EmojiStore.getEmojiById(status.emojiId) : null;
		const attribution = getEmojiAttribution({
			emojiId: status.emojiId,
			guildId: emoji?.guildId ?? null,
			guild: emoji?.guildId ? GuildStore.getGuild(emoji.guildId) : null,
			emojiName: status.emojiName,
		});

		const getEmojiDisplayName = (): string => {
			if (status.emojiId) {
				return `:${status.emojiName}:`;
			}
			if (status.emojiName) {
				return UnicodeEmojis.convertSurrogateToName(status.emojiName, true, status.emojiName);
			}
			return '';
		};

		const emojiName = getEmojiDisplayName();
		const tooltipEmojiUrl = getTooltipEmojiUrl(status);

		const triggerRef = React.useRef<HTMLElement>(null);
		const mergedRef = useMergeRefs([targetRef, triggerRef]);

		const TriggerComponent = isButton ? 'button' : 'span';
		const triggerProps = isButton
			? {type: 'button' as const, className: styles.emojiPressable, onClick}
			: {className: styles.emojiTooltipTrigger};

		return (
			<>
				<TriggerComponent
					ref={mergedRef as React.Ref<HTMLButtonElement & HTMLSpanElement>}
					{...triggerProps}
					{...handlers}
				>
					{children}
				</TriggerComponent>
				{state.isOpen && (
					<FloatingPortal root={tooltipPortalRoot}>
						<AnimatePresence>
							<motion.div
								ref={(node) => {
									(tooltipRef as React.MutableRefObject<HTMLDivElement | null>).current = node;
									if (node && targetRef.current) {
										updatePosition();
									}
								}}
								style={{
									position: 'fixed',
									left: state.x,
									top: state.y,
									zIndex: 'var(--z-index-tooltip)',
									visibility: state.isReady ? 'visible' : 'hidden',
								}}
								initial={{opacity: 0, scale: 0.98}}
								animate={{opacity: 1, scale: 1}}
								exit={{opacity: 0, scale: 0.98}}
								transition={{
									opacity: {duration: 0.1},
									scale: {type: 'spring', damping: 25, stiffness: 500},
								}}
								{...tooltipHandlers}
							>
								<EmojiTooltipContent
									emojiUrl={tooltipEmojiUrl}
									emoji={shouldUseNativeEmoji && status.emojiName && !status.emojiId ? status.emojiName : undefined}
									emojiAlt={status.emojiName ?? undefined}
									primaryContent={emojiName}
									subtext={
										<EmojiAttributionSubtext
											attribution={attribution}
											classes={{
												container: styles.emojiTooltipSubtext,
												guildRow: styles.emojiTooltipGuildRow,
												guildIcon: styles.emojiTooltipGuildIcon,
												guildName: styles.emojiTooltipGuildName,
												verifiedIcon: styles.emojiTooltipVerifiedIcon,
											}}
										/>
									}
								/>
							</motion.div>
						</AnimatePresence>
					</FloatingPortal>
				)}
			</>
		);
	},
);

interface EmojiRenderResult {
	node: React.ReactNode;
	altText: string;
}

const renderStatusEmoji = (
	status: CustomStatus,
	emojiClassName?: string,
	animateOnParentHover?: boolean,
	alwaysAnimate?: boolean,
): EmojiRenderResult | null => {
	const sizeSuffix = '?size=96&quality=lossless';

	if (status.emojiId) {
		const emoji = EmojiStore.getEmojiById(status.emojiId);
		const altText = `:${status.emojiName}:`;
		const isAnimated = emoji?.animated ?? status.emojiAnimated ?? false;
		const staticUrl = `${AvatarUtils.getEmojiURL({id: status.emojiId, animated: false})}${sizeSuffix}`;
		const animatedUrl = isAnimated
			? `${AvatarUtils.getEmojiURL({id: status.emojiId, animated: true})}${sizeSuffix}`
			: null;

		if (alwaysAnimate && animatedUrl) {
			return {
				node: (
					<img
						src={animatedUrl}
						alt={status.emojiName ?? undefined}
						draggable={false}
						className={clsx(styles.statusEmoji, emojiClassName)}
					/>
				),
				altText,
			};
		}

		if (animateOnParentHover && animatedUrl) {
			return {
				node: (
					<span className={styles.statusEmojiWrapper}>
						<img
							src={staticUrl}
							alt={status.emojiName ?? undefined}
							draggable={false}
							className={clsx(styles.statusEmoji, styles.staticEmoji, emojiClassName)}
						/>
						<img
							src={animatedUrl}
							alt={status.emojiName ?? undefined}
							draggable={false}
							className={clsx(styles.statusEmoji, styles.animatedEmoji, emojiClassName)}
						/>
					</span>
				),
				altText,
			};
		}

		return {
			node: (
				<img
					src={staticUrl}
					alt={status.emojiName ?? undefined}
					draggable={false}
					className={clsx(styles.statusEmoji, emojiClassName)}
				/>
			),
			altText,
		};
	}

	if (status.emojiName) {
		const altText = status.emojiName;

		if (!shouldUseNativeEmoji) {
			const twemojiUrl = getEmojiURL(status.emojiName);
			if (twemojiUrl) {
				return {
					node: (
						<img
							src={twemojiUrl}
							alt={status.emojiName}
							draggable={false}
							className={clsx(styles.statusEmoji, emojiClassName)}
						/>
					),
					altText,
				};
			}
		}
		return {
			node: <span className={clsx(styles.statusEmoji, styles.nativeEmoji, emojiClassName)}>{status.emojiName}</span>,
			altText,
		};
	}

	return null;
};

export const CustomStatusDisplay = observer(
	({
		className,
		emojiClassName,
		customStatus,
		userId,
		showText = true,
		showTooltip = true,
		allowJumboEmoji = false,
		maxLines = 1,
		isEditable = false,
		onEdit,
		onEmojiPress,
		constrained = false,
		showPlaceholder = false,
		animateOnParentHover = false,
		alwaysAnimate = false,
	}: CustomStatusDisplayProps) => {
		const containerRef = React.useRef<HTMLDivElement>(null);
		const status = customStatus === undefined ? (userId ? PresenceStore.getCustomStatus(userId) : null) : customStatus;
		const normalized = normalizeCustomStatus(status);
		const displayText = normalized?.text ? sanitizeText(normalized.text) : null;
		const isOverflowing = useTextOverflow(containerRef, displayText, maxLines > 1);

		if (!normalized) {
			if (showPlaceholder && isEditable && onEdit) {
				return (
					<FocusRing offset={-2}>
						<button type="button" className={styles.placeholder} onClick={onEdit}>
							<SmileyIcon size={14} weight="regular" className={styles.placeholderIcon} />
							<span className={styles.placeholderText}>
								<Trans>Set a custom status</Trans>
							</span>
						</button>
					</FocusRing>
				);
			}
			return null;
		}

		const fullText = getCustomStatusText(normalized);
		const hasEmoji = Boolean(normalized.emojiId || normalized.emojiName);
		const hasText = Boolean(normalized.text);

		if (!hasEmoji && !hasText) {
			return null;
		}

		const emojiResult = hasEmoji
			? renderStatusEmoji(normalized, emojiClassName, animateOnParentHover, alwaysAnimate)
			: null;
		const isEmojiOnly = hasEmoji && !hasText;
		const isSingleLine = maxLines === 1 && !isEmojiOnly;
		const shouldClamp = maxLines > 1 && !isEmojiOnly;
		const clampedStyle: ClampedStyle | undefined = shouldClamp ? {'--max-lines': maxLines} : undefined;

		if (isEditable && onEdit) {
			const isDesktop = !MobileLayoutStore.enabled;
			const shouldShowEmojiTooltip = showTooltip && isDesktop && hasEmoji;

			const renderEditableEmoji = () => {
				if (!emojiResult) {
					return null;
				}

				if (shouldShowEmojiTooltip) {
					return (
						<StatusEmojiWithTooltip status={normalized}>
							{emojiResult.node}
							<span className={styles.hiddenVisually}>{emojiResult.altText}</span>
						</StatusEmojiWithTooltip>
					);
				}

				return (
					<>
						{emojiResult.node}
						<span className={styles.hiddenVisually}>{emojiResult.altText}</span>
					</>
				);
			};

			const editableContent = (
				<FocusRing offset={-2}>
					<button
						type="button"
						className={clsx(styles.editableWrapper, {
							[styles.editableTextHover]: hasText,
							[styles.editableEmojiOnly]: isEmojiOnly,
						})}
						onClick={onEdit}
					>
						<div
							ref={containerRef}
							className={clsx(styles.content, className, {
								[styles.jumbo]: allowJumboEmoji && isEmojiOnly,
								[styles.singleLine]: isSingleLine,
								[styles.clamped]: shouldClamp,
							})}
							style={clampedStyle}
						>
							{renderEditableEmoji()}
							{showText && displayText && <span className={styles.truncatedText}>{displayText}</span>}
						</div>
						{isEmojiOnly && <PencilIcon size={12} weight="bold" className={styles.editPencilIcon} />}
					</button>
				</FocusRing>
			);

			if (showTooltip && fullText && isOverflowing) {
				return <Tooltip text={fullText}>{editableContent}</Tooltip>;
			}

			return editableContent;
		}

		const handleEmojiPress = () => {
			if (!onEmojiPress || !normalized) {
				return;
			}
			const emoji = EmojiStore.getEmojiById(normalized.emojiId ?? '');
			const shouldAnimate = emoji?.animated ?? normalized.emojiAnimated ?? false;
			onEmojiPress({
				id: normalized.emojiId,
				name: normalized.emojiName ?? '',
				animated: shouldAnimate,
			});
		};

		const renderEmojiNode = () => {
			if (!emojiResult) {
				return null;
			}

			const isDesktop = !MobileLayoutStore.enabled;
			const shouldShowEmojiTooltip = showTooltip && isDesktop && hasEmoji;

			if (onEmojiPress && hasEmoji) {
				if (shouldShowEmojiTooltip) {
					return (
						<StatusEmojiWithTooltip status={normalized} onClick={handleEmojiPress} isButton>
							{emojiResult.node}
							<span className={styles.hiddenVisually}>{emojiResult.altText}</span>
						</StatusEmojiWithTooltip>
					);
				}
				return (
					<button type="button" className={styles.emojiPressable} onClick={handleEmojiPress}>
						{emojiResult.node}
						<span className={styles.hiddenVisually}>{emojiResult.altText}</span>
					</button>
				);
			}

			if (shouldShowEmojiTooltip) {
				return (
					<StatusEmojiWithTooltip status={normalized}>
						{emojiResult.node}
						<span className={styles.hiddenVisually}>{emojiResult.altText}</span>
					</StatusEmojiWithTooltip>
				);
			}

			return (
				<span className={styles.emojiTooltipTrigger}>
					{emojiResult.node}
					<span className={styles.hiddenVisually}>{emojiResult.altText}</span>
				</span>
			);
		};

		const content = (
			<div
				ref={containerRef}
				className={clsx(styles.content, className, {
					[styles.jumbo]: allowJumboEmoji && isEmojiOnly,
					[styles.singleLine]: isSingleLine,
					[styles.clamped]: shouldClamp,
					[styles.constrained]: constrained,
				})}
				style={clampedStyle}
			>
				{renderEmojiNode()}
				{showText && displayText && <span className={styles.truncatedText}>{displayText}</span>}
			</div>
		);

		if (showTooltip && fullText && isOverflowing) {
			return <Tooltip text={fullText}>{content}</Tooltip>;
		}

		return content;
	},
);

CustomStatusDisplay.displayName = 'CustomStatusDisplay';
