/**
 * JTZL_WebIRC_Chat - Message List Component
 *
 * @package   JTZL_WebIRC_Chat
 * @copyright Copyright (c) 2025, JT. G.
 * @license   GPL-3.0+
 * @since     3.0.0
 */

import { forwardRef, useMemo, memo, useState, useCallback } from 'react';
import type { ReactNode } from 'react';
import { Avatar } from '../ui/avatar';
import { ScrollArea } from '../ui/scroll-area';
import type { LogMessage } from '../../hooks/useMessageLog';
import { ImageMessage } from './ImageMessage';
import { ImagePreview } from './ImagePreview';
import type { ImageData } from '../../types/image';

interface MessageListProps {
	messages: LogMessage[];
	formatTimestamp: (date: Date) => string;
	className?: string;
	currentNick?: string;
	scrollContainerRef?: React.RefObject<HTMLDivElement>;
	getUserAvatar?: (username: string) => string | undefined;
}

interface ParsedMessage {
	id: string;
	timestamp: Date;
	type:
		| 'message'
		| 'system'
		| 'action'
		| 'notice'
		| 'error'
		| 'self'
		| 'debug'
		| 'image';
	user?: string;
	content: string;
	className?: string;
	images?: ImageData[];
	caption?: string;
}

/**
 * Check if message content contains image data.
 * Format: [IMG:id1,id2,id3] optional caption
 *
 * @since 3.1.0
 */
function isImageMessage(content: string): boolean {
	return /\[IMG:[^\]]+\]/.test(content);
}

/**
 * Parse image message to extract image IDs and caption.
 * Format: [IMG:id1,id2,id3] optional caption
 *
 * @since 3.1.0
 */
function parseImageMessage(content: string): {
	imageIds: string[];
	caption?: string;
} {
	const match = content.match(/\[IMG:([^\]]+)\](.*)$/);
	if (!match) {
		return { imageIds: [] };
	}

	const imageIds = match[1].split(',').map((id) => id.trim());
	const caption = match[2]?.trim() || undefined;

	return { imageIds, caption };
}

/**
 * Parse IRC message content to extract user and message parts.
 *
 * @since 3.0.0
 */
function parseMessageContent(
	content: string,
	className: string
): ParsedMessage['type'] {
	// Check for image messages first.
	if (isImageMessage(content)) {
		return 'image';
	}
	// Determine message type based on className and content patterns
	if (className === 'sys' || content.includes('• ')) return 'system';
	if (className === 'action' || content.includes('* ')) return 'action';
	if (
		className === 'notice' ||
		content.includes('! ') ||
		content.includes('[DEBUG]')
	)
		return 'notice';
	if (className === 'err') return 'error';
	if (className === 'self') return 'self';
	if (content.includes('▸ ')) return 'message';

	// More aggressive user message detection
	const withoutTimestamp = content.replace(/^\[\d{2}:\d{2}\] /, '');

	// If it starts with system patterns, it's definitely a system message
	if (
		withoutTimestamp.startsWith('[IRC]') ||
		withoutTimestamp.startsWith('[DEBUG]') ||
		withoutTimestamp.startsWith('[PM]') ||
		withoutTimestamp.startsWith('•') ||
		withoutTimestamp.startsWith('!')
	) {
		return 'system';
	}

	// Check for user message patterns
	// Look for pattern: "username" followed by text (no special prefixes)
	const spaceIndex = withoutTimestamp.indexOf(' ');
	if (spaceIndex > 0) {
		const potentialUsername = withoutTimestamp.substring(0, spaceIndex);
		// If it looks like a username (alphanumeric, no special system chars)
		if (
			potentialUsername.length > 0 &&
			/^[a-zA-Z0-9_\-\[\]{}\\|`]+$/.test(potentialUsername) &&
			!potentialUsername.includes('[') &&
			!potentialUsername.includes('•') &&
			!potentialUsername.includes('*')
		) {
			return 'message';
		}
	}

	return 'system'; // Default fallback
}

/**
 * Extract user and message from IRC message content.
 *
 * @since 3.0.0
 */
function extractUserAndMessage(
	content: string,
	type: ParsedMessage['type']
): { user?: string; message: string } {
	// Remove timestamp if present (various formats: [HH:MM], HH:MM at start)
	let withoutTimestamp = content.replace(/^\[\d{2}:\d{2}\]\s*/, '');
	withoutTimestamp = withoutTimestamp.replace(/^\d{2}:\d{2}\s+/, '');

	if (type === 'image') {
		// For image messages, extract username similar to regular messages.
		// Format: "username ▸ [IMG:id1,id2] caption"
		const arrowMatch = withoutTimestamp.match(/^(.+?) ▸ (.+)$/);
		if (arrowMatch) {
			return { user: arrowMatch[1], message: arrowMatch[2] };
		}

		// Alternative format: "username [IMG:id1,id2] caption"
		const spaceIndex = withoutTimestamp.indexOf(' ');
		if (spaceIndex > 0) {
			const username = withoutTimestamp.substring(0, spaceIndex);
			const message = withoutTimestamp.substring(spaceIndex + 1);
			return { user: username, message };
		}

		// No username found, return the whole message.
		return { message: withoutTimestamp };
	}

	if (type === 'message' || type === 'self') {
		// Special case: Handle [PM to target] format for outgoing private messages
		const pmToMatch = withoutTimestamp.match(/^\[PM to (.+?)\] (.+)$/);
		if (pmToMatch) {
			return { user: `[PM to ${pmToMatch[1]}]`, message: pmToMatch[2] };
		}

		// Format: "username ▸ message"
		const arrowMatch = withoutTimestamp.match(/^(.+?) ▸ (.+)$/);
		if (arrowMatch) {
			return { user: arrowMatch[1], message: arrowMatch[2] };
		}

		// Handle case where username has timestamp embedded in various formats
		// Pattern 1: "wp43823:33 message"
		let usernameWithTimeMatch = withoutTimestamp.match(
			/^([a-zA-Z0-9_\-\[\]{}\\|`]+)\d{2}:\d{2}\s+(.+)$/
		);
		if (usernameWithTimeMatch) {
			return {
				user: usernameWithTimeMatch[1],
				message: usernameWithTimeMatch[2],
			};
		}

		// Pattern 2: Handle case where there's no space after timestamp: "wp43823:33message"
		usernameWithTimeMatch = withoutTimestamp.match(
			/^([a-zA-Z0-9_\-\[\]{}\\|`]+)\d{2}:\d{2}(.+)$/
		);
		if (usernameWithTimeMatch) {
			return {
				user: usernameWithTimeMatch[1],
				message: usernameWithTimeMatch[2].trim(),
			};
		}

		// Alternative format: "username message" (without ▸)
		const spaceIndex = withoutTimestamp.indexOf(' ');
		if (spaceIndex > 0) {
			const username = withoutTimestamp.substring(0, spaceIndex);
			const message = withoutTimestamp.substring(spaceIndex + 1);
			return { user: username, message };
		}
	}

	if (type === 'action') {
		// Format: "* username action"
		const match = withoutTimestamp.match(/^\* (.+?) (.+)$/);
		if (match) {
			return { user: match[1], message: match[2] };
		}
	}

	// For system messages, notices, errors - no user extraction
	return { message: withoutTimestamp };
}

/**
 * Get avatar initials from username.
 *
 * @since 3.0.0
 */
function getAvatarInitials(username: string): string {
	return username.slice(0, 2).toUpperCase();
}

/**
 * Process message content to highlight @ mentions.
 * Uses IRC-compliant nickname pattern: starts with letter or special chars, followed by letters/numbers/special chars.
 * Only highlights mentions of the current user in the latest message.
 *
 * @since 3.0.0
 */
function processMessageMentions(
	content: string,
	currentNick?: string,
	isLatestMessage = false
): ReactNode {
	// IRC nickname pattern: starts with letter or special chars like _[]{}|\, followed by letters/numbers/special chars
	// This prevents matching things like @1-21-49-97.east.dxpn.ucom.ne.jp
	const parts = content.split(
		/(@[a-zA-Z_\[\]{}\\|`][a-zA-Z0-9_\[\]{}\\|`-]*)/g
	);

	return parts.map((part, index) => {
		if (part.startsWith('@')) {
			// Extract the nickname without the @ symbol
			const mentionedNick = part.slice(1);

			// Only highlight if:
			// 1. This is the latest message
			// 2. The mentioned nick matches the current user's nick (case-insensitive)
			// 3. We have a current nick to compare against
			const shouldHighlight =
				isLatestMessage &&
				currentNick &&
				mentionedNick.toLowerCase() === currentNick.toLowerCase();

			if (shouldHighlight) {
				return (
					<span
						key={index}
						className="bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 px-1 rounded font-medium"
					>
						{part}
					</span>
				);
			}
			// Return the mention without highlighting
			return <span key={index}>{part}</span>;
		}
		return part;
	});
}

/**
 * Get message type styling classes to match v0 app.
 *
 * @since 3.0.0
 */
function getMessageTypeClasses(type: ParsedMessage['type']): string {
	switch (type) {
		case 'system':
			return 'text-muted-foreground italic';
		case 'action':
		case 'notice':
			return 'text-accent font-medium';
		case 'error':
			return 'text-destructive font-medium';
		case 'self':
		case 'message':
		default:
			return 'text-card-foreground';
	}
}

/**
 * Get text wrapping classes based on message type.
 * User messages wrap for readability, system messages preserve formatting.
 *
 * @since 3.0.0
 */
function getMessageWrappingClasses(type: ParsedMessage['type']): string {
	switch (type) {
		case 'message':
		case 'self':
		case 'action':
			// User messages: wrap for readability
			return 'break-words overflow-wrap-anywhere';
		case 'system':
		case 'notice':
		case 'error':
		case 'debug':
		default:
			// System/debug messages: preserve formatting, allow horizontal scroll
			return 'whitespace-pre-wrap font-mono text-xs overflow-x-auto';
	}
}

/**
 * Individual message item component with memoization.
 *
 * @since 3.0.0
 */
const MessageItem = memo(
	({
		message,
		formatTimestamp,
		currentNick,
		isLatestMessage,
		onImageClick,
		getUserAvatar,
	}: {
		message: ParsedMessage;
		formatTimestamp: (date: Date) => string;
		currentNick?: string;
		isLatestMessage?: boolean;
		onImageClick?: (images: ImageData[], index: number) => void;
		getUserAvatar?: (username: string) => string | undefined;
	}) => {
		// For image messages, render with avatar.
		if (message.type === 'image' && message.images) {
			const sender = message.user || 'Unknown';
			const avatarUrl = getUserAvatar?.(sender);

			return (
				<div className="group w-full min-w-0">
					{/* First line: Avatar + Username + Timestamp */}
					<div className="flex items-center gap-2 mb-1 min-w-0">
						<Avatar
							className="h-6 w-6 flex-shrink-0"
							imageUrl={avatarUrl}
							fallbackText={getAvatarInitials(sender)}
						/>
						<span className="font-medium flex-shrink-0 text-primary">
							{sender}
						</span>
						<span className="text-xs text-muted-foreground ml-auto whitespace-nowrap flex-shrink-0">
							{formatTimestamp(message.timestamp)}
						</span>
					</div>
					{/* Second line: Image content */}
					<div className="pl-8">
						<ImageMessage
							images={message.images}
							timestamp={message.timestamp}
							sender={sender}
							onImageClick={(image, index) => {
								if (onImageClick && message.images) {
									onImageClick(message.images, index);
								}
							}}
							formatTimestamp={formatTimestamp}
							hideHeader={true}
						/>
						{/* Caption below images if present */}
						{message.caption && (
							<div className="mt-2 text-sm text-card-foreground">
								{message.caption}
							</div>
						)}
					</div>
				</div>
			);
		}

		// For user messages, use two-line layout: avatar+username+timestamp on first line, message on second line
		if (
			(message.type === 'message' ||
				message.type === 'self' ||
				message.type === 'action') &&
			message.user
		) {
			// Extract username for avatar from PM format if needed
			const avatarUser = message.user.startsWith('[PM to ')
				? message.user.slice(7, -1) // Extract "wp9124" from "[PM to wp9124]"
				: message.user;

			// Get avatar URL for user
			const avatarUrl = getUserAvatar?.(avatarUser);

			return (
				<div className="group w-full min-w-0">
					{/* First line: Avatar + Username + Timestamp */}
					<div className="flex items-center gap-2 mb-1 min-w-0">
						<Avatar
							className="h-6 w-6 flex-shrink-0"
							imageUrl={avatarUrl}
							fallbackText={getAvatarInitials(avatarUser)}
						/>
						<span
							className={`font-medium flex-shrink-0 ${message.type === 'action' ? 'text-accent' : 'text-primary'}`}
						>
							{message.user}
						</span>
						<span className="text-xs text-muted-foreground ml-auto whitespace-nowrap flex-shrink-0">
							{formatTimestamp(message.timestamp)}
						</span>
					</div>
					{/* Second line: Message content */}
					<div className="pl-8">
						<span
							className={`${getMessageTypeClasses(message.type)} ${getMessageWrappingClasses(message.type)} webirc-message-content`}
						>
							{processMessageMentions(
								message.content,
								currentNick,
								isLatestMessage
							)}
						</span>
					</div>
				</div>
			);
		}

		// For system messages, keep single line layout
		// Skip rendering if this is an image message that fell through (shouldn't happen but defensive)
		if (message.type === 'image') {
			return null;
		}

		return (
			<div className="flex gap-2 group w-full min-w-0">
				<div className="text-xs text-muted-foreground whitespace-nowrap flex-shrink-0 pt-0.5 min-w-[40px]">
					{formatTimestamp(message.timestamp)}
				</div>
				<div className="flex-1 min-w-0 overflow-x-auto">
					<span
						className={`${getMessageTypeClasses(message.type)} ${getMessageWrappingClasses(message.type)} webirc-message-content`}
					>
						{processMessageMentions(
							message.content,
							currentNick,
							isLatestMessage
						)}
					</span>
				</div>
			</div>
		);
	}
);

MessageItem.displayName = 'MessageItem';

/**
 * MessageList component for displaying IRC messages matching v0 app structure.
 *
 * @since 3.0.0
 */
const MessageListComponent = forwardRef<HTMLDivElement, MessageListProps>(
	(
		{
			messages,
			formatTimestamp,
			className,
			currentNick,
			scrollContainerRef,
			getUserAvatar,
		},
		ref
	) => {
		// State for image preview modal.
		const [previewState, setPreviewState] = useState<{
			isOpen: boolean;
			images: ImageData[];
			initialIndex: number;
		}>({
			isOpen: false,
			images: [],
			initialIndex: 0,
		});

		// Handle image click to open preview.
		const handleImageClick = useCallback(
			(images: ImageData[], index: number) => {
				setPreviewState({
					isOpen: true,
					images,
					initialIndex: index,
				});
			},
			[]
		);

		// Handle preview close.
		const handlePreviewClose = useCallback(() => {
			setPreviewState((prev) => ({ ...prev, isOpen: false }));
		}, []);

		// Parse messages to match v0 app format with grouping logic
		const parsedMessages = useMemo(() => {
			const parsed = messages.map((msg): ParsedMessage => {
				const type = parseMessageContent(
					msg.content,
					msg.className || ''
				);
				const { user, message } = extractUserAndMessage(
					msg.content,
					type
				);

				// Handle image messages.
				if (type === 'image') {
					const { imageIds, caption } = parseImageMessage(message);

					// Convert image IDs/URLs to ImageData objects.
					// Support both full URLs and filename-only format.
					const images: ImageData[] = imageIds.map(
						(idOrUrl, index) => {
							// Check if it's already a full URL.
							const isFullUrl =
								idOrUrl.startsWith('http://') ||
								idOrUrl.startsWith('https://') ||
								idOrUrl.startsWith('/');

							// For filenames, construct query string URL.
							const imageUrl = isFullUrl
								? idOrUrl
								: `/?chat_webirc_image=${encodeURIComponent(idOrUrl)}`;

							// Extract filename for alt text.
							let filename = `image-${index}`;
							try {
								if (isFullUrl && idOrUrl.includes('?')) {
									// Extract from query string.
									const url = new URL(
										idOrUrl,
										window.location.origin
									);
									filename =
										url.searchParams.get(
											'chat_webirc_image'
										) ||
										url.searchParams.get(
											'chat_webirc_avatar'
										) ||
										filename;
								} else {
									// Extract from path.
									filename =
										imageUrl.split('/').pop() || filename;
								}
							} catch {
								// Fallback if URL parsing fails.
								filename = idOrUrl;
							}

							const altText = filename
								.replace(/\.[^/.]+$/, '')
								.replace(/[_-]/g, ' ');

							// Create image data with initial fileSize of 0.
							// We'll fetch the actual size asynchronously.
							const imageData: ImageData = {
								id: idOrUrl,
								url: imageUrl,
								thumbnailUrl: imageUrl,
								alt: altText,
								fileSize: 0,
							};

							// Fetch file size asynchronously using HEAD request.
							fetch(imageUrl, { method: 'HEAD' })
								.then((response) => {
									const contentLength =
										response.headers.get('Content-Length');
									if (contentLength) {
										imageData.fileSize = parseInt(
											contentLength,
											10
										);
									}
								})
								.catch(() => {
									// Silently fail - fileSize will remain 0.
								});

							return imageData;
						}
					);

					return {
						id: msg.id,
						timestamp: msg.timestamp,
						type,
						user,
						content: message,
						className: msg.className,
						images,
						caption,
					};
				}

				return {
					id: msg.id,
					timestamp: msg.timestamp,
					type,
					user,
					content: message,
					className: msg.className,
				};
			});

			// Group consecutive messages from the same user
			const grouped: ParsedMessage[] = [];
			let lastUser: string | undefined;
			let lastTimestamp: Date | undefined;

			for (const msg of parsed) {
				const timeDiff = lastTimestamp
					? msg.timestamp.getTime() - lastTimestamp.getTime()
					: Infinity;

				// Group if same user and within 5 minutes (but not image messages).
				const shouldGroup =
					msg.user &&
					msg.user === lastUser &&
					msg.type === 'message' &&
					timeDiff < 5 * 60 * 1000;

				if (shouldGroup) {
					// Add to previous message group
					grouped[grouped.length - 1] = {
						...grouped[grouped.length - 1],
						content: `${grouped[grouped.length - 1].content}\n${msg.content}`,
					};
				} else {
					grouped.push(msg);
				}

				lastUser = msg.user;
				lastTimestamp = msg.timestamp;
			}

			return grouped;
		}, [messages]);

		return (
			<ScrollArea
				ref={scrollContainerRef}
				className={`${className || ''}`}
			>
				<div
					className="space-y-2 w-full min-w-0"
					role="log"
					aria-live="polite"
					aria-relevant="additions"
				>
					{parsedMessages.map((msg, index) => (
						<MessageItem
							key={msg.id}
							message={msg}
							formatTimestamp={formatTimestamp}
							currentNick={currentNick}
							isLatestMessage={
								index === parsedMessages.length - 1
							}
							onImageClick={handleImageClick}
							getUserAvatar={getUserAvatar}
						/>
					))}
					<div ref={ref} />
				</div>

				{/* Image preview modal */}
				<ImagePreview
					images={previewState.images}
					initialIndex={previewState.initialIndex}
					isOpen={previewState.isOpen}
					onClose={handlePreviewClose}
				/>
			</ScrollArea>
		);
	}
);

MessageListComponent.displayName = 'MessageListComponent';

// Export memoized version to prevent unnecessary re-renders
export const MessageList = memo(MessageListComponent);
