import { useEffect, useRef, useState } from 'react';
import { ArrowSquareOut, Play } from '@phosphor-icons/react';
import { useAction } from 'convex/react';
import { api } from '../../../../../convex/_generated/api';
import { usePlatform } from '../../platform';
import { PausedGif } from './PausedGif';
import styles from './LinkEmbed.module.css';

interface UrlPreview {
	title?: string;
	description?: string;
	imageUrl?: string;
	siteName?: string;
}

const VIDEO_HOSTS = [
	'youtube.com',
	'youtu.be',
	'www.youtube.com',
	'vimeo.com',
	'www.vimeo.com',
	'twitch.tv',
	'www.twitch.tv',
	'dailymotion.com',
	'www.dailymotion.com',
];

const DIRECT_VIDEO_EXTS = ['.mp4', '.webm', '.ogg', '.mov'];
const DIRECT_IMAGE_EXTS = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'];

function isDirectMedia(url: string): 'video' | 'image' | null {
	try {
		const pathname = new URL(url).pathname.toLowerCase();
		if (DIRECT_VIDEO_EXTS.some((ext) => pathname.endsWith(ext))) return 'video';
		if (DIRECT_IMAGE_EXTS.some((ext) => pathname.endsWith(ext))) return 'image';
	} catch {}
	return null;
}

function isVideoUrl(url: string): boolean {
	try {
		const hostname = new URL(url).hostname;
		return VIDEO_HOSTS.some((h) => hostname === h || hostname.endsWith('.' + h));
	} catch {
		return false;
	}
}

// Module-scope cache so we don't refetch the same URL every re-render.
// `null` means "we tried and there's no preview" — we cache that too to
// avoid hammering the fetcher for URLs that will never resolve.
const previewCache = new Map<string, UrlPreview | null>();

function normaliseMetadata(raw: any): UrlPreview | null {
	if (!raw || typeof raw !== 'object') return null;

	// Accept a few possible shapes: the Electron preload IPC returns
	// `{ title, description, image, siteName, url }`; matrix-js-sdk style
	// returns `{ 'og:title', 'og:description', 'og:image', 'og:site_name' }`.
	const title =
		raw.title ?? raw['og:title'] ?? raw.ogTitle ?? undefined;
	const description =
		raw.description ?? raw['og:description'] ?? raw.ogDescription ?? undefined;
	const imageUrl =
		raw.image ?? raw.imageUrl ?? raw['og:image'] ?? raw.ogImage ?? undefined;
	const siteName =
		raw.siteName ?? raw['og:site_name'] ?? raw.ogSiteName ?? undefined;

	if (!title && !description && !imageUrl) return null;
	return { title, description, imageUrl, siteName };
}

function useUrlPreview(url: string): UrlPreview | null {
	const platform = usePlatform();
	const fetchPreviewAction = useAction(api.links.fetchPreview);
	const [preview, setPreview] = useState<UrlPreview | null>(
		previewCache.get(url) ?? null,
	);

	useEffect(() => {
		if (previewCache.has(url)) {
			setPreview(previewCache.get(url) ?? null);
			return;
		}

		let cancelled = false;

		(async () => {
			try {
				// Prefer the platform-native fetcher when available (Electron
				// ships one via IPC — no CORS, no round-trip through the
				// backend). On web, `fetchMetadata` returns null due to CORS,
				// so we fall through to the Convex Node action which performs
				// the fetch server-side.
				let result: UrlPreview | null = null;
				const fetcher = platform?.links?.fetchMetadata;
				if (typeof fetcher === 'function') {
					try {
						const raw = await fetcher(url);
						result = normaliseMetadata(raw);
					} catch {
						result = null;
					}
				}
				if (!result) {
					try {
						const raw = await fetchPreviewAction({ url });
						if (!cancelled) result = normaliseMetadata(raw);
					} catch {
						result = null;
					}
				}
				if (cancelled) return;
				previewCache.set(url, result);
				setPreview(result);
			} catch {
				if (!cancelled) previewCache.set(url, null);
			}
		})();

		return () => {
			cancelled = true;
		};
	}, [url, platform, fetchPreviewAction]);

	return preview;
}

function DirectMediaEmbed({
	url,
	type,
	onOpenGif,
}: {
	url: string;
	type: 'video' | 'image';
	onOpenGif?: (url: string) => void;
}) {
	const videoRef = useRef<HTMLVideoElement>(null);
	const [playing, setPlaying] = useState(false);

	if (type === 'video') {
		const handlePlay = () => {
			if (!videoRef.current) return;
			videoRef.current.controls = true;
			void videoRef.current.play();
			setPlaying(true);
		};

		return (
			<div className={`${styles.embed} ${styles.embedBare}`}>
				<div className={styles.directVideoWrapper}>
					<video
						ref={videoRef}
						className={styles.directVideo}
						src={url}
						preload="metadata"
						onPause={() => {
							if (videoRef.current && videoRef.current.ended) {
								videoRef.current.controls = false;
								setPlaying(false);
							}
						}}
						onEnded={() => {
							if (videoRef.current) {
								videoRef.current.controls = false;
								setPlaying(false);
							}
						}}
					/>
					{!playing && (
						<button
							type="button"
							className={styles.directPlayOverlay}
							onClick={handlePlay}
						>
							<Play size={36} weight="fill" />
						</button>
					)}
				</div>
			</div>
		);
	}

	// GIFs (and other direct images) render through PausedGif so
	// the idle state freezes on the first frame and only animates
	// on hover. Click opens a fullscreen viewer via `onOpenGif` —
	// the parent MessageGroup owns the lightbox state and reuses
	// its existing ImageLightbox.
	const isGif = /\.gif(\?|#|$)/i.test(url);
	if (isGif) {
		return (
			<PausedGif url={url} onOpen={onOpenGif} className={styles.directGif} />
		);
	}

	return (
		<div className={`${styles.embed} ${styles.embedBare}`}>
			<a href={url} target="_blank" rel="noopener noreferrer">
				<img
					className={styles.directImage}
					src={url}
					alt=""
					loading="lazy"
				/>
			</a>
		</div>
	);
}

interface LinkEmbedProps {
	url: string;
	/** Called when the user clicks a paused GIF preview. The parent
	 *  owns the fullscreen viewer state (MessageGroup reuses its
	 *  existing ImageLightbox). Ignored for non-GIF embeds. */
	onOpenGif?: (url: string) => void;
}

export function LinkEmbed({ url, onOpenGif }: LinkEmbedProps) {
	const directType = isDirectMedia(url);
	if (directType) {
		return <DirectMediaEmbed url={url} type={directType} onOpenGif={onOpenGif} />;
	}

	const preview = useUrlPreview(url);

	// If the platform has no metadata fetcher (or it returned null / threw),
	// degrade gracefully to nothing — the raw link is already rendered in
	// the message body by MessageContent.
	if (!preview) return null;

	const isVideo = isVideoUrl(url);
	const hasImage = !!preview.imageUrl;

	return (
		<div className={styles.embed}>
			<div className={styles.grid}>
				<div className={styles.embedContent}>
					{preview.siteName && (
						<div className={styles.provider}>{preview.siteName}</div>
					)}

					{preview.title && (
						<a
							className={styles.title}
							href={url}
							target="_blank"
							rel="noopener noreferrer"
						>
							{preview.title}
						</a>
					)}

					{preview.description && (
						<div className={styles.description}>{preview.description}</div>
					)}

					{hasImage && (
						<div className={styles.mediaContainer}>
							<img
								className={styles.mediaImage}
								src={preview.imageUrl}
								alt={preview.title || ''}
								loading="lazy"
							/>
							{isVideo && (
								<a
									className={styles.mediaOverlay}
									href={url}
									target="_blank"
									rel="noopener noreferrer"
								>
									<div className={styles.mediaControls}>
										<button type="button" className={styles.playButton}>
											<Play size={28} weight="fill" />
										</button>
										<button type="button" className={styles.openButton}>
											<ArrowSquareOut size={22} />
										</button>
									</div>
								</a>
							)}
						</div>
					)}
				</div>
			</div>
		</div>
	);
}

export default LinkEmbed;
