/**
 * CustomEmojisTab — server-settings Custom Emoji surface, ported
 * from the Fluxer GuildEmojiTab design. Shows the emoji slot card
 * (Static/Animated counts + Upload button + help copy), a drag-and-
 * drop zone, and a per-group table (Non-Animated / Animated) of the
 * server's current uploads with rename-in-place + delete-on-hover.
 *
 * Backend: `api.customEmojis.list / upload / rename / remove`. The
 * component infers the `animated` flag from the uploaded file's
 * MIME type (GIF/APNG → animated). MIME-based classification only,
 * no frame inspection.
 */
import { useMutation, useQuery } from 'convex/react';
import {
	MagnifyingGlass,
	Trash,
	UploadSimple,
} from '@phosphor-icons/react';
import { useMemo, useRef, useState } from 'react';
import { Avatar } from '@discord-clone/ui';
import { api } from '../../../../../convex/_generated/api';
import type { Id } from '../../../../../convex/_generated/dataModel';
import { useIsMobile } from '../../hooks/useIsMobile';
import styles from './CustomEmojisTab.module.css';

interface CustomEmojiDoc {
	_id: Id<'customEmojis'>;
	name: string;
	src: string;
	createdAt: number;
	animated: boolean;
	uploadedById: Id<'userProfiles'>;
	uploadedByUsername: string;
	uploadedByDisplayName: string | null;
	uploadedByAvatarUrl: string | null;
}

const ACCEPTED_MIME =
	'image/png,image/gif,image/webp,image/jpeg,image/apng';

function isAnimatedMime(mime: string): boolean {
	const m = mime.toLowerCase();
	return m === 'image/gif' || m === 'image/apng';
}

/** Turn a file name like `"cat face!.gif"` into a valid emoji
 *  shortcode (`"cat_face"`), matching the server's validation. */
function sanitizeEmojiName(filename: string): string {
	const stem = filename.replace(/\.[^.]+$/, '');
	const clean = stem
		.toLowerCase()
		.replace(/[^a-z0-9_]/g, '_')
		.replace(/_+/g, '_')
		.replace(/^_+|_+$/g, '')
		.slice(0, 32);
	if (clean.length >= 2) return clean;
	return 'emoji';
}

export function CustomEmojisTab() {
	const isMobile = useIsMobile();
	const userId =
		typeof localStorage !== 'undefined'
			? (localStorage.getItem('userId') as Id<'userProfiles'> | null)
			: null;

	const emojis = (useQuery(api.customEmojis.list, {}) ??
		[]) as CustomEmojiDoc[];
	const generateUploadUrl = useMutation(api.files.generateUploadUrl);
	const uploadEmoji = useMutation(api.customEmojis.upload);
	const renameEmoji = useMutation(api.customEmojis.rename);
	const removeEmoji = useMutation(api.customEmojis.remove);

	const fileInputRef = useRef<HTMLInputElement>(null);
	const [uploading, setUploading] = useState(false);
	const [search, setSearch] = useState('');
	const [status, setStatus] = useState<{
		type: 'ok' | 'err';
		message: string;
	} | null>(null);
	const [isDraggingFiles, setIsDraggingFiles] = useState(false);
	// Inline rename draft per emoji id — `undefined` means "no local
	// draft, read the server name" so the query reactively updates
	// rows that other clients renamed.
	const [nameDrafts, setNameDrafts] = useState<Record<string, string>>({});

	const filtered = useMemo(() => {
		const q = search.trim().toLowerCase();
		if (!q) return emojis;
		return emojis.filter((e) => e.name.toLowerCase().includes(q));
	}, [emojis, search]);

	const staticEmojis = useMemo(
		() => filtered.filter((e) => !e.animated),
		[filtered],
	);
	const animatedEmojis = useMemo(
		() => filtered.filter((e) => e.animated),
		[filtered],
	);

	const uploadFiles = async (files: FileList | File[]) => {
		if (!userId) {
			setStatus({ type: 'err', message: 'You must be logged in.' });
			return;
		}
		const list = Array.from(files).filter((f) => f.type.startsWith('image/'));
		if (list.length === 0) return;
		setUploading(true);
		setStatus(null);
		let okCount = 0;
		try {
			for (const file of list) {
				const animated = isAnimatedMime(file.type);
				try {
					const uploadUrl = await generateUploadUrl({});
					const res = await fetch(uploadUrl, {
						method: 'POST',
						headers: { 'Content-Type': file.type },
						body: file,
					});
					if (!res.ok) throw new Error(`Upload failed (${res.status})`);
					const { storageId } = (await res.json()) as {
						storageId: Id<'_storage'>;
					};
					await uploadEmoji({
						userId,
						name: sanitizeEmojiName(file.name),
						storageId,
						animated,
					});
					okCount++;
				} catch (err: any) {
					setStatus({
						type: 'err',
						message: err?.message ?? `Failed to upload ${file.name}`,
					});
				}
			}
			if (okCount > 0 && !status) {
				setStatus({
					type: 'ok',
					message: `Uploaded ${okCount} ${okCount === 1 ? 'emoji' : 'emojis'}.`,
				});
			}
		} finally {
			setUploading(false);
		}
	};

	const handlePickFile = () => fileInputRef.current?.click();

	const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
		const files = e.target.files;
		if (fileInputRef.current) fileInputRef.current.value = '';
		if (!files || files.length === 0) return;
		await uploadFiles(files);
	};

	const handleDrop = async (e: React.DragEvent<HTMLDivElement>) => {
		e.preventDefault();
		setIsDraggingFiles(false);
		if (!e.dataTransfer.files || e.dataTransfer.files.length === 0) return;
		await uploadFiles(e.dataTransfer.files);
	};

	const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
		e.preventDefault();
		setIsDraggingFiles(true);
	};

	const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
		e.preventDefault();
		setIsDraggingFiles(false);
	};

	const handleRename = async (emoji: CustomEmojiDoc, nextName: string) => {
		if (!userId) return;
		const clean = nextName.trim();
		if (clean === emoji.name) return;
		try {
			await renameEmoji({
				userId,
				emojiId: emoji._id,
				name: clean,
			});
			setNameDrafts((prev) => {
				const next = { ...prev };
				delete next[emoji._id];
				return next;
			});
		} catch (err: any) {
			setStatus({ type: 'err', message: err?.message ?? 'Rename failed' });
		}
	};

	const handleRemove = async (emoji: CustomEmojiDoc) => {
		if (!userId) return;
		if (!confirm(`Delete :${emoji.name}:?`)) return;
		setStatus(null);
		try {
			await removeEmoji({ userId, emojiId: emoji._id });
		} catch (err: any) {
			setStatus({ type: 'err', message: err?.message ?? 'Delete failed' });
		}
	};

	const renderRows = (rows: CustomEmojiDoc[]) => (
		<>
			<div className={styles.tableHeader}>
				<span>Emoji</span>
				<span>Name</span>
				<span>Uploaded By</span>
				<span />
			</div>
			<div className={styles.tableBody}>
				{rows.map((emoji) => {
					const draft =
						nameDrafts[emoji._id] !== undefined
							? nameDrafts[emoji._id]
							: emoji.name;
					return (
						<div key={emoji._id} className={styles.emojiRow}>
							<div className={styles.emojiCell}>
								<img
									src={emoji.src}
									alt={emoji.name}
									className={styles.emojiImage}
									draggable={false}
								/>
							</div>
							<div className={styles.nameCell}>
								<input
									type="text"
									className={styles.nameInput}
									value={`:${draft}:`}
									onChange={(e) => {
										// Strip the surrounding colons so only the raw
										// shortcode is stored — users edit what reads as
										// `:name:` but the backend gets the bare name.
										const stripped = e.target.value.replace(/^:|:$/g, '');
										setNameDrafts((prev) => ({
											...prev,
											[emoji._id]: stripped,
										}));
									}}
									onBlur={() => handleRename(emoji, draft)}
									onKeyDown={(e) => {
										if (e.key === 'Enter') e.currentTarget.blur();
										else if (e.key === 'Escape') {
											setNameDrafts((prev) => {
												const next = { ...prev };
												delete next[emoji._id];
												return next;
											});
											(e.currentTarget as HTMLInputElement).blur();
										}
									}}
									maxLength={32}
								/>
							</div>
							<div className={styles.uploaderCell}>
								<Avatar
									src={emoji.uploadedByAvatarUrl ?? undefined}
									size={24}
									fallback={
										emoji.uploadedByDisplayName ||
										emoji.uploadedByUsername
									}
								/>
								<span className={styles.uploaderName}>
									{emoji.uploadedByDisplayName || emoji.uploadedByUsername}
								</span>
							</div>
							<div className={styles.deleteCell}>
								<button
									type="button"
									className={styles.deleteButton}
									onClick={() => handleRemove(emoji)}
									aria-label={`Delete ${emoji.name}`}
									title="Delete"
								>
									<Trash size={14} weight="bold" />
								</button>
							</div>
						</div>
					);
				})}
			</div>
		</>
	);

	return (
		<div className={styles.container}>
			<div className={styles.searchWrap}>
				<MagnifyingGlass
					className={styles.searchIcon}
					size={16}
					weight="regular"
				/>
				<input
					type="text"
					className={styles.searchInput}
					placeholder="Search emojis..."
					value={search}
					onChange={(e) => setSearch(e.target.value)}
				/>
			</div>

			<div className={styles.slotsCard}>
				<div className={styles.slotsHeader}>
					<div className={styles.slotsHeaderLeft}>
						<h3 className={styles.slotsTitle}>Upload Custom Emoji</h3>
					</div>
					<button
						type="button"
						className={styles.uploadButton}
						onClick={handlePickFile}
						disabled={uploading}
					>
						<UploadSimple size={16} weight="bold" />
						{uploading ? 'Uploading…' : 'Upload Emoji'}
					</button>
				</div>
				<p className={styles.slotsDescription}>
					Emoji names must be at least 2 characters long and can only contain
					alphanumeric characters and underscores. Allowed file types: JPEG,
					PNG, WebP, GIF. We compress images to 128×128 pixels. Maximum size:
					384 KB per emoji.
				</p>
			</div>

			{/* Drag-and-drop is desktop-only — mobile browsers can't
			    receive OS-level file drops, so the zone is hidden and
			    users upload via the Upload Emoji button in the slots
			    card above. */}
			{!isMobile && (
				<div
					className={`${styles.dropZone} ${isDraggingFiles ? styles.dropZoneActive : ''}`}
					onClick={handlePickFile}
					onDragOver={handleDragOver}
					onDragLeave={handleDragLeave}
					onDrop={handleDrop}
				>
					<UploadSimple
						size={32}
						weight="bold"
						className={styles.dropZoneIcon}
					/>
					<span>Drag and drop emoji files here</span>
				</div>
			)}

			<input
				ref={fileInputRef}
				type="file"
				accept={ACCEPTED_MIME}
				multiple
				onChange={handleFileChange}
				style={{ display: 'none' }}
			/>

			{status && (
				<div
					className={`${styles.statusBanner} ${status.type === 'ok' ? styles.statusOk : styles.statusErr}`}
				>
					{status.message}
				</div>
			)}

			{filtered.length === 0 ? (
				<div className={styles.emptyState}>
					{search
						? 'No emojis match your search.'
						: 'No custom emojis yet. Upload one to get started.'}
				</div>
			) : (
				<>
					{staticEmojis.length > 0 && (
						<div className={styles.section}>
							<h3 className={styles.sectionTitle}>
								Non-Animated Emoji ({staticEmojis.length})
							</h3>
							{renderRows(staticEmojis)}
						</div>
					)}
					{animatedEmojis.length > 0 && (
						<div className={styles.section}>
							<h3 className={styles.sectionTitle}>
								Animated Emoji ({animatedEmojis.length})
							</h3>
							{renderRows(animatedEmojis)}
						</div>
					)}
				</>
			)}
		</div>
	);
}
