import type { ActorIdentifier } from '@atcute/lexicons';

import { uniqueBy } from '@mary/array-fns';

import { cleanError } from '#/lib/errors';
import { targetToShareUrl } from '#/lib/routes/app-links';
import { enforceLen } from '#/lib/utils/text';

import { useSearchPostsQuery } from '#/state/queries/search-posts';
import { useSession } from '#/state/session';
import { useTitle } from '#/state/use-title';

import { Trans } from '#/locale/Trans';

import { signinDialogHandle } from '#/components/dialogs/handles';
import { List } from '#/components/List/List';
import { ListEmpty } from '#/components/List/ListEmpty';
import { ListError } from '#/components/List/ListError';
import * as ListTail from '#/components/List/ListTail';
import { Post } from '#/components/Post/Post';
import { PostFeedLoadingPlaceholder } from '#/components/PostFeed/PostFeedLoadingPlaceholder';
import { SearchError } from '#/components/SearchError';
import { shareUrl } from '#/components/sharing';
import { type Section, Tabs } from '#/components/Tabs';
import { Text } from '#/components/Text';
import { Button, ButtonIcon } from '#/components/web/Button';
import * as Layout from '#/components/web/Layout';
import { InlineButton } from '#/components/web/Link';

import Share from '#/icons/central/ArrowOutOfBox_round_outlined_radius1_stroke2.svg';
import MagnifyingGlassIcon from '#/icons/central/MagnifyingGlass_round_outlined_radius1_stroke2.svg';
import { m } from '#/paraglide/messages';
import { useParams } from '#/router';

export default function HashtagScreen() {
	useTitle(m['navigation.hashtag.title']());

	const [{ tag, author, tab }, replaceParams] = useParams('Hashtag');
	const isCashtag = tag.startsWith('$');

	// Cashtags already include the $ prefix, hashtags need # added
	const fullTag = isCashtag ? tag : `#${tag}`;

	// Keep cashtags uppercase, lowercase hashtags
	const displayTag = isCashtag ? fullTag.toUpperCase() : fullTag.toLowerCase();
	const headerTitle = enforceLen(displayTag, 24, true, 'middle');

	// DIDs have no `@` prefix; handles do.
	const sanitizedAuthor = author ? (author.startsWith('did:') ? author : `@${author}`) : '';

	const onShare = () => {
		void shareUrl(targetToShareUrl({ name: 'Hashtag', author, tag }));
	};

	const sections: Section<'latest' | 'top'>[] = [
		{
			id: 'top',
			label: m['common.search.top'](),
			children: <HashtagScreenTab author={author} fullTag={fullTag} sort="top" />,
		},
		{
			id: 'latest',
			label: m['common.search.latest'](),
			children: <HashtagScreenTab author={author} fullTag={fullTag} sort="latest" />,
		},
	];

	return (
		<Layout.Screen>
			<Tabs
				sections={sections}
				value={tab ?? 'top'}
				onValueChange={(next) => replaceParams({ tab: next })}
				header={
					<Layout.Header.Outer noBottomBorder sticky={false}>
						<Layout.Header.BackButton />
						<Layout.Header.Content>
							<Layout.Header.TitleText>{headerTitle}</Layout.Header.TitleText>
							{author && (
								<Layout.Header.SubtitleText>
									{m['screens.hashtag.fromAuthor']({ author: sanitizedAuthor })}
								</Layout.Header.SubtitleText>
							)}
						</Layout.Header.Content>
						<Layout.Header.EndSlot>
							<Button
								color="primary"
								label={m['common.share.action.share']()}
								onClick={onShare}
								shape="round"
								size="small"
								variant="ghost"
							>
								<ButtonIcon icon={Share} />
							</Button>
						</Layout.Header.EndSlot>
					</Layout.Header.Outer>
				}
			/>
		</Layout.Screen>
	);
}

const POST_ITEM_HEIGHT_ESTIMATE = 300;

function HashtagScreenTab({
	author,
	fullTag,
	sort,
}: {
	author: ActorIdentifier | undefined;
	fullTag: string;
	sort: 'top' | 'latest';
}) {
	const { hasSession } = useSession();

	const isCashtag = fullTag.startsWith('$');

	// Cashtags need # prefix for search: "#$BTC"
	const queryParam = isCashtag ? `#${fullTag}` : fullTag;

	const { data, isFetchingNextPage, isPending, isError, error, refetch, fetchNextPage } = useSearchPostsQuery(
		{ author, query: queryParam, sort },
	);

	if (!hasSession) {
		return (
			<SearchError title={m['common.search.loggedOutError']()}>
				<Text align="center" leading="snug" size="md">
					<Trans
						message={m['common.search.signInPrompt']}
						markup={{
							t0: ({ children }) => (
								<InlineButton
									label={m['common.session.action.signIn']()}
									onClick={() => signinDialogHandle.openWithPayload({})}
								>
									{children}
								</InlineButton>
							),
							t1: ({ children }) => <Text>{children}</Text>,
							t2: ({ children }) => <Text color="textContrastMedium">{children}</Text>,
						}}
					/>
				</Text>
			</SearchError>
		);
	}

	const posts = uniqueBy(data?.pages.flatMap((page) => page.posts) ?? [], (post) => post.uri);

	if (posts.length < 1) {
		if (isError) {
			return <ListError message={cleanError(error)} onRetry={() => void refetch()} />;
		}

		if (isPending) {
			return <PostFeedLoadingPlaceholder />;
		}

		return <ListEmpty icon={MagnifyingGlassIcon} message={m['screens.hashtag.empty']()} />;
	}

	return (
		<List
			data={posts}
			estimateHeight={POST_ITEM_HEIGHT_ESTIMATE}
			keyExtractor={(item) => item.uri}
			renderItem={({ index, item }) => <Post hideTopBorder={index === 0} post={item} />}
			ListFooterComponent={
				<ListTail.Frame>
					{isFetchingNextPage ? (
						<ListTail.Pending />
					) : isError ? (
						<ListTail.Error message={cleanError(error)} onRetry={() => void fetchNextPage()} />
					) : null}
				</ListTail.Frame>
			}
			onEndReached={() => {
				if (isError) {
					return;
				}
				void fetchNextPage();
			}}
			onEndReachedThreshold={2}
		/>
	);
}
