import { lazy, type CSSProperties, type ReactNode, Suspense } from 'react';

import type { AppBskyEmbedExternal } from '@atcute/bluesky';

import { clsx } from 'clsx';

import { cleanError } from '#/lib/errors';
import type { Gif } from '#/lib/gif';
import { toNiceDomain } from '#/lib/links/nice-domain';
import { getBlobUrl } from '#/lib/utils/blob-url';

import { useResolveGifQuery, useResolveLinkQuery } from '#/state/queries/resolve-link';

import { ExternalEmbedRemoveBtn } from '#/features/composer/ExternalEmbedRemoveBtn';

import { ExternalEmbed } from '#/components/ExternalEmbed';
import { NavigationDisabled } from '#/components/NavigationDisabled';
import { ModeratedFeedEmbed } from '#/components/Post/Embed/FeedEmbed';
import { ModeratedListEmbed } from '#/components/Post/Embed/ListEmbed';
import { StandardSiteEmbed } from '#/components/Post/Embed/StandardSiteEmbed';
import { isStandardSiteEmbed } from '#/components/Post/Embed/StandardSiteEmbed/utils';
import { parseTangledStringUrl } from '#/components/Post/Embed/TangledStringEmbed/detect';
import { TangledStringPlaceholder } from '#/components/Post/Embed/TangledStringEmbed/Placeholder';
import { Spinner } from '#/components/Spinner';
import { Embed as StarterPackEmbed } from '#/components/StarterPack/StarterPackCard';
import { Text } from '#/components/Text';

import { m } from '#/paraglide/messages';

import * as styles from './ExternalEmbed.css';

const TangledStringEmbed = lazy(() =>
	import('#/components/Post/Embed/TangledStringEmbed').then((mod) => ({ default: mod.TangledStringEmbed })),
);

export const ExternalEmbedGif = ({ onRemove, gif }: { onRemove: () => void; gif: Gif }) => {
	const { data, error } = useResolveGifQuery(gif);
	const thumbUrl = getBlobUrl(data?.thumb?.source.blob);
	let linkInfo: AppBskyEmbedExternal.ViewExternal | undefined;
	if (data) {
		// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- a tenor url and a `blob:` object url, both absolute
		linkInfo = {
			title: data.title ?? data.uri,
			uri: data.uri,
			description: data.description ?? '',
			thumb: thumbUrl,
		} as AppBskyEmbedExternal.ViewExternal;
	}

	const loadingStyle: CSSProperties = {
		aspectRatio: (() => {
			const dims = gif.media_formats.gif?.dims;
			if (dims && dims[0] > 0 && dims[1] > 0) {
				return dims[0] / dims[1];
			}
			return 16 / 9; // Default aspect ratio
		})(),
		width: '100%',
	};

	return (
		<div className={styles.container} role="group" aria-label={m['view.composer.embed.a11y.gifPreview']()}>
			{linkInfo ? (
				<NavigationDisabled>
					<ExternalEmbed link={linkInfo} hideAlt />
				</NavigationDisabled>
			) : error ? (
				<Container className={styles.errorContainer}>
					<Text numberOfLines={1} color="textContrastHigh">
						{gif.url}
					</Text>
					<Text numberOfLines={2} className={styles.textNegative}>
						{cleanError(error)}
					</Text>
				</Container>
			) : (
				<Container style={loadingStyle}>
					<Spinner color="default" label={m['common.status.loading']()} size="_2xl" />
				</Container>
			)}
			<ExternalEmbedRemoveBtn onRemove={onRemove} />
		</div>
	);
};

export const ExternalEmbedLink = ({
	uri,
	hasQuote,
	onRemove,
}: {
	uri: string;
	hasQuote: boolean;
	onRemove: () => void;
}) => {
	const { data, error } = useResolveLinkQuery(uri);
	const thumbUrl = getBlobUrl(data?.type === 'external' ? data.thumb?.source.blob : undefined);
	const tangledTarget = parseTangledStringUrl(uri);
	let linkComponent: ReactNode;
	if (tangledTarget) {
		linkComponent = (
			<Suspense fallback={<TangledStringPlaceholder />}>
				<TangledStringEmbed target={tangledTarget} />
			</Suspense>
		);
	} else if (data) {
		if (data.type === 'external') {
			const external = data.view?.external;
			if (external && isStandardSiteEmbed(external)) {
				linkComponent = (
					<StandardSiteEmbed
						preview
						view={
							// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- a resolved http(s) link and a `blob:` object url, both absolute
							{
								...external,
								title: external.title || data.title || uri,
								uri,
								description: external.description || data.description,
								// prefer opengraph data to atproto record-derived image
								thumb: thumbUrl || external.thumb,
							} as AppBskyEmbedExternal.ViewExternal
						}
					/>
				);
			} else {
				linkComponent = (
					<ExternalEmbed
						link={
							// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- a resolved http(s) link and a `blob:` object url, both absolute
							{
								title: data.title || uri,
								uri,
								description: data.description,
								thumb: thumbUrl,
							} as AppBskyEmbedExternal.ViewExternal
						}
						hideAlt
					/>
				);
			}
		} else if (data.kind === 'feed') {
			linkComponent = (
				<ModeratedFeedEmbed
					embed={{
						$type: 'app.bsky.feed.defs#generatorView',
						...data.view,
					}}
				/>
			);
		} else if (data.kind === 'list') {
			linkComponent = (
				<ModeratedListEmbed
					embed={{
						$type: 'app.bsky.graph.defs#listView',
						...data.view,
					}}
				/>
			);
		} else if (data.kind === 'starterPack') {
			linkComponent = <StarterPackEmbed starterPack={data.view} />;
		}
	}

	if (data?.type === 'record' && hasQuote) {
		// This is not currently supported by the data model so don't preview it.
		return null;
	}

	return (
		<div
			className={styles.linkContainer}
			role="group"
			aria-label={m['view.composer.embed.a11y.linkPreview']({ niceUrl: toNiceDomain(uri) })}
		>
			{linkComponent ? (
				<NavigationDisabled>{linkComponent}</NavigationDisabled>
			) : error ? (
				<Container className={styles.errorContainer}>
					<Text numberOfLines={1} color="textContrastHigh">
						{uri}
					</Text>
					<Text numberOfLines={2} className={styles.textNegative}>
						{cleanError(error)}
					</Text>
				</Container>
			) : (
				<Container>
					<Spinner color="default" label={m['common.status.loading']()} size="_2xl" />
				</Container>
			)}
			<ExternalEmbedRemoveBtn onRemove={onRemove} />
		</div>
	);
};

function Container({
	style,
	className,
	children,
}: {
	style?: CSSProperties;
	className?: string;
	children: ReactNode;
}) {
	return (
		<div className={clsx(styles.contentContainer, className)} style={style}>
			{children}
		</div>
	);
}
