import { useState } from 'react';

import type {
	AnyProfileView,
	AppBskyNotificationDefs,
	AppBskyNotificationListActivitySubscriptions,
} from '@atcute/bluesky';
import type { ModerationOptions } from '@atcute/bluesky-moderation';
import { ok } from '@atcute/client';

import { type InfiniteData, useMutation, useQueryClient } from '@tanstack/react-query';

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

import { updateProfileShadow } from '#/state/cache/profile-shadow';
import { RQKEY_getActivitySubscriptions } from '#/state/queries/activity-subscriptions';
import { getClients } from '#/state/session';

import * as Dialog from '#/components/Dialog';
import * as Toggle from '#/components/forms/Toggle';
import { Spinner } from '#/components/Spinner';
import { Stack } from '#/components/Stack';
import { Text } from '#/components/Text';
import * as Toast from '#/components/Toast';
import { Admonition } from '#/components/web/Admonition';
import { Button, type ButtonProps, ButtonText } from '#/components/web/Button';
import * as ProfileCard from '#/components/web/ProfileCard';

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

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

type SubscriptionChoice = 'all' | 'off' | 'posts';

// the three reachable subscription states; `reply` can't be set without `post`.
const CHOICE_STATES: Record<SubscriptionChoice, { post: boolean; reply: boolean }> = {
	all: { post: true, reply: true },
	off: { post: false, reply: false },
	posts: { post: true, reply: false },
};

export function SubscribeProfileDialog({
	handle,
	profile,
	moderationOpts,
	includeProfile,
}: {
	handle: Dialog.DialogHandle;
	profile: AnyProfileView;
	moderationOpts: ModerationOptions;
	/** shows whose notifications are being edited. for callers opening this away from the profile itself. */
	includeProfile?: boolean;
}) {
	return (
		<Dialog.Root handle={handle}>
			<Dialog.Popup size="narrow">
				<DialogInner
					handle={handle}
					profile={profile}
					moderationOpts={moderationOpts}
					includeProfile={includeProfile}
				/>
			</Dialog.Popup>
		</Dialog.Root>
	);
}

function DialogInner({
	handle,
	profile,
	moderationOpts,
	includeProfile,
}: {
	handle: Dialog.DialogHandle;
	profile: AnyProfileView;
	moderationOpts: ModerationOptions;
	includeProfile?: boolean;
}) {
	const { appview } = getClients();
	const queryClient = useQueryClient();
	const initialState = parseActivitySubscription(profile.viewer?.activitySubscription);
	const [state, setState] = useState(initialState);

	const selected: SubscriptionChoice = state.post ? (state.reply ? 'all' : 'posts') : 'off';

	const onSelect = ([value]: string[]) => {
		// the group is a radio set over `CHOICE_STATES`, but `Toggle.Group` reports a bare `string[]`
		switch (value) {
			case 'all':
			case 'off':
			case 'posts': {
				setState(CHOICE_STATES[value]);
				break;
			}
		}
	};

	const {
		mutate: saveChanges,
		isPending: isSaving,
		error,
	} = useMutation({
		mutationFn: async (activitySubscription: Omit<AppBskyNotificationDefs.ActivitySubscription, '$type'>) => {
			await ok(
				appview.post('app.bsky.notification.putActivitySubscription', {
					input: {
						subject: profile.did,
						activitySubscription,
					},
				}),
			);
		},
		onSuccess: (_data, activitySubscription) => {
			handle.close();
			updateProfileShadow(queryClient, profile.did, {
				activitySubscription,
			});

			if (!activitySubscription.post && !activitySubscription.reply) {
				Toast.show(
					m['components.activityNotifications.unsubscribedToast']({
						handle: `@${profile.handle}`,
					}),
					{
						type: 'success',
					},
				);

				// filter out the subscription
				queryClient.setQueryData(
					RQKEY_getActivitySubscriptions,
					(old?: InfiniteData<AppBskyNotificationListActivitySubscriptions.$output>) => {
						if (!old) {
							return old;
						}
						return {
							...old,
							pages: old.pages.map((page) => ({
								...page,
								subscriptions: page.subscriptions.filter((item) => item.did !== profile.did),
							})),
						};
					},
				);
			} else {
				if (!initialState.post && !initialState.reply) {
					Toast.show(
						m['components.activityNotifications.subscribedToast']({
							handle: `@${profile.handle}`,
						}),
						{
							type: 'success',
						},
					);
				} else {
					Toast.show(m['components.activityNotifications.savedToast'](), {
						type: 'success',
					});
				}
			}
		},
		onError: (err) => {
			console.error('Could not save activity subscription', err);
		},
	});

	const isDirty = state.post !== initialState.post || state.reply !== initialState.reply;
	const hasAny = state.post || state.reply;

	let buttonProps: Omit<ButtonProps, 'children'>;
	if (isDirty) {
		buttonProps = {
			label: m['common.action.saveChanges'](),
			color: hasAny ? 'primary' : 'negative',
			onClick: () => saveChanges(state),
			disabled: isSaving,
		};
	} else {
		buttonProps = {
			label: m['common.action.saveChanges'](),
			color: 'secondary',
			disabled: true,
		};
	}

	return (
		<Stack gap="xl">
			<Stack gap="xs">
				<Dialog.TitleRow>
					<Dialog.Title>{m['components.activityNotifications.subscribe']()}</Dialog.Title>
					<Dialog.Close />
				</Dialog.TitleRow>
				<Text color="textContrastMedium" size="md">
					{m['components.activityNotifications.activityHint']()}
				</Text>
			</Stack>

			{includeProfile && (
				<ProfileCard.Header>
					<ProfileCard.Avatar profile={profile} moderationOpts={moderationOpts} disabledPreview />
					<ProfileCard.NameAndHandle profile={profile} moderationOpts={moderationOpts} />
				</ProfileCard.Header>
			)}

			<Toggle.Group
				className={styles.radioList}
				label={m['components.activityNotifications.title']()}
				onChange={onSelect}
				type="radio"
				values={[selected]}
			>
				<Toggle.RadioItem label={m['components.activityNotifications.postsAndReplies']()} value="all">
					<Toggle.Panel>
						<Toggle.RadioIndicator />
						<Toggle.PanelText>{m['components.activityNotifications.postsAndReplies']()}</Toggle.PanelText>
					</Toggle.Panel>
				</Toggle.RadioItem>
				<Toggle.RadioItem label={m['components.activityNotifications.postsOnly']()} value="posts">
					<Toggle.Panel>
						<Toggle.RadioIndicator />
						<Toggle.PanelText>{m['components.activityNotifications.postsOnly']()}</Toggle.PanelText>
					</Toggle.Panel>
				</Toggle.RadioItem>
				<Toggle.RadioItem label={m['common.status.off']()} value="off">
					<Toggle.Panel>
						<Toggle.RadioIndicator />
						<Toggle.PanelText>{m['common.status.off']()}</Toggle.PanelText>
					</Toggle.Panel>
				</Toggle.RadioItem>
			</Toggle.Group>

			{error && (
				<Admonition type="error">
					{m['components.activityNotifications.saveError']({ error: cleanError(error) })}
				</Admonition>
			)}

			<Dialog.Actions>
				<Button {...buttonProps} variant="solid">
					<ButtonText>{buttonProps.label}</ButtonText>
					{isSaving && <Spinner color="white" label={m['common.status.saving']()} size="sm" />}
				</Button>
			</Dialog.Actions>
		</Stack>
	);
}

function parseActivitySubscription(
	sub?: AppBskyNotificationDefs.ActivitySubscription,
): Omit<AppBskyNotificationDefs.ActivitySubscription, '$type'> {
	if (!sub) {
		return { post: false, reply: false };
	}
	const { post, reply } = sub;
	return { post, reply };
}
