/**
 * Mention autocomplete popup — opens above the chat input when the user
 * types `@`. Renders the server members for the current channel, with
 * arrow-key navigation and Enter/click to insert.
 */
import {
	forwardRef,
	useEffect,
	useImperativeHandle,
	useLayoutEffect,
	useMemo,
	useRef,
	useState,
} from 'react';
import { createPortal } from 'react-dom';
import { useQuery } from 'convex/react';
import { Avatar } from '@discord-clone/ui';
import { Megaphone } from '@phosphor-icons/react';
import { api } from '../../../../../convex/_generated/api';
import styles from './MentionAutocomplete.module.css';

export type MentionItem =
	| {
			kind: 'user';
			userId: string;
			displayName: string;
			username: string;
			avatar?: string;
	  }
	| {
			kind: 'everyone';
	  };

export interface MentionAutocompleteHandle {
	/** Move the keyboard selection. Returns true if the popup consumed the key. */
	moveSelection: (delta: number) => boolean;
	/** Commit the currently highlighted item. Returns true if something was selected. */
	commit: () => boolean;
}

interface Props {
	channelId: string;
	query: string;
	anchorEl: HTMLElement | null;
	onSelect: (item: MentionItem) => void;
	onClose: () => void;
}

const MAX_RESULTS = 10;

/**
 * Rank a candidate against the query. Lower score = better.
 * -2: startsWith match (best)
 * -1: word-boundary match
 *  0: substring match anywhere
 *  null: no match
 */
function scoreMatch(candidate: string, query: string): number | null {
	if (!query) return 0;
	const c = candidate.toLowerCase();
	const q = query.toLowerCase();
	if (c.startsWith(q)) return -2;
	const words = c.split(/[\s_\-.:/]+/);
	for (const w of words) {
		if (w !== c && w.startsWith(q)) return -1;
	}
	return c.includes(q) ? 0 : null;
}

interface ConvexMember {
	id: string;
	username: string;
	displayName: string | null;
	avatarUrl: string | null;
}

export const MentionAutocomplete = forwardRef<MentionAutocompleteHandle, Props>(
	function MentionAutocomplete({ channelId, query, anchorEl, onSelect, onClose }, ref) {
		const [selected, setSelected] = useState(0);
		const [pos, setPos] = useState<{ left: number; bottom: number; width: number } | null>(
			null,
		);
		const containerRef = useRef<HTMLDivElement>(null);

		const membersRaw = useQuery(
			api.members.getChannelMembers,
			channelId ? { channelId: channelId as any } : 'skip',
		) as ConvexMember[] | undefined;

		const items = useMemo<MentionItem[]>(() => {
			const result: MentionItem[] = [];
			if (scoreMatch('everyone', query) !== null) {
				result.push({ kind: 'everyone' });
			}
			if (membersRaw) {
				type Scored = { item: MentionItem; score: number; name: string };
				const scored: Scored[] = [];
				for (const m of membersRaw) {
					const name = m.displayName || m.username;
					const s1 = scoreMatch(name, query);
					const s2 = scoreMatch(m.username, query);
					const best =
						s1 === null && s2 === null
							? null
							: Math.min(s1 ?? Infinity, s2 ?? Infinity);
					if (best === null) continue;
					scored.push({
						item: {
							kind: 'user',
							userId: m.id,
							displayName: name,
							username: m.username,
							avatar: m.avatarUrl ?? undefined,
						},
						score: best,
						name,
					});
				}
				scored.sort((a, b) => {
					if (a.score !== b.score) return a.score - b.score;
					return a.name.localeCompare(b.name);
				});
				result.push(...scored.slice(0, MAX_RESULTS).map((s) => s.item));
			}
			return result;
		}, [membersRaw, query]);

		// Clamp selection when the list changes.
		useEffect(() => {
			setSelected((prev) => {
				if (items.length === 0) return 0;
				if (prev >= items.length) return items.length - 1;
				return prev;
			});
		}, [items]);

		// Close if the filter yielded nothing — but only after the query
		// has had a chance to resolve. While the query is still loading
		// (membersRaw === undefined) we keep the popup open so the user
		// doesn't see it flicker.
		useEffect(() => {
			if (membersRaw !== undefined && items.length === 0) {
				onClose();
			}
		}, [membersRaw, items.length, onClose]);

		// Position above the anchor (the textarea).
		useLayoutEffect(() => {
			if (!anchorEl) return;
			const update = () => {
				const rect = anchorEl.getBoundingClientRect();
				setPos({
					left: rect.left,
					bottom: window.innerHeight - rect.top + 8,
					width: Math.max(280, Math.min(rect.width, 520)),
				});
			};
			update();
			window.addEventListener('resize', update);
			window.addEventListener('scroll', update, true);
			return () => {
				window.removeEventListener('resize', update);
				window.removeEventListener('scroll', update, true);
			};
		}, [anchorEl]);

		// Keep the highlighted row in view.
		useEffect(() => {
			const container = containerRef.current;
			if (!container) return;
			const el = container.querySelector<HTMLElement>(`[data-index="${selected}"]`);
			el?.scrollIntoView({ block: 'nearest' });
		}, [selected]);

		useImperativeHandle(
			ref,
			() => ({
				moveSelection: (delta) => {
					if (items.length === 0) return false;
					setSelected((prev) => {
						const next = (prev + delta + items.length) % items.length;
						return next;
					});
					return true;
				},
				commit: () => {
					const item = items[selected];
					if (!item) return false;
					onSelect(item);
					return true;
				},
			}),
			[items, selected, onSelect],
		);

		if (!pos || items.length === 0) return null;

		return createPortal(
			<div
				ref={containerRef}
				className={styles.container}
				style={{
					position: 'fixed',
					left: pos.left,
					bottom: pos.bottom,
					width: pos.width,
					zIndex: 15000,
				}}
				// Prevent the contenteditable from losing focus when users
				// click into the popup.
				onMouseDown={(e) => e.preventDefault()}
			>
				<div className={styles.header}>MEMBERS</div>
				<div className={styles.scroller}>
					{items.map((item, index) => {
						const isActive = index === selected;
						return (
							<button
								key={item.kind === 'everyone' ? 'everyone' : item.userId}
								type="button"
								data-index={index}
								className={`${styles.row} ${isActive ? styles.rowActive : ''}`}
								onMouseEnter={() => setSelected(index)}
								onClick={() => onSelect(item)}
							>
								<div className={styles.icon}>
									{item.kind === 'everyone' ? (
										<div className={styles.everyoneIcon}>
											<Megaphone size={16} weight="fill" />
										</div>
									) : (
										<Avatar
											src={item.avatar}
											fallback={item.displayName}
											size={24}
										/>
									)}
								</div>
								<div className={styles.nameWrapper}>
									<div className={styles.name}>
										{item.kind === 'everyone' ? '@everyone' : item.displayName}
									</div>
								</div>
								<div className={styles.description}>
									{item.kind === 'everyone'
										? 'Notify everyone in this channel'
										: item.username}
								</div>
							</button>
						);
					})}
				</div>
			</div>,
			document.body,
		);
	},
);
