import {
	Bicycle,
	BowlFood,
	CaretDown,
	Clock,
	Flag,
	GameController,
	Gif,
	Heart,
	ImageSquare,
	Leaf,
	Magnet,
	MagnifyingGlass,
	Smiley,
	SmileyWink,
	Sticker,
	X,
} from '@phosphor-icons/react';
import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
import { useQuery } from 'convex/react';
import emojiData from '@app/data/emojis.json';
import { api } from '../../../../../convex/_generated/api';
import { GifPicker } from './GifPicker';
import { TwemojiImg } from './TwemojiImg';
import styles from './EmojiPicker.module.css';

export type ExpressionTab = 'gifs' | 'media' | 'stickers' | 'emojis';

export type EmojiPickerValue =
	| {
			kind: 'unicode';
			surrogates: string;
			name: string;
	  }
	| {
			kind: 'custom';
			shortcode: string;
			url: string;
	  }
	| {
			// Picked from the GIFs tab — composer should send this as
			// a plain text URL so the existing link-embed renderer
			// shows it inline.
			kind: 'gif';
			url: string;
	  };

interface CustomEmojiListEntry {
	_id: string;
	name: string;
	src: string;
}

interface EmojiPickerProps {
	onSelect: (value: EmojiPickerValue) => void;
	onClose?: () => void;
	mobile?: boolean;
	initialTab?: ExpressionTab;
	onTabChange?: (tab: ExpressionTab) => void;
}

interface EmojiEntry {
	names: string[];
	surrogates: string;
}

type EmojiData = Record<string, EmojiEntry[]>;

const EXPRESSION_TABS: Array<{ key: ExpressionTab; label: string }> = [
	{ key: 'gifs', label: 'GIFs' },
	{ key: 'media', label: 'Media' },
	{ key: 'stickers', label: 'Stickers' },
	{ key: 'emojis', label: 'Emojis' },
];

interface CategoryDef {
	key: string;
	label: string;
	icon: ReactNode;
}

const CATEGORIES: CategoryDef[] = [
	{ key: 'people', label: 'Smileys & People', icon: <Smiley size={20} /> },
	{ key: 'nature', label: 'Animals & Nature', icon: <Leaf size={20} /> },
	{ key: 'food', label: 'Food & Drink', icon: <BowlFood size={20} /> },
	{ key: 'activity', label: 'Activities', icon: <GameController size={20} /> },
	{ key: 'travel', label: 'Travel & Places', icon: <Bicycle size={20} /> },
	{ key: 'objects', label: 'Objects', icon: <Magnet size={20} /> },
	{ key: 'symbols', label: 'Symbols', icon: <Heart size={20} /> },
	{ key: 'flags', label: 'Flags', icon: <Flag size={20} /> },
];

const RECENTS_STORAGE_KEY = 'discord_clone_recent_emojis_v1';
const MAX_RECENTS = 24;

function loadRecents(): EmojiPickerValue[] {
	if (typeof localStorage === 'undefined') return [];
	try {
		const raw = localStorage.getItem(RECENTS_STORAGE_KEY);
		return raw ? (JSON.parse(raw) as EmojiPickerValue[]) : [];
	} catch {
		return [];
	}
}

function saveRecents(list: EmojiPickerValue[]) {
	if (typeof localStorage === 'undefined') return;
	try {
		localStorage.setItem(RECENTS_STORAGE_KEY, JSON.stringify(list.slice(0, MAX_RECENTS)));
	} catch {}
}

export function EmojiPicker({
	onSelect,
	onClose,
	mobile = false,
	initialTab = 'emojis',
	onTabChange,
}: EmojiPickerProps) {
	const [activeTab, setActiveTabState] = useState<ExpressionTab>(initialTab);
	const setActiveTab = (tab: ExpressionTab) => {
		setActiveTabState(tab);
		onTabChange?.(tab);
	};
	// Allow the parent to imperatively switch tabs by changing
	// `initialTab` while the picker is mounted — used by the composer
	// when the user clicks a different expression-button without
	// closing the picker first.
	useEffect(() => {
		setActiveTabState(initialTab);
	}, [initialTab]);
	const [search, setSearch] = useState('');
	const [activeCategory, setActiveCategory] = useState<string>('people');
	const [collapsed, setCollapsed] = useState<Set<string>>(new Set());
	const [hovered, setHovered] = useState<EmojiPickerValue | null>(null);
	const [recents, setRecents] = useState<EmojiPickerValue[]>(() => loadRecents());
	// Saved-media filter chips. `all` shows every row; the others
	// filter by the `kind` string that `api.savedMedia.save` writes
	// (first half of the MIME type: image / video / audio).
	const [mediaFilter, setMediaFilter] = useState<
		'all' | 'image' | 'video' | 'audio'
	>('all');

	// Saved-media library — only fetched when the Media tab is open
	// to keep the picker cheap during normal emoji use.
	const myUserId =
		typeof localStorage !== 'undefined'
			? localStorage.getItem('userId')
			: null;
	const savedMedia =
		useQuery(
			api.savedMedia.list,
			myUserId && activeTab === 'media'
				? { userId: myUserId as any }
				: 'skip',
		) ?? [];

	const handleRepostSaved = (item: any) => {
		// ChannelTextarea listens for this event and re-uses the
		// stored attachment metadata so the same file can be
		// re-posted into the active channel without re-uploading.
		window.dispatchEvent(
			new CustomEvent('brycord:repost-saved-media', {
				detail: {
					url: item.url,
					filename: item.filename,
					mimeType: item.mimeType,
					width: item.width,
					height: item.height,
					size: item.size,
					encryptionKey: item.encryptionKey,
					encryptionIv: item.encryptionIv,
				},
			}),
		);
		onClose?.();
	};

	const gridRef = useRef<HTMLDivElement>(null);
	const searchRef = useRef<HTMLInputElement>(null);
	const categoryRefs = useRef<Record<string, HTMLDivElement | null>>({});

	const data = emojiData as unknown as EmojiData;

	useEffect(() => {
		if (!mobile && activeTab === 'emojis') {
			searchRef.current?.focus();
		}
	}, [activeTab, mobile]);

	const filtered = useMemo(() => {
		const q = search.trim().toLowerCase();
		if (!q) return null;
		const results: EmojiEntry[] = [];
		for (const cat of CATEGORIES) {
			for (const entry of data[cat.key] ?? []) {
				if (entry.names.some((n) => n.toLowerCase().includes(q))) {
					results.push(entry);
				}
			}
		}
		return results;
	}, [search, data]);

	const customEmojis = (useQuery(api.customEmojis.list, {}) ?? []) as CustomEmojiListEntry[];

	const recentKey = (v: EmojiPickerValue): string =>
		v.kind === 'unicode' ? `u:${v.surrogates}` : `c:${v.shortcode}`;

	const addRecent = (value: EmojiPickerValue) => {
		setRecents((prev) => {
			const key = recentKey(value);
			const next = [value, ...prev.filter((r) => recentKey(r) !== key)].slice(0, MAX_RECENTS);
			saveRecents(next);
			return next;
		});
	};

	const handleSelect = (entry: EmojiEntry) => {
		const value: EmojiPickerValue = {
			kind: 'unicode',
			surrogates: entry.surrogates,
			name: entry.names[0] ?? '',
		};
		addRecent(value);
		onSelect(value);
	};

	const handleSelectRecent = (value: EmojiPickerValue) => {
		addRecent(value);
		onSelect(value);
	};

	const handleCategoryJump = (key: string) => {
		setActiveCategory(key);
		const el = categoryRefs.current[key];
		if (el && gridRef.current) {
			gridRef.current.scrollTo({ top: el.offsetTop - 8, behavior: 'smooth' });
		}
	};

	const handleScroll = () => {
		const grid = gridRef.current;
		if (!grid) return;
		const scrollTop = grid.scrollTop;
		let best: string = activeCategory;
		let bestDelta = Number.POSITIVE_INFINITY;
		for (const key of Object.keys(categoryRefs.current)) {
			const el = categoryRefs.current[key];
			if (!el) continue;
			const delta = Math.abs(el.offsetTop - scrollTop - 8);
			if (delta < bestDelta) {
				bestDelta = delta;
				best = key;
			}
		}
		if (best !== activeCategory) setActiveCategory(best);
	};

	const toggleCollapsed = (key: string) => {
		setCollapsed((prev) => {
			const next = new Set(prev);
			if (next.has(key)) next.delete(key);
			else next.add(key);
			return next;
		});
	};

	const renderSectionHeader = (key: string, label: string) => {
		const isCollapsed = collapsed.has(key);
		return (
			<button
				type="button"
				className={styles.sectionHeader}
				onClick={() => toggleCollapsed(key)}
			>
				<span className={styles.sectionHeaderLabel}>{label}</span>
				<CaretDown
					size={14}
					weight="bold"
					className={`${styles.sectionHeaderCaret} ${
						isCollapsed ? styles.sectionHeaderCaretCollapsed : ''
					}`}
				/>
			</button>
		);
	};

	// The index is included in the key because `emojis.json` contains
	// the same surrogate in multiple categories (e.g. 🌆 lives under
	// both "travel" and "nature" in some builds of the source data),
	// which makes React complain about duplicate keys. Pair the
	// surrogate with its map index so keys stay unique even when the
	// underlying data isn't.
	const renderUnicodeButton = (entry: EmojiEntry, idx: number) => (
		<button
			key={`${entry.surrogates}-${idx}`}
			type="button"
			className={styles.emojiButton}
			onClick={() => handleSelect(entry)}
			onMouseEnter={() =>
				setHovered({
					kind: 'unicode',
					surrogates: entry.surrogates,
					name: entry.names[0] ?? '',
				})
			}
			onMouseLeave={() => setHovered(null)}
			title={`:${entry.names[0] ?? ''}:`}
		>
			<TwemojiImg emoji={entry.surrogates} size={28} />
		</button>
	);

	const handleSelectCustom = (entry: CustomEmojiListEntry) => {
		const value: EmojiPickerValue = {
			kind: 'custom',
			shortcode: entry.name,
			url: entry.src,
		};
		addRecent(value);
		onSelect(value);
	};

	const renderCustomButton = (entry: CustomEmojiListEntry) => (
		<button
			key={entry._id}
			type="button"
			className={styles.emojiButton}
			onClick={() => handleSelectCustom(entry)}
			onMouseEnter={() =>
				setHovered({ kind: 'custom', shortcode: entry.name, url: entry.src })
			}
			onMouseLeave={() => setHovered(null)}
			title={`:${entry.name}:`}
		>
			<img
				src={entry.src}
				alt={entry.name}
				draggable={false}
				style={{ width: 32, height: 32, objectFit: 'contain' }}
			/>
		</button>
	);

	const renderRecentButton = (r: EmojiPickerValue, i: number) => {
		if (r.kind === 'custom') {
			return (
				<button
					key={`r-${i}-c-${r.shortcode}`}
					type="button"
					className={styles.emojiButton}
					onClick={() => handleSelectRecent(r)}
					onMouseEnter={() => setHovered(r)}
					onMouseLeave={() => setHovered(null)}
					title={`:${r.shortcode}:`}
				>
					<img
						src={r.url}
						alt={r.shortcode}
						draggable={false}
						style={{ width: 32, height: 32, objectFit: 'contain' }}
					/>
				</button>
			);
		}
		return (
			<button
				key={`r-${i}-u-${r.surrogates}`}
				type="button"
				className={styles.emojiButton}
				onClick={() => handleSelectRecent(r)}
				onMouseEnter={() => setHovered(r)}
				onMouseLeave={() => setHovered(null)}
			>
				<TwemojiImg emoji={r.surrogates} size={28} />
			</button>
		);
	};

	const renderSidebarTab = (key: string, label: string, icon: ReactNode) => {
		const isActive = activeCategory === key;
		return (
			<button
				key={key}
				type="button"
				className={`${styles.sideTab} ${isActive ? styles.sideTabActive : ''}`}
				onClick={() => handleCategoryJump(key)}
				title={label}
				aria-label={label}
			>
				{icon}
			</button>
		);
	};

	// Mobile layout - flat sticky headers + bottom category bar
	if (mobile) {
		return (
			<div className={`${styles.picker} ${styles.pickerMobile}`}>
				<div className={styles.searchBar}>
					<MagnifyingGlass size={16} weight="regular" className={styles.searchIcon} />
					<input
						ref={searchRef}
						className={styles.searchInput}
						placeholder="Search emoji"
						value={search}
						onChange={(e) => setSearch(e.target.value)}
					/>
					{search && (
						<button type="button" className={styles.searchClear} onClick={() => setSearch('')}>
							<X size={14} />
						</button>
					)}
				</div>
				<div className={styles.grid} ref={gridRef} onScroll={handleScroll}>
					{filtered ? (
						filtered.length === 0 ? (
							<div className={styles.noResults}>No emoji found</div>
						) : (
							<>
								<div className={styles.categoryHeader}>Search Results</div>
								<div className={styles.emojiGrid}>
									{filtered.map(renderUnicodeButton)}
								</div>
							</>
						)
					) : (
						<>
							{customEmojis.length > 0 && (
								<div
									ref={(el) => {
										categoryRefs.current['custom'] = el;
									}}
								>
									<div className={styles.categoryHeader}>Server Emojis</div>
									<div className={styles.emojiGrid}>
										{customEmojis.map(renderCustomButton)}
									</div>
								</div>
							)}
							{recents.length > 0 && (
								<div
									ref={(el) => {
										categoryRefs.current['recent'] = el;
									}}
								>
									<div className={styles.categoryHeader}>Recently Used</div>
									<div className={styles.emojiGrid}>
										{recents.map((r, i) => renderRecentButton(r, i))}
									</div>
								</div>
							)}
							{CATEGORIES.map((cat) => (
								<div
									key={cat.key}
									ref={(el) => {
										categoryRefs.current[cat.key] = el;
									}}
								>
									<div className={styles.categoryHeader}>{cat.label}</div>
									<div className={styles.emojiGrid}>
										{(data[cat.key] ?? []).map(renderUnicodeButton)}
									</div>
								</div>
							))}
						</>
					)}
				</div>
				<div className={styles.categoryBar}>
					{customEmojis.length > 0 && (
						<button
							type="button"
							className={`${styles.categoryTab} ${activeCategory === 'custom' ? styles.categoryTabActive : ''}`}
							onClick={() => handleCategoryJump('custom')}
							title="Server Emojis"
						>
							<SmileyWink size={20} />
						</button>
					)}
					{recents.length > 0 && (
						<button
							type="button"
							className={`${styles.categoryTab} ${activeCategory === 'recent' ? styles.categoryTabActive : ''}`}
							onClick={() => handleCategoryJump('recent')}
							title="Recently Used"
						>
							<Clock size={20} />
						</button>
					)}
					{CATEGORIES.map((cat) => (
						<button
							key={cat.key}
							type="button"
							className={`${styles.categoryTab} ${activeCategory === cat.key ? styles.categoryTabActive : ''}`}
							onClick={() => handleCategoryJump(cat.key)}
							title={cat.label}
						>
							{cat.icon}
						</button>
					))}
				</div>
				{onClose && (
					<button type="button" className={styles.searchClear} onClick={onClose}>
						<X size={18} />
					</button>
				)}
			</div>
		);
	}

	// Desktop layout - top tab bar + sidebar + grid + inspector
	return (
		<div className={styles.picker}>
			<div className={styles.tabBar}>
				{EXPRESSION_TABS.map((tab) => {
					const isActive = activeTab === tab.key;
					return (
						<button
							key={tab.key}
							type="button"
							className={`${styles.tabButton} ${isActive ? styles.tabButtonActive : ''}`}
							onClick={() => setActiveTab(tab.key)}
						>
							{tab.label}
						</button>
					);
				})}
			</div>

			{activeTab !== 'gifs' && (
				<div
					className={`${styles.searchRow} ${activeTab === 'media' ? styles.searchRowFlush : ''}`}
				>
					<div className={styles.searchBar}>
						<MagnifyingGlass size={16} weight="regular" className={styles.searchIcon} />
						<input
							ref={searchRef}
							className={styles.searchInput}
							placeholder={
								activeTab === 'emojis'
									? 'Search emoji'
									: activeTab === 'media'
										? 'Search media'
										: 'Coming soon'
							}
							value={search}
							onChange={(e) => setSearch(e.target.value)}
							disabled={activeTab !== 'emojis' && activeTab !== 'media'}
						/>
						{search && (
							<button type="button" className={styles.searchClear} onClick={() => setSearch('')}>
								<X size={14} />
							</button>
						)}
					</div>
				</div>
			)}

			{activeTab === 'media' && (
				<div className={styles.filterChips}>
					{(
						[
							{ id: 'all', label: 'All' },
							{ id: 'image', label: 'Images' },
							{ id: 'video', label: 'Videos' },
							{ id: 'audio', label: 'Audio' },
						] as const
					).map((chip) => {
						const isActive = mediaFilter === chip.id;
						return (
							<button
								key={chip.id}
								type="button"
								className={`${styles.filterChip} ${isActive ? styles.filterChipActive : ''}`}
								onClick={() => setMediaFilter(chip.id)}
							>
								{chip.label}
							</button>
						);
					})}
				</div>
			)}

			<div
				className={`${styles.main} ${activeTab === 'media' ? styles.mainMedia : ''}`}
			>
				{activeTab === 'emojis' && (
					<div className={styles.sideBar}>
						{customEmojis.length > 0 &&
							renderSidebarTab('custom', 'Server Emojis', <SmileyWink size={20} />)}
						{recents.length > 0 &&
							renderSidebarTab('recent', 'Recently Used', <Clock size={20} />)}
						{CATEGORIES.map((cat) => renderSidebarTab(cat.key, cat.label, cat.icon))}
					</div>
				)}

				<div
					className={`${styles.grid} ${activeTab === 'gifs' ? styles.gridGifs : ''}`}
					ref={gridRef}
					onScroll={handleScroll}
				>
					{activeTab === 'gifs' ? (
						<GifPicker
							onSelectGif={(url) => {
								onSelect({ kind: 'gif', url });
								onClose?.();
							}}
						/>
					) : activeTab === 'media' ? (
						(() => {
							// Filter the saved library by the active chip + the
							// search box (case-insensitive filename substring).
							const q = search.trim().toLowerCase();
							const filteredSaved = (savedMedia as any[]).filter((item) => {
								if (
									mediaFilter !== 'all' &&
									(item.kind ?? '') !== mediaFilter
								) {
									return false;
								}
								if (q && !(item.filename ?? '').toLowerCase().includes(q)) {
									return false;
								}
								return true;
							});
							if (savedMedia.length === 0) {
								return (
									<div className={styles.comingSoon}>
										Nothing saved yet. Star an attachment to bookmark it
										here for quick re-sharing.
									</div>
								);
							}
							if (filteredSaved.length === 0) {
								return (
									<div className={styles.comingSoon}>
										No saved media match your filter.
									</div>
								);
							}
							return (
							<div
								style={{
									display: 'grid',
									gridTemplateColumns: 'repeat(auto-fill, minmax(96px, 1fr))',
									gap: 8,
									padding: 8,
								}}
							>
								{filteredSaved.map((item: any) => {
									const isImage = item.kind === 'image';
									const isVideo = item.kind === 'video';
									return (
										<button
											key={item._id}
											type="button"
											title={item.filename}
											onClick={() => handleRepostSaved(item)}
											style={{
												aspectRatio: '1 / 1',
												borderRadius: 6,
												overflow: 'hidden',
												border: '1px solid var(--background-modifier-accent)',
												background: 'var(--background-tertiary)',
												cursor: 'pointer',
												padding: 0,
												position: 'relative',
												display: 'flex',
												alignItems: 'center',
												justifyContent: 'center',
												color: 'var(--text-tertiary)',
											}}
										>
											{isImage ? (
												// Server-stored URL is publicly fetchable but
												// the bytes are encrypted — show a generic
												// thumbnail label since we'd need the per-file
												// key to decrypt for a real preview.
												<ImageSquare size={32} weight="fill" />
											) : isVideo ? (
												<Gif size={32} weight="fill" />
											) : (
												<Sticker size={32} weight="fill" />
											)}
											<span
												style={{
													position: 'absolute',
													bottom: 4,
													left: 4,
													right: 4,
													fontSize: 9,
													color: 'var(--text-secondary)',
													overflow: 'hidden',
													textOverflow: 'ellipsis',
													whiteSpace: 'nowrap',
													textAlign: 'center',
												}}
											>
												{item.filename}
											</span>
										</button>
									);
								})}
							</div>
							);
						})()
					) : activeTab !== 'emojis' ? (
						<div className={styles.comingSoon}>
							{EXPRESSION_TABS.find((t) => t.key === activeTab)?.label} are coming soon.
						</div>
					) : filtered ? (
						filtered.length === 0 ? (
							<div className={styles.noResults}>No emoji found</div>
						) : (
							<div className={styles.emojiGrid}>{filtered.map(renderUnicodeButton)}</div>
						)
					) : (
						<>
							{customEmojis.length > 0 && (
								<div
									className={styles.section}
									ref={(el) => {
										categoryRefs.current['custom'] = el;
									}}
								>
									{renderSectionHeader('custom', 'Server Emojis')}
									{!collapsed.has('custom') && (
										<div className={styles.emojiGrid}>
											{customEmojis.map(renderCustomButton)}
										</div>
									)}
								</div>
							)}
							{recents.length > 0 && (
								<div
									className={styles.section}
									ref={(el) => {
										categoryRefs.current['recent'] = el;
									}}
								>
									{renderSectionHeader('recent', 'Recently Used')}
									{!collapsed.has('recent') && (
										<div className={styles.emojiGrid}>
											{recents.map((r, i) => renderRecentButton(r, i))}
										</div>
									)}
								</div>
							)}
							{CATEGORIES.map((cat) => (
								<div
									key={cat.key}
									className={styles.section}
									ref={(el) => {
										categoryRefs.current[cat.key] = el;
									}}
								>
									{renderSectionHeader(cat.key, cat.label)}
									{!collapsed.has(cat.key) && (
										<div className={styles.emojiGrid}>
											{(data[cat.key] ?? []).map(renderUnicodeButton)}
										</div>
									)}
								</div>
							))}
						</>
					)}
				</div>
			</div>

			{activeTab === 'emojis' && (
				<div className={styles.inspector}>
					{hovered ? (
						hovered.kind === 'custom' ? (
							<>
								<span className={styles.inspectorEmoji}>
									<img
										src={hovered.url}
										alt={hovered.shortcode}
										style={{ width: 32, height: 32, objectFit: 'contain' }}
									/>
								</span>
								<span className={styles.inspectorName}>:{hovered.shortcode}:</span>
							</>
						) : (
							<>
								<span className={styles.inspectorEmoji}>
									<TwemojiImg emoji={hovered.surrogates} size={32} />
								</span>
								<span className={styles.inspectorName}>:{hovered.name}:</span>
							</>
						)
					) : (
						<span className={styles.inspectorName}>Pick your emoji</span>
					)}
				</div>
			)}
		</div>
	);
}

// Suppress unused icon warnings — they're kept imported for parity with
// the original's GIFs/Media/Stickers tabs.
void Gif;
void ImageSquare;
void Sticker;

export default EmojiPicker;
