import VoiceStore from '@app/stores/VoiceStore';
import {
	FriendManager,
	MatrixClientManager,
	type Member,
	MemberManager,
	VoiceModerationManager,
	type VoiceParticipantSnapshot,
	parseMatrixUserFromIdentity,
} from '@brycord/matrix-client';
import { Avatar } from '@brycord/ui';
import { MicrophoneSlash, SpeakerSlash } from '@phosphor-icons/react';
import { observer } from 'mobx-react-lite';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { MemberProfileModal } from '../member/MemberProfileModal';
import { NicknameModal } from '../member/NicknameModal';
import { VoiceParticipantContextMenu } from './VoiceParticipantContextMenu';
import styles from './VoiceParticipantItem.module.css';

interface VoiceParticipantItemProps {
	participant: VoiceParticipantSnapshot;
}

interface ContextMenuState {
	x: number;
	y: number;
}

export const VoiceParticipantItem = observer(function VoiceParticipantItem({ participant }: VoiceParticipantItemProps) {
	const navigate = useNavigate();
	const { displayName, avatar, isMuted, isSpeaking, isScreenSharing, isLocal, identity } = participant;

	// For the local user we read VoiceStore directly so every
	// toggle shows instant feedback without waiting for our own
	// state-event round-trip. Remote participants read
	// `participant.isDeafened` which the snapshot pipeline has
	// already merged from the `io.brycord.voice_state` state event
	// published by that user. Pre-join and in-call use the same
	// source of truth.
	const isDeafened = isLocal ? VoiceStore.isDeafened : participant.isDeafened;

	const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
	const [profileMember, setProfileMember] = useState<Member | null>(null);
	const [nicknameMember, setNicknameMember] = useState<Member | null>(null);

	const matrixUserId = parseMatrixUserFromIdentity(identity);

	// Is the target participant in the same voice call as us right
	// now? Needed for Volume + admin actions (mute/deafen/disconnect
	// require the LiveKit data channel for disconnect, and
	// moderation state events need the voice channel context).
	const sameCallAsLocal =
		VoiceStore.connectedChannelId !== null && VoiceStore.participants.some((p) => p.identity === identity);

	const handleContextMenu = (e: React.MouseEvent) => {
		e.preventDefault();
		e.stopPropagation();
		setContextMenu({ x: e.clientX, y: e.clientY });
	};

	const closeMenu = () => setContextMenu(null);

	// Resolve the Member object for this participant. Looks up the
	// currently-connected voice channel first (since that's where
	// the participant is visible), falling back to any room the
	// user shares with the local user via MemberManager.
	const getMember = (): Member | null => {
		try {
			const channelId = VoiceStore.connectedChannelId;
			if (!channelId) return null;
			const serverId = VoiceStore.connectedServerId ?? undefined;
			const members = MemberManager.getInstance().getRoomMembers(channelId, serverId);
			return members.find((m) => m.user.id === matrixUserId) ?? null;
		} catch {
			return null;
		}
	};

	const handleViewProfile = () => {
		const member = getMember();
		closeMenu();
		if (member) setProfileMember(member);
	};

	const handleMessage = async () => {
		closeMenu();
		try {
			const roomId = await FriendManager.getInstance().getOrCreateDM(matrixUserId);
			navigate(`/channels/@me/${roomId}`);
		} catch {
			// Silent — the user can retry from the profile modal.
		}
	};

	const handleVolumeChange = (level: number) => {
		VoiceStore.setParticipantVolume(identity, level);
	};

	const handleChangeNickname = () => {
		const member = getMember();
		closeMenu();
		if (member) setNicknameMember(member);
	};

	const handleToggleServerMute = async () => {
		closeMenu();
		const channelId = VoiceStore.connectedChannelId;
		if (!channelId) return;
		const current = VoiceModerationManager.getInstance().getVoiceModeration(channelId, matrixUserId);
		await VoiceStore.setTargetServerMuted(channelId, matrixUserId, !(current?.muted === true));
	};

	const handleToggleServerDeafen = async () => {
		closeMenu();
		const channelId = VoiceStore.connectedChannelId;
		if (!channelId) return;
		const current = VoiceModerationManager.getInstance().getVoiceModeration(channelId, matrixUserId);
		await VoiceStore.setTargetServerDeafened(channelId, matrixUserId, !(current?.deafened === true));
	};

	const handleDisconnect = async () => {
		closeMenu();
		await VoiceStore.disconnectTarget(identity);
	};

	// Permission checks — recomputed fresh every render so they
	// stay in sync with power level / membership changes.
	const myUserId = (() => {
		try {
			return MatrixClientManager.getInstance().getClient().getUserId();
		} catch {
			return null;
		}
	})();
	const isSelf = matrixUserId === myUserId;

	const connectedChannelId = VoiceStore.connectedChannelId;
	const canModerate = connectedChannelId
		? VoiceModerationManager.getInstance().canModerateVoice(connectedChannelId)
		: false;
	const canRename = connectedChannelId ? MemberManager.getInstance().canSetOtherNicknames(connectedChannelId) : false;

	// Self-protection: admins can't moderate themselves via the
	// context menu (renaming self is fine — you can always rename
	// yourself, it's a separate power level).
	const showChangeNickname = !isSelf && canRename;
	const showAdminActions = !isSelf && canModerate && sameCallAsLocal;
	const showVolume = !isSelf && sameCallAsLocal;

	const targetModeration = connectedChannelId
		? VoiceModerationManager.getInstance().getVoiceModeration(connectedChannelId, matrixUserId)
		: null;

	return (
		<>
			<button type="button" className={styles.item} onContextMenu={handleContextMenu}>
				<Avatar
					src={avatar}
					fallback={displayName}
					size={24}
					className={isSpeaking ? styles.avatarSpeaking : undefined}
				/>
				<span className={styles.name}>{displayName}</span>
				<div className={styles.icons}>
					{isMuted && <MicrophoneSlash size={14} className={styles.muteIcon} />}
					{isDeafened && <SpeakerSlash size={14} className={styles.deafenIcon} />}
					{isScreenSharing && <span className={styles.liveBadge}>LIVE</span>}
				</div>
			</button>

			{contextMenu && (
				<VoiceParticipantContextMenu
					x={contextMenu.x}
					y={contextMenu.y}
					onClose={closeMenu}
					onViewProfile={handleViewProfile}
					onMessage={handleMessage}
					onVolumeChange={showVolume ? handleVolumeChange : undefined}
					currentVolume={showVolume ? VoiceStore.getParticipantVolume(identity) : 1}
					onChangeNickname={showChangeNickname ? handleChangeNickname : undefined}
					onToggleServerMute={showAdminActions ? handleToggleServerMute : undefined}
					onToggleServerDeafen={showAdminActions ? handleToggleServerDeafen : undefined}
					onDisconnect={showAdminActions ? handleDisconnect : undefined}
					isServerMuted={targetModeration?.muted === true}
					isServerDeafened={targetModeration?.deafened === true}
				/>
			)}

			{profileMember && <MemberProfileModal isOpen member={profileMember} onClose={() => setProfileMember(null)} />}

			{nicknameMember && connectedChannelId && (
				<NicknameModal
					isOpen
					member={nicknameMember}
					channelId={connectedChannelId}
					serverId={VoiceStore.connectedServerId ?? undefined}
					onClose={() => setNicknameMember(null)}
				/>
			)}
		</>
	);
});
