// SPDX-License-Identifier: AGPL-3.0-or-later

import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes';
import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError';
import type {RichEmbedRequest} from '@fluxer/schema/src/domains/message/MessageRequestSchemas';
import type {AttachmentID, ChannelID} from '../../../BrandedTypes';
import type {AttachmentRequestData} from '../../AttachmentDTOs';
import type {RichEmbedMediaWithMetadata} from '../../EmbedTypes';
import {makeAttachmentCdnUrl} from './MessageHelpers';

interface ProcessedAttachment {
	attachment_id: AttachmentID;
	filename: string;
	width: number | null;
	height: number | null;
	content_type: string;
	content_hash: string | null;
	placeholder: string | null;
	flags: number;
	duration: number | null;
	nsfw: boolean | null;
}

interface RichEmbedRequestWithMetadata extends Omit<RichEmbedRequest, 'image' | 'thumbnail'> {
	image?: RichEmbedMediaWithMetadata | null;
	thumbnail?: RichEmbedMediaWithMetadata | null;
}

const SUPPORTED_IMAGE_EXTENSIONS = new Set(['png', 'jpg', 'jpeg', 'webp', 'gif']);

export class MessageEmbedAttachmentResolver {
	validateAttachmentReferences(params: {
		embeds: Array<RichEmbedRequest> | undefined;
		attachments: Array<AttachmentRequestData> | undefined;
		existingAttachments?: Array<{
			filename: string;
		}>;
	}): void {
		if (!params.embeds || params.embeds.length === 0) {
			return;
		}
		let availableFilenames: Set<string> | undefined;
		if (params.attachments !== undefined) {
			const filenames = params.attachments
				.map((att) => ('filename' in att ? att.filename : undefined))
				.filter((filename): filename is string => typeof filename === 'string' && filename.length > 0);
			if (filenames.length > 0) {
				availableFilenames = new Set(filenames);
			}
		} else if (params.existingAttachments) {
			availableFilenames = new Set(params.existingAttachments.map((att) => att.filename));
		}
		if (!availableFilenames || availableFilenames.size === 0) {
			for (const embed of params.embeds) {
				if (embed.image?.url?.startsWith('attachment://') || embed.thumbnail?.url?.startsWith('attachment://')) {
					throw InputValidationError.fromCode(
						'embeds',
						ValidationErrorCodes.CANNOT_REFERENCE_ATTACHMENTS_WITHOUT_ATTACHMENTS,
					);
				}
			}
			return;
		}
		const validateAttachmentReference = (filename: string, field: string, embedIndex: number) => {
			if (!availableFilenames.has(filename)) {
				throw InputValidationError.fromCode(
					`embeds[${embedIndex}].${field}`,
					ValidationErrorCodes.REFERENCED_ATTACHMENT_NOT_FOUND,
					{filename},
				);
			}
			const extension = filename.split('.').pop()?.toLowerCase();
			if (!extension || !SUPPORTED_IMAGE_EXTENSIONS.has(extension)) {
				throw InputValidationError.fromCode(
					`embeds[${embedIndex}].${field}`,
					ValidationErrorCodes.ATTACHMENT_MUST_BE_IMAGE,
					{filename},
				);
			}
		};
		for (let embedIndex = 0; embedIndex < params.embeds.length; embedIndex++) {
			const embed = params.embeds[embedIndex];
			if (embed.image?.url?.startsWith('attachment://')) {
				const filename = embed.image.url.slice(13);
				validateAttachmentReference(filename, 'image.url', embedIndex);
			}
			if (embed.thumbnail?.url?.startsWith('attachment://')) {
				const filename = embed.thumbnail.url.slice(13);
				validateAttachmentReference(filename, 'thumbnail.url', embedIndex);
			}
		}
	}

	collectReferencedAttachmentFilenames(embeds: Array<RichEmbedRequest> | undefined): Set<string> {
		const filenames = new Set<string>();
		if (!embeds) {
			return filenames;
		}
		for (const embed of embeds) {
			if (embed.image?.url?.startsWith('attachment://')) {
				filenames.add(embed.image.url.slice(13));
			}
			if (embed.thumbnail?.url?.startsWith('attachment://')) {
				filenames.add(embed.thumbnail.url.slice(13));
			}
		}
		return filenames;
	}

	resolveEmbedAttachmentUrls(params: {
		embeds: Array<RichEmbedRequest> | undefined;
		attachments: Array<ProcessedAttachment>;
		channelId: ChannelID;
	}): Array<RichEmbedRequestWithMetadata> | undefined {
		if (!params.embeds || params.embeds.length === 0) {
			return params.embeds as Array<RichEmbedRequestWithMetadata> | undefined;
		}
		const attachmentMap = new Map<
			string,
			{
				cdnUrl: string;
				metadata: ProcessedAttachment;
			}
		>();
		for (const attachment of params.attachments) {
			const cdnUrl = makeAttachmentCdnUrl(params.channelId, attachment.attachment_id, attachment.filename);
			attachmentMap.set(attachment.filename, {cdnUrl, metadata: attachment});
		}
		const resolveAttachmentUrl = (
			filename: string,
			field: string,
		): {
			cdnUrl: string;
			metadata: ProcessedAttachment;
		} => {
			const attachmentData = attachmentMap.get(filename);
			if (!attachmentData) {
				throw InputValidationError.fromCode(field, ValidationErrorCodes.REFERENCED_ATTACHMENT_NOT_FOUND, {filename});
			}
			const extension = filename.split('.').pop()?.toLowerCase();
			if (!extension || !SUPPORTED_IMAGE_EXTENSIONS.has(extension)) {
				throw InputValidationError.fromCode(field, ValidationErrorCodes.ATTACHMENT_MUST_BE_IMAGE, {filename});
			}
			return attachmentData;
		};
		return params.embeds.map((embed) => {
			const resolvedEmbed: RichEmbedRequestWithMetadata = {...embed};
			if (embed.image?.url?.startsWith('attachment://')) {
				const filename = embed.image.url.slice(13);
				const {cdnUrl, metadata} = resolveAttachmentUrl(filename, 'embeds.image.url');
				resolvedEmbed.image = {
					...embed.image,
					url: cdnUrl,
					_attachmentMetadata: {
						width: metadata.width,
						height: metadata.height,
						content_type: metadata.content_type,
						content_hash: metadata.content_hash,
						placeholder: metadata.placeholder,
						flags: metadata.flags,
						duration: metadata.duration,
						nsfw: metadata.nsfw,
					},
				};
			}
			if (embed.thumbnail?.url?.startsWith('attachment://')) {
				const filename = embed.thumbnail.url.slice(13);
				const {cdnUrl, metadata} = resolveAttachmentUrl(filename, 'embeds.thumbnail.url');
				resolvedEmbed.thumbnail = {
					...embed.thumbnail,
					url: cdnUrl,
					_attachmentMetadata: {
						width: metadata.width,
						height: metadata.height,
						content_type: metadata.content_type,
						content_hash: metadata.content_hash,
						placeholder: metadata.placeholder,
						flags: metadata.flags,
						duration: metadata.duration,
						nsfw: metadata.nsfw,
					},
				};
			}
			return resolvedEmbed;
		});
	}
}
