import { useState } from 'react';

import type { AnyProfileView, ChatBskyConvoDefs } from '@atcute/bluesky';
import { ClientResponseError } from '@atcute/client';
import type { Did } from '@atcute/lexicons';

import { useQueryClient } from '@tanstack/react-query';

import { isNetworkError } from '#/lib/errors';

import type { Shadow } from '#/state/cache/types';
import { useLeaveConvo } from '#/state/queries/messages/leave-conversation';
import {
	createListMutualGroupsQueryKey,
	useListMutualGroupsQuery,
} from '#/state/queries/messages/list-mutual-groups';
import { useRemoveFromGroupChat } from '#/state/queries/messages/remove-from-group';
import { useSession } from '#/state/session';

import { AvatarBubbles } from '#/components/AvatarBubbles';
import { CenteredSpinner } from '#/components/CenteredSpinner';
import * as Dialog from '#/components/Dialog';
import { parseConvoView } from '#/components/dms/util';
import { Spinner } from '#/components/Spinner';
import { Text } from '#/components/Text';
import * as Toast from '#/components/Toast';
import { Button, ButtonText } from '#/components/web/Button';

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

import * as css from './BlockDialog.css';

const GROUP_ITEM_HEIGHT_ESTIMATE = 56;

type Item = ChatBskyConvoDefs.ConvoView;

type BlockDialogProps = {
	handle: Dialog.DialogHandle;
	profile: Shadow<AnyProfileView>;
	onBlock: () => Promise<void>;
	currentConvoId?: string;
};

export function BlockDialog({ handle, profile, onBlock, currentConvoId }: BlockDialogProps) {
	return (
		<Dialog.Root handle={handle}>
			<Dialog.Popup className={css.popup} scroll="body">
				<BlockDialogInner
					currentConvoId={currentConvoId}
					handle={handle}
					onBlock={onBlock}
					profile={profile}
				/>
			</Dialog.Popup>
		</Dialog.Root>
	);
}

function BlockDialogInner({
	currentConvoId,
	handle,
	onBlock,
	profile,
}: {
	currentConvoId?: string;
	handle: Dialog.DialogHandle;
	onBlock: () => Promise<void>;
	profile: Shadow<AnyProfileView>;
}) {
	// Optimistically hide convos the viewer has left or removed the profile from, before the query
	// refetches. We don't expect many items here, so a simple filter is fine.
	const [removedConvoIds, setRemovedConvoIds] = useState<Set<string>>(() => new Set());
	const onOptimisticallyRemoveConvo = (convoId: string) => {
		setRemovedConvoIds((prev) => {
			const next = new Set(prev);
			next.add(convoId);
			return next;
		});
	};
	const onRestoreConvo = (convoId: string) => {
		setRemovedConvoIds((prev) => {
			const next = new Set(prev);
			next.delete(convoId);
			return next;
		});
	};

	const { data, isLoading, hasNextPage, isFetchingNextPage, fetchNextPage } = useListMutualGroupsQuery({
		subject: profile.did,
		enabled: !profile.viewer?.blocking,
	});
	const items: Item[] = (data?.pages.flatMap((page) => page.convos) ?? []).filter(
		(item) => !removedConvoIds.has(item.id),
	);
	const hasMutualGroupChats = items.length > 0;

	const onEndReached = () => {
		if (isFetchingNextPage || !hasNextPage) {
			return;
		}
		fetchNextPage().catch(() => {});
	};

	const isBlocking = !!profile.viewer?.blocking;
	const blockActionLabel = isBlocking ? m['common.block.action.unblock']() : m['common.block.action.block']();

	return (
		<>
			<div className={css.header}>
				<Dialog.TitleRow>
					<Dialog.Title>
						{isBlocking
							? m['components.moderation.block.unblockTitle']()
							: m['components.moderation.block.confirmTitle']()}
					</Dialog.Title>
					<Dialog.Close />
				</Dialog.TitleRow>

				<Text color="textContrastMedium">
					{isBlocking
						? m['common.block.unblockHint']()
						: profile.associated?.labeler
							? m['components.moderation.block.descriptionLabels']()
							: m['components.moderation.block.description']()}
				</Text>
			</div>

			<Dialog.List
				data={items}
				estimateHeight={GROUP_ITEM_HEIGHT_ESTIMATE}
				keyExtractor={(item) => item.id}
				renderItem={({ item }) => (
					<MutualGroupChat
						currentConvoId={currentConvoId}
						onOptimisticallyRemoveConvo={onOptimisticallyRemoveConvo}
						onRestoreConvo={onRestoreConvo}
						profileDid={profile.did}
						view={item}
					/>
				)}
				onEndReached={onEndReached}
				ListFooterComponent={
					isFetchingNextPage ? <CenteredSpinner label={m['common.status.loading']()} size="xl" /> : null
				}
				ListHeaderComponent={
					hasMutualGroupChats ? (
						<Text className={css.groupsLabel} color="textContrastHigh" size="sm" weight="semiBold">
							{m['components.moderation.chat.mutualGroups']()}
						</Text>
					) : null
				}
				ListEmptyComponent={
					isLoading ? (
						<div className={css.loading}>
							<CenteredSpinner label={m['common.status.loading']()} size="_2xl" />
						</div>
					) : null
				}
			/>

			<Dialog.Footer>
				<Dialog.Actions direction="column">
					<Button
						color={isBlocking ? 'primary' : 'negative'}
						label={blockActionLabel}
						onClick={() => {
							handle.close();
							void onBlock();
						}}
						size="large"
					>
						<ButtonText>{blockActionLabel}</ButtonText>
					</Button>
					<Button
						color="secondary"
						label={m['common.a11y.closeDialog']()}
						onClick={() => handle.close()}
						size="large"
					>
						<ButtonText>{m['common.action.cancel']()}</ButtonText>
					</Button>
				</Dialog.Actions>
			</Dialog.Footer>
		</>
	);
}

function MutualGroupChat({
	currentConvoId,
	onOptimisticallyRemoveConvo,
	onRestoreConvo,
	profileDid,
	view,
}: {
	currentConvoId?: string;
	onOptimisticallyRemoveConvo: (convoId: string) => void;
	onRestoreConvo: (convoId: string) => void;
	profileDid: Did;
	view: ChatBskyConvoDefs.ConvoView;
}) {
	const { currentAccount } = useSession();
	const queryClient = useQueryClient();

	const convo = parseConvoView(view, currentAccount?.did);

	const { mutate: leaveConvo, isPending: isLeavePending } = useLeaveConvo(convo?.view.id, {
		onSuccess: () => {
			Toast.show(m['components.moderation.chat.leftToast']());
			void queryClient.invalidateQueries({
				queryKey: createListMutualGroupsQueryKey({ subject: profileDid }),
			});
		},
		onError: (error) => {
			onRestoreConvo(view.id);
			console.error('Error leaving group chat', error);
			let errorMessage = m['components.dms.leave.error.leave']();
			if (isNetworkError(error)) {
				errorMessage = m['common.error.network']();
			} else if (error instanceof ClientResponseError && error.error === 'InvalidConvo') {
				errorMessage = m['components.moderation.chat.error.notFound']();
			} else if (error instanceof ClientResponseError && error.error === 'OwnerCannotLeave') {
				errorMessage = m['components.moderation.chat.error.ownerCannotLeave']();
			}
			Toast.show(errorMessage, { type: 'error' });
		},
	});

	const { mutate: removeMembers, isPending: isRemovePending } = useRemoveFromGroupChat(convo?.view.id, {
		onSuccess: () => {
			Toast.show(m['components.moderation.chat.memberRemovedToast']());
			void queryClient.invalidateQueries({
				queryKey: createListMutualGroupsQueryKey({ subject: profileDid }),
			});
		},
		onError: (error) => {
			onRestoreConvo(view.id);
			console.error('Error removing group chat member', error);
			let errorMessage = m['components.moderation.chat.error.removeMember']();
			if (isNetworkError(error)) {
				errorMessage = m['common.error.network']();
			} else if (error instanceof ClientResponseError && error.error === 'InvalidConvo') {
				errorMessage = m['components.moderation.chat.error.notFound']();
			} else if (error instanceof ClientResponseError && error.error === 'InsufficientRole') {
				errorMessage = m['components.moderation.chat.error.mustBeOwnerToRemove']();
			}
			Toast.show(errorMessage, { type: 'error' });
		},
	});

	if (!convo || convo.kind !== 'group') {
		return null;
	}

	const owner = convo.primaryMember;
	const isViewerOwner = owner?.did != null && owner.did === currentAccount?.did;
	const isProfileOwner = owner?.did != null && owner.did === profileDid;
	const isCurrentConvo = view.id === currentConvoId;

	return (
		<div className={css.row}>
			<div className={css.rowMain}>
				<AvatarBubbles profiles={convo.members} size={40} />
				<div className={css.rowText}>
					<Text numberOfLines={1} size="md" weight="semiBold">
						{convo.details.name}
					</Text>
					{isViewerOwner ? (
						<Text color="textContrastMedium" size="xs">
							{m['components.moderation.chat.youOwn']()}
						</Text>
					) : isProfileOwner ? (
						<Text color="textContrastMedium" size="xs">
							{m['components.moderation.chat.theyOwn']()}
						</Text>
					) : null}
				</div>
			</div>
			{isViewerOwner ? (
				<Button
					color="negative_subtle"
					disabled={isRemovePending}
					label={m['components.moderation.chat.removeMember']()}
					onClick={() => {
						onOptimisticallyRemoveConvo(view.id);
						removeMembers({ members: [profileDid] });
					}}
					size="small"
				>
					<ButtonText>{m['components.moderation.chat.removeMember']()}</ButtonText>
					{isRemovePending && <Spinner color="white" label={m['common.status.saving']()} size="sm" />}
				</Button>
			) : isCurrentConvo ? (
				<Text color="textContrastMedium" size="sm" weight="medium">
					{m['components.moderation.chat.current']()}
				</Text>
			) : (
				<Button
					color="secondary"
					disabled={isLeavePending}
					label={m['common.chat.action.leave']()}
					onClick={() => {
						onOptimisticallyRemoveConvo(view.id);
						leaveConvo();
					}}
					size="small"
				>
					<ButtonText>{m['common.chat.action.leave']()}</ButtonText>
					{isLeavePending && <Spinner color="default" label={m['common.status.saving']()} size="sm" />}
				</Button>
			)}
		</div>
	);
}
