/*
 * Copyright (C) 2026 Fluxer Contributors
 *
 * This file is part of Fluxer.
 *
 * Fluxer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Fluxer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
 */

import {RTC_REGION_ID_MAX_LENGTH, RTC_REGION_ID_MIN_LENGTH} from '@fluxer/constants/src/LimitConstants';
import {ChannelResponse, RtcRegionResponse} from '@fluxer/schema/src/domains/channel/ChannelSchemas';
import {GuildEmojiResponse, GuildStickerResponse} from '@fluxer/schema/src/domains/guild/GuildEmojiSchemas';
import {GuildMemberResponse} from '@fluxer/schema/src/domains/guild/GuildMemberSchemas';
import {GuildResponse} from '@fluxer/schema/src/domains/guild/GuildResponseSchemas';
import {GuildRoleResponse} from '@fluxer/schema/src/domains/guild/GuildRoleSchemas';
import {FavoriteMemeResponse} from '@fluxer/schema/src/domains/meme/MemeSchemas';
import {CustomStatusPayload} from '@fluxer/schema/src/domains/user/UserRequestSchemas';
import {
	CustomStatusResponse,
	RelationshipResponse,
	UserGuildSettingsResponse,
	UserPrivateResponse,
	UserSettingsResponse,
} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
import {createStringType, SnowflakeStringType, SnowflakeType} from '@fluxer/schema/src/primitives/SchemaPrimitives';
import {z} from 'zod';

export const ReadStateResponse = z.object({
	id: SnowflakeStringType.describe('The channel ID for this read state'),
	mention_count: z.number().describe('Number of unread mentions in the channel'),
	last_message_id: SnowflakeStringType.nullish().describe('ID of the last read message'),
	last_pin_timestamp: z.string().nullish().describe('Timestamp of the last pinned message'),
});

export type ReadStateResponse = z.infer<typeof ReadStateResponse>;

export const RpcRequest = z.discriminatedUnion('type', [
	z.object({
		type: z.literal('session').describe('Request type for session initialization'),
		token: createStringType().describe('Authentication token for the session'),
		version: z.literal(1).describe('RPC protocol version'),
		ip: createStringType(1, 45).optional().describe('Client IP address'),
		latitude: createStringType(1, 32).optional().describe('Client latitude for region selection'),
		longitude: createStringType(1, 32).optional().describe('Client longitude for region selection'),
	}),
	z.object({
		type: z.literal('guild').describe('Request type for fetching guild data'),
		guild_id: SnowflakeType.describe('ID of the guild to fetch'),
	}),
	z.object({
		type: z.literal('log_guild_crash').describe('Request type for logging guild crashes'),
		guild_id: SnowflakeType.describe('ID of the guild that crashed'),
		stacktrace: z.string().describe('Error stacktrace from the crash'),
	}),
	z.object({
		type: z.literal('get_user_guild_settings').describe('Request type for fetching user guild settings'),
		user_ids: z.array(SnowflakeType).describe('IDs of users to fetch settings for'),
		guild_id: SnowflakeType.describe('ID of the guild'),
	}),
	z.object({
		type: z.literal('get_push_subscriptions').describe('Request type for fetching push notification subscriptions'),
		user_ids: z.array(SnowflakeType).describe('IDs of users to fetch subscriptions for'),
	}),
	z.object({
		type: z.literal('get_badge_counts').describe('Request type for fetching notification badge counts'),
		user_ids: z.array(SnowflakeType).describe('IDs of users to fetch badge counts for'),
	}),
	z.object({
		type: z.literal('geoip_lookup').describe('Request type for IP geolocation lookup'),
		ip: createStringType(1, 45).describe('IP address to lookup'),
	}),
	z.object({
		type: z.literal('delete_push_subscriptions').describe('Request type for deleting push notification subscriptions'),
		subscriptions: z
			.array(
				z.object({
					user_id: SnowflakeType.describe('ID of the user'),
					subscription_id: createStringType().describe('ID of the subscription to delete'),
				}),
			)
			.describe('List of subscriptions to delete'),
	}),
	z.object({
		type: z.literal('get_user_blocked_ids').describe('Request type for fetching blocked user IDs'),
		user_ids: z.array(SnowflakeType).describe('IDs of users to fetch blocked lists for'),
	}),
	z.object({
		type: z.literal('voice_get_token').describe('Request type for getting voice connection token'),
		guild_id: SnowflakeType.optional().describe('ID of the guild for the voice channel'),
		channel_id: SnowflakeType.describe('ID of the voice channel'),
		user_id: SnowflakeType.describe('ID of the user joining voice'),
		connection_id: createStringType().optional().describe('Existing connection ID for reconnection'),
		rtc_region: createStringType(RTC_REGION_ID_MIN_LENGTH, RTC_REGION_ID_MAX_LENGTH)
			.optional()
			.describe(
				`Preferred voice region for the connection (${RTC_REGION_ID_MIN_LENGTH}-${RTC_REGION_ID_MAX_LENGTH} characters)`,
			),
		latitude: createStringType(1, 32).optional().describe('Client latitude for region selection'),
		longitude: createStringType(1, 32).optional().describe('Client longitude for region selection'),
		can_speak: z.boolean().optional().describe('Whether the user can speak in the channel'),
		can_stream: z.boolean().optional().describe('Whether the user can stream in the channel'),
		can_video: z.boolean().optional().describe('Whether the user can use video in the channel'),
		token_nonce: createStringType(1, 64).optional().describe('Token nonce for replay prevention'),
	}),
	z.object({
		type: z.literal('kick_temporary_member').describe('Request type for kicking temporary guild members'),
		user_id: SnowflakeType.describe('ID of the user to kick'),
		guild_ids: z.array(SnowflakeType).describe('IDs of guilds to kick the user from'),
	}),
	z.object({
		type: z
			.literal('voice_force_disconnect_participant')
			.describe('Request type for force disconnecting a voice participant'),
		guild_id: SnowflakeType.optional().describe('ID of the guild'),
		channel_id: SnowflakeType.describe('ID of the voice channel'),
		user_id: SnowflakeType.describe('ID of the user to disconnect'),
		connection_id: createStringType().describe('Connection ID of the user'),
	}),
	z.object({
		type: z.literal('voice_update_participant').describe('Request type for updating voice participant state'),
		guild_id: SnowflakeType.optional().describe('ID of the guild'),
		channel_id: SnowflakeType.describe('ID of the voice channel'),
		user_id: SnowflakeType.describe('ID of the user to update'),
		mute: z.boolean().describe('Whether the user is muted'),
		deaf: z.boolean().describe('Whether the user is deafened'),
	}),
	z.object({
		type: z
			.literal('voice_force_disconnect_channel')
			.describe('Request type for force disconnecting all participants from a channel'),
		guild_id: SnowflakeType.optional().describe('ID of the guild'),
		channel_id: SnowflakeType.describe('ID of the voice channel to clear'),
	}),
	z.object({
		type: z
			.literal('voice_update_participant_permissions')
			.describe('Request type for updating voice participant permissions'),
		guild_id: SnowflakeType.optional().describe('ID of the guild'),
		channel_id: SnowflakeType.describe('ID of the voice channel'),
		user_id: SnowflakeType.describe('ID of the user to update'),
		connection_id: createStringType().describe('Connection ID of the user'),
		can_speak: z.boolean().describe('Whether the user can speak'),
		can_stream: z.boolean().describe('Whether the user can stream'),
		can_video: z.boolean().describe('Whether the user can use video'),
	}),
	z.object({
		type: z.literal('call_ended').describe('Request type for notifying that a call has ended'),
		channel_id: SnowflakeType.describe('ID of the channel where the call ended'),
		message_id: SnowflakeType.describe('ID of the call start message'),
		participants: z.array(SnowflakeType).describe('IDs of users who participated in the call'),
		ended_timestamp: z.number().describe('Unix timestamp when the call ended'),
	}),
	z.object({
		type: z.literal('get_dm_channel').describe('Request type for fetching a DM channel'),
		channel_id: SnowflakeType.describe('ID of the DM channel'),
		user_id: SnowflakeType.describe('ID of the user requesting the channel'),
	}),
	z.object({
		type: z.literal('validate_custom_status').describe('Request type for validating a custom status'),
		user_id: SnowflakeType.describe('ID of the user'),
		custom_status: CustomStatusPayload.nullish().describe('Custom status data to validate'),
	}),
]);

export type RpcRequest = z.infer<typeof RpcRequest>;

export const RpcResponseSessionData = z.object({
	auth_session_id_hash: z.string().nullish().describe('Hash of the authentication session ID'),
	user: UserPrivateResponse.describe('Private user data for the authenticated user'),
	user_settings: UserSettingsResponse.nullish().describe('User settings configuration'),
	user_guild_settings: z.array(UserGuildSettingsResponse).describe('Per-guild settings for the user'),
	notes: z.record(SnowflakeStringType, z.string()).describe('User notes keyed by user ID'),
	read_states: z.array(ReadStateResponse).describe('Read state for each channel'),
	private_channels: z.array(ChannelResponse).describe('List of DM and group DM channels'),
	relationships: z.array(RelationshipResponse).describe('User relationships (friends, blocked, etc.)'),
	favorite_memes: z.array(FavoriteMemeResponse).describe('List of user favorite memes'),
	guild_ids: z.array(SnowflakeStringType).describe('IDs of guilds the user is a member of'),
	pinned_dms: z.array(SnowflakeStringType).describe('IDs of pinned DM channels'),
	country_code: z.string().describe('Two-letter country code from IP geolocation'),
	rtc_regions: z.array(RtcRegionResponse).describe('Available voice server regions'),
	version: z.number().int().describe('Session data version for cache invalidation'),
});

export type RpcResponseSessionData = z.infer<typeof RpcResponseSessionData>;

export const RpcResponseGuildData = z.object({
	guild: GuildResponse.describe('Guild information'),
	roles: z.array(GuildRoleResponse).describe('List of roles in the guild'),
	channels: z.array(ChannelResponse).describe('List of channels in the guild'),
	emojis: z.array(GuildEmojiResponse).describe('List of custom emojis in the guild'),
	stickers: z.array(GuildStickerResponse).describe('List of custom stickers in the guild'),
	members: z.array(GuildMemberResponse).describe('List of guild members'),
});

export type RpcResponseGuildData = z.infer<typeof RpcResponseGuildData>;

export const RpcResponseValidateCustomStatus = z.object({
	custom_status: CustomStatusResponse.nullish().describe('Validated custom status or null if invalid'),
});

export type RpcResponseValidateCustomStatus = z.infer<typeof RpcResponseValidateCustomStatus>;

export const RpcResponse = z.discriminatedUnion('type', [
	z.object({
		type: z.literal('session').describe('Response type for session initialization'),
		data: RpcResponseSessionData.describe('Session initialization data'),
	}),
	z.object({
		type: z.literal('log_guild_crash').describe('Response type for guild crash logging'),
		data: z
			.object({
				success: z.boolean().describe('Whether the crash was logged successfully'),
			})
			.describe('Crash logging result'),
	}),
	z.object({
		type: z.literal('guild').describe('Response type for guild data'),
		data: RpcResponseGuildData.describe('Guild data'),
	}),
	z.object({
		type: z.literal('get_user_guild_settings').describe('Response type for user guild settings'),
		data: z
			.object({
				user_guild_settings: z
					.array(UserGuildSettingsResponse.nullable())
					.describe('Guild settings for each requested user'),
			})
			.describe('User guild settings data'),
	}),
	z.object({
		type: z.literal('get_push_subscriptions').describe('Response type for push subscriptions'),
		data: z
			.record(
				SnowflakeStringType,
				z.array(
					z.object({
						subscription_id: z.string().describe('Unique identifier for the subscription'),
						endpoint: z.string().describe('Push notification endpoint URL'),
						p256dh_key: z.string().describe('P-256 Diffie-Hellman public key'),
						auth_key: z.string().describe('Authentication secret key'),
					}),
				),
			)
			.describe('Push subscriptions keyed by user ID'),
	}),
	z.object({
		type: z.literal('delete_push_subscriptions').describe('Response type for push subscription deletion'),
		data: z.object({success: z.boolean().describe('Whether the deletion was successful')}).describe('Deletion result'),
	}),
	z.object({
		type: z.literal('get_user_blocked_ids').describe('Response type for blocked user IDs'),
		data: z.record(SnowflakeStringType, z.array(SnowflakeStringType)).describe('Blocked user IDs keyed by user ID'),
	}),
	z.object({
		type: z.literal('voice_get_token').describe('Response type for voice connection token'),
		data: z
			.object({
				token: z.string().describe('Voice server authentication token'),
				endpoint: z.string().describe('Voice server endpoint URL'),
				connectionId: z.string().describe('Unique connection identifier'),
				tokenNonce: z.string().describe('Token nonce for webhook confirmation'),
			})
			.describe('Voice connection credentials'),
	}),
	z.object({
		type: z.literal('kick_temporary_member').describe('Response type for temporary member kick'),
		data: z
			.object({
				success: z.boolean().describe('Whether the kick was successful'),
			})
			.describe('Kick result'),
	}),
	z.object({
		type: z.literal('get_badge_counts').describe('Response type for badge counts'),
		data: z
			.object({
				badge_counts: z.record(SnowflakeStringType, z.number().int().min(0)).describe('Badge counts keyed by user ID'),
			})
			.describe('Badge count data'),
	}),
	z.object({
		type: z.literal('voice_force_disconnect_participant').describe('Response type for force disconnect participant'),
		data: z
			.object({
				success: z.boolean().describe('Whether the disconnect was successful'),
			})
			.describe('Disconnect result'),
	}),
	z.object({
		type: z.literal('voice_update_participant').describe('Response type for voice participant update'),
		data: z
			.object({
				success: z.boolean().describe('Whether the update was successful'),
			})
			.describe('Update result'),
	}),
	z.object({
		type: z.literal('voice_force_disconnect_channel').describe('Response type for force disconnect channel'),
		data: z
			.object({
				success: z.boolean().describe('Whether the operation was successful'),
				disconnected_count: z.number().optional().describe('Number of participants disconnected'),
				message: z.string().optional().describe('Additional status message'),
			})
			.describe('Channel disconnect result'),
	}),
	z.object({
		type: z.literal('voice_update_participant_permissions').describe('Response type for voice permissions update'),
		data: z
			.object({
				success: z.boolean().describe('Whether the permissions update was successful'),
			})
			.describe('Permissions update result'),
	}),
	z.object({
		type: z.literal('call_ended').describe('Response type for call ended notification'),
		data: z
			.object({
				success: z.boolean().describe('Whether the notification was processed successfully'),
			})
			.describe('Call ended result'),
	}),
	z.object({
		type: z.literal('validate_custom_status').describe('Response type for custom status validation'),
		data: RpcResponseValidateCustomStatus.describe('Custom status validation result'),
	}),
	z.object({
		type: z.literal('geoip_lookup').describe('Response type for IP geolocation lookup'),
		data: z.object({country_code: z.string().describe('Two-letter country code')}).describe('Geolocation result'),
	}),
	z.object({
		type: z.literal('get_dm_channel').describe('Response type for DM channel fetch'),
		data: z
			.object({
				channel: ChannelResponse.nullish().describe('The DM channel or null if not found'),
			})
			.describe('DM channel result'),
	}),
]);

export type RpcResponse = z.infer<typeof RpcResponse>;
