import React, { useState, useRef, useEffect, useLayoutEffect, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useConvex, useMutation, useQuery } from 'convex/react';
import { api } from '../../../../convex/_generated/api';
import Tooltip from './Tooltip';
import { useVoice } from '../contexts/VoiceContext';
import ChannelSettingsModal from './ChannelSettingsModal';
import ServerSettingsModal from './ServerSettingsModal';
import ScreenShareModal from './ScreenShareModal';
import DMList from './DMList';
import Avatar from './Avatar';
import UserSettings from './UserSettings';
import { Track } from 'livekit-client';
import { DndContext, closestCenter, PointerSensor, useSensor, useSensors, DragOverlay, useDraggable } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import muteIcon from '../assets/icons/mute.svg';
import mutedIcon from '../assets/icons/muted.svg';
import defeanIcon from '../assets/icons/defean.svg';
import defeanedIcon from '../assets/icons/defeaned.svg';
import settingsIcon from '../assets/icons/settings.svg';
import voiceIcon from '../assets/icons/voice.svg';
import disconnectIcon from '../assets/icons/disconnect.svg';
import cameraIcon from '../assets/icons/camera.svg';
import screenIcon from '../assets/icons/screen.svg';
import inviteUserIcon from '../assets/icons/invite_user.svg';
import personalMuteIcon from '../assets/icons/personal_mute.svg';
import serverMuteIcon from '../assets/icons/server_mute.svg';
import categoryCollapsedIcon from '../assets/icons/category_collapsed_icon.svg';
import PingSound from '../assets/sounds/ping.mp3';
import screenShareStartSound from '../assets/sounds/screenshare_start.mp3';
import screenShareStopSound from '../assets/sounds/screenshare_stop.mp3';
import { getUserPref, setUserPref } from '../utils/userPreferences';
import { usePlatform } from '../platform';
import ColoredIcon from './ColoredIcon';

const USER_COLORS = ['#5865F2', '#EBA7CD', '#57F287', '#FEE75C', '#EB459E', '#ED4245'];

const ICON_COLOR_DEFAULT = 'hsl(240, 4.294%, 68.039%)';
const ICON_COLOR_ACTIVE = 'hsl(357.692, 67.826%, 54.902%)';
const SERVER_MUTE_RED = 'hsl(1.343, 84.81%, 69.02%)';

const controlButtonStyle = {
    background: 'transparent',
    border: 'none',
    cursor: 'pointer',
    padding: '6px',
    borderRadius: '4px',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center'
};

function getUserColor(name) {
    let hash = 0;
    for (let i = 0; i < name.length; i++) {
        hash = name.charCodeAt(i) + ((hash << 5) - hash);
    }
    return USER_COLORS[Math.abs(hash) % USER_COLORS.length];
}

function bytesToHex(bytes) {
    return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
}

function randomHex(length) {
    const bytes = new Uint8Array(length);
    crypto.getRandomValues(bytes);
    return bytesToHex(bytes);
}

const VoiceTimer = () => {
    const [elapsed, setElapsed] = React.useState(0);
    React.useEffect(() => {
        const start = Date.now();
        const interval = setInterval(() => setElapsed(Math.floor((Date.now() - start) / 1000)), 1000);
        return () => clearInterval(interval);
    }, []);
    const hours = Math.floor(elapsed / 3600);
    const mins = Math.floor((elapsed % 3600) / 60);
    const secs = elapsed % 60;
    const time = hours > 0
        ? `${hours}:${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`
        : `${mins}:${String(secs).padStart(2, '0')}`;
    return <span className="voice-timer">{time}</span>;
};

const STATUS_OPTIONS = [
    { value: 'online', label: 'Online', color: '#3ba55c' },
    { value: 'idle', label: 'Idle', color: '#faa61a' },
    { value: 'dnd', label: 'Do Not Disturb', color: '#ed4245' },
    { value: 'invisible', label: 'Invisible', color: '#747f8d' },
];

const UserControlPanel = React.memo(({ username, userId }) => {
    const { session, idle, searchDB } = usePlatform();
    const { isMuted, isDeafened, toggleMute, toggleDeafen, connectionState, disconnectVoice } = useVoice();
    const [showStatusMenu, setShowStatusMenu] = useState(false);
    const [showUserSettings, setShowUserSettings] = useState(false);
    const [currentStatus, setCurrentStatus] = useState('online');
    const updateStatusMutation = useMutation(api.auth.updateStatus);
    const navigate = useNavigate();
    const manualStatusRef = useRef(false);
    const preIdleStatusRef = useRef('online');
    const hasInitializedRef = useRef(false);
    const currentStatusRef = useRef(currentStatus);
    currentStatusRef.current = currentStatus;

    // Fetch stored status preference from server and sync local state
    const allUsers = useQuery(api.auth.getPublicKeys) || [];
    const myUser = allUsers.find(u => u.id === userId);
    React.useEffect(() => {
        if (myUser) {
            const isInitial = !hasInitializedRef.current;
            if (isInitial) hasInitializedRef.current = true;

            // 'idle' is auto-set by the idle detector, not a user preference —
            // on a fresh app launch, reset it to 'online' just like 'offline'
            const shouldReset = !myUser.status || myUser.status === 'offline'
                || (isInitial && myUser.status === 'idle');

            if (shouldReset) {
                setCurrentStatus('online');
                manualStatusRef.current = false;
                if (userId) {
                    updateStatusMutation({ userId, status: 'online' }).catch(() => {});
                }
            } else if (myUser.status) {
                setCurrentStatus(myUser.status);
                manualStatusRef.current = (myUser.status === 'dnd' || myUser.status === 'invisible');
            }
        }
    }, [myUser?.status]);

    const handleLogout = async () => {
        // Disconnect voice if connected
        if (connectionState === 'connected') {
            try { disconnectVoice(); } catch {}
        }
        // Save and close search DB
        if (searchDB?.isOpen()) {
            try { await searchDB.save(); searchDB.close(); } catch {}
        }
        // Clear persisted session
        if (session) {
            try { await session.clear(); } catch {}
        }
        // Clear storage (preserve theme and user preferences)
        const theme = localStorage.getItem('theme');
        const savedPrefs = {};
        for (let i = 0; i < localStorage.length; i++) {
            const key = localStorage.key(i);
            if (key.startsWith('userPrefs_')) {
                savedPrefs[key] = localStorage.getItem(key);
            }
        }
        localStorage.clear();
        if (theme) localStorage.setItem('theme', theme);
        for (const [key, value] of Object.entries(savedPrefs)) {
            localStorage.setItem(key, value);
        }
        sessionStorage.clear();
        navigate('/');
    };

    const effectiveMute = isMuted || isDeafened;
    const statusColor = STATUS_OPTIONS.find(s => s.value === currentStatus)?.color || '#3ba55c';

    const handleStatusChange = async (status) => {
        manualStatusRef.current = (status !== 'online');
        setCurrentStatus(status);
        setShowStatusMenu(false);
        if (userId) {
            try {
                await updateStatusMutation({ userId, status });
            } catch (e) {
                console.error('Failed to update status:', e);
            }
        }
    };

    // Auto-idle detection via platform idle API
    useEffect(() => {
        if (!idle || !userId) return;
        const handleIdleChange = (data) => {
            if (manualStatusRef.current) return;
            if (data.isIdle) {
                preIdleStatusRef.current = currentStatusRef.current;
                setCurrentStatus('idle');
                updateStatusMutation({ userId, status: 'idle' }).catch(() => {});
            } else {
                const restoreTo = preIdleStatusRef.current || 'online';
                setCurrentStatus(restoreTo);
                updateStatusMutation({ userId, status: restoreTo }).catch(() => {});
            }
        };
        idle.onIdleStateChanged(handleIdleChange);
        return () => idle.removeIdleStateListener();
    }, [userId]);

    return (
        <div style={{
            height: '64px',
            margin: '0 8px 8px 8px',
            borderRadius: connectionState === 'connected' ? '0px 0px 8px 8px' : '8px',
            backgroundColor: 'var(--panel-bg)',
            display: 'flex',
            alignItems: 'center',
            padding: '0 8px',
            flexShrink: 0,
            position: 'relative'
        }}>
            {showStatusMenu && (
                <div className="status-menu">
                    {STATUS_OPTIONS.map(opt => (
                        <div
                            key={opt.value}
                            className="status-menu-item"
                            onClick={() => handleStatusChange(opt.value)}
                        >
                            <div className="status-menu-dot" style={{ backgroundColor: opt.color }} />
                            <span>{opt.label}</span>
                        </div>
                    ))}
                </div>
            )}
            <div className="user-control-info" onClick={() => setShowStatusMenu(!showStatusMenu)}>
                <div style={{ position: 'relative', marginRight: '8px' }}>
                    <Avatar username={username} avatarUrl={myUser?.avatarUrl} size={32} />
                    <div style={{
                        position: 'absolute',
                        bottom: '-2px',
                        right: '-2px',
                        width: '10px',
                        height: '10px',
                        borderRadius: '50%',
                        backgroundColor: statusColor,
                        border: '2px solid var(--panel-bg)',
                        cursor: 'pointer'
                    }} />
                </div>
                <div style={{ display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
                     <div style={{ color: 'var(--header-primary)', fontWeight: '600', fontSize: '14px', lineHeight: '18px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                        {username || 'Unknown'}
                    </div>
                    <div style={{ color: 'var(--header-secondary)', fontSize: '12px', lineHeight: '13px' }}>
                        {STATUS_OPTIONS.find(s => s.value === currentStatus)?.label || 'Online'}
                    </div>
                </div>
            </div>

            <div style={{ display: 'flex' }}>
                <Tooltip text={effectiveMute ? "Unmute" : "Mute"} position="top">
                    <button onClick={toggleMute} style={controlButtonStyle}>
                        <ColoredIcon
                            src={effectiveMute ? mutedIcon : muteIcon}
                            color={effectiveMute ? ICON_COLOR_ACTIVE : ICON_COLOR_DEFAULT}
                        />
                    </button>
                </Tooltip>
                <Tooltip text={isDeafened ? "Undeafen" : "Deafen"} position="top">
                    <button onClick={toggleDeafen} style={controlButtonStyle}>
                        <ColoredIcon
                            src={isDeafened ? defeanedIcon : defeanIcon}
                            color={isDeafened ? ICON_COLOR_ACTIVE : ICON_COLOR_DEFAULT}
                        />
                    </button>
                </Tooltip>
                <Tooltip text="User Settings" position="top">
                    <button style={controlButtonStyle} onClick={() => setShowUserSettings(true)}>
                         <ColoredIcon
                            src={settingsIcon}
                            color={ICON_COLOR_DEFAULT}
                        />
                    </button>
                </Tooltip>
            </div>
            {showUserSettings && (
                <UserSettings
                    onClose={() => setShowUserSettings(false)}
                    userId={userId}
                    username={username}
                    onLogout={handleLogout}
                />
            )}
        </div>
    );
});



const headerButtonStyle = {
    background: 'transparent',
    border: 'none',
    color: 'var(--header-secondary)',
    cursor: 'pointer',
    fontSize: '18px',
    padding: '0 4px'
};

const voicePanelButtonStyle = {
    flex: 1,
    alignItems: 'center',
    minHeight: '32px',
    background: 'hsla(240, 4%, 60.784%, 0.078)',
    border: 'hsla(0, 0%, 100%, 0.078)',
    borderColor: 'hsla(240, 4%, 60.784%, 0.039)',
    borderRadius: '8px',
    cursor: 'pointer',
    padding: '4px',
    display: 'flex',
    justifyContent: 'center'
};

const liveBadgeStyle = {
    backgroundColor: '#ed4245',
    borderRadius: '8px',
    padding: '0 6px',
    textOverflow: 'ellipsis',
    whiteSpace: 'nowrap',
    overflow: 'hidden',
    textAlign: 'center',
    height: '16px',
    minHeight: '16px',
    minWidth: '16px',
    color: 'hsl(0, 0%, 100%)',
    fontSize: '12px',
    fontWeight: '700',
    letterSpacing: '.02em',
    lineHeight: '1.3333333333333333',
    textTransform: 'uppercase',
    display: 'flex',
    alignItems: 'center',
    marginRight: '4px'
};

const ACTIVE_SPEAKER_SHADOW = '0 0 0 0px hsl(134.526, 41.485%, 44.902%), inset 0 0 0 2px hsl(134.526, 41.485%, 44.902%), inset 0 0 0 3px hsl(240, 7.143%, 10.98%)';
const VOICE_ACTIVE_COLOR = 'hsl(132.809, 34.902%, 50%)';

async function encryptKeyForUsers(convex, channelId, keyHex, crypto) {
    const users = await convex.query(api.auth.getPublicKeys, {});
    const batchKeys = [];

    for (const u of users) {
        if (!u.public_identity_key) continue;
        try {
            const payload = JSON.stringify({ [channelId]: keyHex });
            const encryptedKeyHex = await crypto.publicEncrypt(u.public_identity_key, payload);
            batchKeys.push({
                channelId,
                userId: u.id,
                encryptedKeyBundle: encryptedKeyHex,
                keyVersion: 1
            });
        } catch (e) {
            console.error("Failed to encrypt for user", u.id, e);
        }
    }

    await convex.mutation(api.channelKeys.uploadKeys, { keys: batchKeys });
}

function getScreenCaptureConstraints(selection) {
    if (selection.type === 'device') {
        return { video: { deviceId: { exact: selection.deviceId } }, audio: false };
    }
    return {
        audio: selection.shareAudio ? {
            mandatory: {
                chromeMediaSource: 'desktop',
                chromeMediaSourceId: selection.sourceId
            }
        } : false,
        video: {
            mandatory: {
                chromeMediaSource: 'desktop',
                chromeMediaSourceId: selection.sourceId
            }
        }
    };
}

const VoiceUserContextMenu = ({ x, y, onClose, user, onMute, isMuted, onServerMute, isServerMuted, hasPermission, onDisconnect, hasDisconnectPermission, onMessage, isSelf, userVolume, onVolumeChange }) => {
    const menuRef = useRef(null);
    const [pos, setPos] = useState({ top: y, left: x });

    useEffect(() => {
        const h = () => onClose();
        window.addEventListener('click', h);
        window.addEventListener('close-context-menus', h);
        return () => { window.removeEventListener('click', h); window.removeEventListener('close-context-menus', h); };
    }, [onClose]);

    useLayoutEffect(() => {
        if (!menuRef.current) return;
        const rect = menuRef.current.getBoundingClientRect();
        let newTop = y, newLeft = x;
        if (x + rect.width > window.innerWidth) newLeft = x - rect.width;
        if (y + rect.height > window.innerHeight) newTop = y - rect.height;
        if (newLeft < 0) newLeft = 10;
        if (newTop < 0) newTop = 10;
        setPos({ top: newTop, left: newLeft });
    }, [x, y]);

    const sliderPercent = (userVolume / 200) * 100;

    return (
        <div ref={menuRef} className="context-menu" style={{ top: pos.top, left: pos.left }} onClick={(e) => e.stopPropagation()}>
            {!isSelf && (
                <>
                    <div className="context-menu-volume" onMouseDown={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}>
                        <div className="context-menu-volume-label">
                            <span>User Volume</span>
                            <span>{userVolume}%</span>
                        </div>
                        <input
                            type="range"
                            min="0"
                            max="200"
                            value={userVolume}
                            onChange={(e) => onVolumeChange(Number(e.target.value))}
                            className="context-menu-volume-slider"
                            style={{ background: `linear-gradient(to right, hsl(235, 86%, 65%) ${sliderPercent}%, var(--bg-tertiary) ${sliderPercent}%)` }}
                        />
                    </div>
                    <div className="context-menu-separator" />
                </>
            )}
            <div
                className="context-menu-item context-menu-checkbox-item"
                role="menuitemcheckbox"
                aria-checked={isMuted}
                onClick={(e) => { e.stopPropagation(); onMute(); }}
            >
                <span>Mute</span>
                <div className="context-menu-checkbox">
                    <div className={`context-menu-checkbox-indicator ${isMuted ? 'checked' : ''}`}>
                        {isMuted ? (
                            <svg width="18" height="18" viewBox="0 0 24 24">
                                <path fill="white" d="M8.99991 16.17L4.82991 12L3.40991 13.41L8.99991 19L20.9999 7L19.5899 5.59L8.99991 16.17Z" />
                            </svg>
                        ) : (
                            <svg width="10" height="10" viewBox="0 0 10 10">
                            </svg>
                        )}
                    </div>
                </div>
            </div>
            {hasPermission && (
                <div
                    className="context-menu-item context-menu-checkbox-item"
                    role="menuitemcheckbox"
                    aria-checked={isServerMuted}
                    onClick={(e) => { e.stopPropagation(); onServerMute(); }}
                >
                    <span style={{ color: SERVER_MUTE_RED }}>Server Mute</span>
                    <div className="context-menu-checkbox">
                        <div
                            className={`context-menu-checkbox-indicator ${isServerMuted ? 'checked' : ''}`}
                            style={isServerMuted ? { backgroundColor: SERVER_MUTE_RED, borderColor: SERVER_MUTE_RED } : {}}
                        >
                            {isServerMuted ? (
                                <svg width="18" height="18" viewBox="0 0 24 24">
                                    <path fill="white" d="M8.99991 16.17L4.82991 12L3.40991 13.41L8.99991 19L20.9999 7L19.5899 5.59L8.99991 16.17Z" />
                                </svg>
                            ) : (
                                <svg width="10" height="10" viewBox="0 0 10 10">
                                </svg>
                            )}
                        </div>
                    </div>
                </div>
            )}
            {!isSelf && hasDisconnectPermission && (
                <div
                    className="context-menu-item"
                    onClick={(e) => { e.stopPropagation(); onDisconnect(); onClose(); }}
                >
                    <span style={{ color: SERVER_MUTE_RED }}>Disconnect</span>
                </div>
            )}
            <div className="context-menu-separator" />
            <div className="context-menu-item" onClick={(e) => { e.stopPropagation(); onMessage(); onClose(); }}>
                <span>Message</span>
            </div>
        </div>
    );
};

const ChannelListContextMenu = ({ x, y, onClose, onCreateChannel, onCreateCategory }) => {
    const menuRef = useRef(null);
    const [pos, setPos] = useState({ top: y, left: x });

    useEffect(() => {
        const h = () => onClose();
        window.addEventListener('click', h);
        window.addEventListener('close-context-menus', h);
        return () => { window.removeEventListener('click', h); window.removeEventListener('close-context-menus', h); };
    }, [onClose]);

    useLayoutEffect(() => {
        if (!menuRef.current) return;
        const rect = menuRef.current.getBoundingClientRect();
        let newTop = y, newLeft = x;
        if (x + rect.width > window.innerWidth) newLeft = x - rect.width;
        if (y + rect.height > window.innerHeight) newTop = y - rect.height;
        if (newLeft < 0) newLeft = 10;
        if (newTop < 0) newTop = 10;
        setPos({ top: newTop, left: newLeft });
    }, [x, y]);

    return (
        <div ref={menuRef} className="context-menu" style={{ top: pos.top, left: pos.left }} onClick={(e) => e.stopPropagation()}>
            <div className="context-menu-item" onClick={(e) => { e.stopPropagation(); onCreateChannel(); onClose(); }}>
                <span>Create Channel</span>
            </div>
            <div className="context-menu-item" onClick={(e) => { e.stopPropagation(); onCreateCategory(); onClose(); }}>
                <span>Create Category</span>
            </div>
        </div>
    );
};

const CreateChannelModal = ({ onClose, onSubmit, categoryId }) => {
    const [channelType, setChannelType] = useState('text');
    const [channelName, setChannelName] = useState('');

    const handleSubmit = () => {
        if (!channelName.trim()) return;
        onSubmit(channelName.trim(), channelType, categoryId);
        onClose();
    };

    return (
        <div className="create-channel-modal-overlay" onClick={onClose}>
            <div className="create-channel-modal" onClick={(e) => e.stopPropagation()}>
                <div className="create-channel-modal-header">
                    <div>
                        <h2 style={{ margin: 0, color: 'var(--header-primary)', fontSize: '20px', fontWeight: 700 }}>Create Channel</h2>
                        <p style={{ margin: '4px 0 0', color: 'var(--header-secondary)', fontSize: '12px' }}>in Text Channels</p>
                    </div>
                    <button className="create-channel-modal-close" onClick={onClose}>
                        <svg width="24" height="24" viewBox="0 0 24 24">
                            <path fill="currentColor" d="M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z" />
                        </svg>
                    </button>
                </div>

                <div style={{ padding: '0 16px' }}>
                    <div style={{ marginBottom: '16px' }}>
                        <label style={{ display: 'block', color: 'var(--header-secondary)', fontSize: '12px', fontWeight: 700, textTransform: 'uppercase', marginBottom: '8px' }}>Channel Type</label>

                        <div
                            className={`channel-type-option ${channelType === 'text' ? 'selected' : ''}`}
                            onClick={() => setChannelType('text')}
                        >
                            <div style={{ display: 'flex', alignItems: 'center', gap: '12px', flex: 1 }}>
                                <span style={{ fontSize: '24px', color: 'var(--interactive-normal)', width: '24px', textAlign: 'center' }}>#</span>
                                <div>
                                    <div style={{ color: 'var(--header-primary)', fontWeight: 500, fontSize: '16px' }}>Text</div>
                                    <div style={{ color: 'var(--header-secondary)', fontSize: '12px', marginTop: '2px' }}>Send messages, images, GIFs, emoji, opinions, and puns</div>
                                </div>
                            </div>
                            <div className={`channel-type-radio ${channelType === 'text' ? 'selected' : ''}`}>
                                {channelType === 'text' && <div className="channel-type-radio-dot" />}
                            </div>
                        </div>

                        <div
                            className={`channel-type-option ${channelType === 'voice' ? 'selected' : ''}`}
                            onClick={() => setChannelType('voice')}
                        >
                            <div style={{ display: 'flex', alignItems: 'center', gap: '12px', flex: 1 }}>
                                <svg width="24" height="24" viewBox="0 0 24 24" style={{ flexShrink: 0, color: 'var(--interactive-normal)' }}>
                                    <path fill="currentColor" d="M11.383 3.07904C11.009 2.92504 10.579 3.01004 10.293 3.29604L6.586 7.00304H3C2.45 7.00304 2 7.45304 2 8.00304V16.003C2 16.553 2.45 17.003 3 17.003H6.586L10.293 20.71C10.579 20.996 11.009 21.082 11.383 20.927C11.757 20.772 12 20.407 12 20.003V4.00304C12 3.59904 11.757 3.23404 11.383 3.07904ZM14 5.00304V7.00304C16.757 7.00304 19 9.24604 19 12.003C19 14.76 16.757 17.003 14 17.003V19.003C17.86 19.003 21 15.863 21 12.003C21 8.14304 17.86 5.00304 14 5.00304ZM14 9.00304V15.003C15.654 15.003 17 13.657 17 12.003C17 10.349 15.654 9.00304 14 9.00304Z" />
                                </svg>
                                <div>
                                    <div style={{ color: 'var(--header-primary)', fontWeight: 500, fontSize: '16px' }}>Voice</div>
                                    <div style={{ color: 'var(--header-secondary)', fontSize: '12px', marginTop: '2px' }}>Hang out together with voice, video, and screen share</div>
                                </div>
                            </div>
                            <div className={`channel-type-radio ${channelType === 'voice' ? 'selected' : ''}`}>
                                {channelType === 'voice' && <div className="channel-type-radio-dot" />}
                            </div>
                        </div>
                    </div>

                    <div style={{ marginBottom: '16px' }}>
                        <label style={{ display: 'block', color: 'var(--header-secondary)', fontSize: '12px', fontWeight: 700, textTransform: 'uppercase', marginBottom: '8px' }}>Channel Name</label>
                        <div className="create-channel-name-input-wrapper">
                            <span style={{ color: 'var(--interactive-normal)', fontSize: '16px', marginRight: '4px' }}>
                                {channelType === 'text' ? '#' : '🔊'}
                            </span>
                            <input
                                autoFocus
                                type="text"
                                placeholder="new-channel"
                                value={channelName}
                                onChange={(e) => setChannelName(e.target.value.toLowerCase().replace(/\s+/g, '-'))}
                                onKeyDown={(e) => { if (e.key === 'Enter') handleSubmit(); }}
                                className="create-channel-name-input"
                            />
                        </div>
                    </div>
                </div>

                <div className="create-channel-modal-footer">
                    <button className="create-channel-cancel-btn" onClick={onClose}>Cancel</button>
                    <button
                        className="create-channel-submit-btn"
                        onClick={handleSubmit}
                        disabled={!channelName.trim()}
                    >
                        Create Channel
                    </button>
                </div>
            </div>
        </div>
    );
};

const CreateCategoryModal = ({ onClose, onSubmit }) => {
    const [categoryName, setCategoryName] = useState('');

    const handleSubmit = () => {
        if (!categoryName.trim()) return;
        onSubmit(categoryName.trim());
        onClose();
    };

    return (
        <div className="create-channel-modal-overlay" onClick={onClose}>
            <div className="create-channel-modal" onClick={(e) => e.stopPropagation()}>
                <div className="create-channel-modal-header">
                    <h2 style={{ margin: 0, color: 'var(--header-primary)', fontSize: '20px', fontWeight: 700 }}>Create Category</h2>
                    <button className="create-channel-modal-close" onClick={onClose}>
                        <svg width="24" height="24" viewBox="0 0 24 24">
                            <path fill="currentColor" d="M18.4 4L12 10.4L5.6 4L4 5.6L10.4 12L4 18.4L5.6 20L12 13.6L18.4 20L20 18.4L13.6 12L20 5.6L18.4 4Z" />
                        </svg>
                    </button>
                </div>

                <div style={{ padding: '0 16px' }}>
                    <div style={{ marginBottom: '16px' }}>
                        <label style={{ display: 'block', color: 'var(--header-secondary)', fontSize: '12px', fontWeight: 700, textTransform: 'uppercase', marginBottom: '8px' }}>Category Name</label>
                        <div className="create-channel-name-input-wrapper">
                            <input
                                autoFocus
                                type="text"
                                placeholder="New Category"
                                value={categoryName}
                                onChange={(e) => setCategoryName(e.target.value)}
                                onKeyDown={(e) => { if (e.key === 'Enter') handleSubmit(); }}
                                className="create-channel-name-input"
                            />
                        </div>
                    </div>

                    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
                        <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
                            <svg width="16" height="16" viewBox="0 0 24 24" style={{ color: 'var(--interactive-normal)' }}>
                                <path fill="currentColor" d="M17 11V7C17 4.243 14.757 2 12 2C9.243 2 7 4.243 7 7V11C5.897 11 5 11.896 5 13V20C5 21.103 5.897 22 7 22H17C18.103 22 19 21.103 19 20V13C19 11.896 18.103 11 17 11ZM12 18C11.172 18 10.5 17.328 10.5 16.5C10.5 15.672 11.172 15 12 15C12.828 15 13.5 15.672 13.5 16.5C13.5 17.328 12.828 18 12 18ZM15 11H9V7C9 5.346 10.346 4 12 4C13.654 4 15 5.346 15 7V11Z" />
                            </svg>
                            <span style={{ color: 'var(--header-primary)', fontSize: '14px', fontWeight: 500 }}>Private Category</span>
                        </div>
                        <div className="category-toggle-switch">
                            <div className="category-toggle-knob" />
                        </div>
                    </div>
                    <p style={{ color: 'var(--header-secondary)', fontSize: '12px', margin: '0 0 16px', lineHeight: '16px' }}>
                        By making a category private, only selected members and roles will be able to view this category. Synced channels will automatically match this category's permissions.
                    </p>
                </div>

                <div className="create-channel-modal-footer">
                    <button className="create-channel-cancel-btn" onClick={onClose}>Cancel</button>
                    <button
                        className="create-channel-submit-btn"
                        onClick={handleSubmit}
                        disabled={!categoryName.trim()}
                    >
                        Create Category
                    </button>
                </div>
            </div>
        </div>
    );
};

// --- DnD wrapper components ---

const SortableCategory = ({ id, children }) => {
    const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
        id,
        data: { type: 'category' },
    });

    const style = {
        transform: CSS.Transform.toString(transform),
        transition,
        opacity: isDragging ? 0.5 : 1,
    };

    return (
        <div ref={setNodeRef} style={style} {...attributes}>
            {React.Children.map(children, (child, i) => {
                // First child is the category header — attach drag listeners to it
                if (i === 0 && React.isValidElement(child)) {
                    return React.cloneElement(child, { dragListeners: listeners });
                }
                return child;
            })}
        </div>
    );
};

const SortableChannel = ({ id, children }) => {
    const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
        id,
        data: { type: 'channel' },
    });

    const style = {
        transform: CSS.Transform.toString(transform),
        transition,
        opacity: isDragging ? 0.5 : 1,
    };

    return (
        <div ref={setNodeRef} style={style} {...attributes}>
            {typeof children === 'function' ? children(listeners) : children}
        </div>
    );
};

const DraggableVoiceUser = ({ userId, channelId, disabled, children }) => {
    const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
        id: `voice-user-${userId}`,
        data: { type: 'voice-user', userId, channelId },
        disabled,
    });

    return (
        <div
            ref={setNodeRef}
            {...attributes}
            {...listeners}
            style={{
                opacity: isDragging ? 0.4 : 1,
                cursor: disabled ? 'default' : 'grab',
            }}
        >
            {children}
        </div>
    );
};

const Sidebar = ({ channels, categories, activeChannel, onSelectChannel, username, channelKeys, view, onViewChange, onOpenDM, activeDMChannel, setActiveDMChannel, dmChannels, userId, serverName = 'Secure Chat', serverIconUrl, isMobile }) => {
    const { crypto, settings } = usePlatform();
    const [isCreating, setIsCreating] = useState(false);
    const [isServerSettingsOpen, setIsServerSettingsOpen] = useState(false);
    const [newChannelName, setNewChannelName] = useState('');
    const [newChannelType, setNewChannelType] = useState('text');
    const [editingChannel, setEditingChannel] = useState(null);
    const [isScreenShareModalOpen, setIsScreenShareModalOpen] = useState(false);
    const [collapsedCategories, setCollapsedCategories] = useState(() => {
        const effectiveUserId = userId || localStorage.getItem('userId');
        return getUserPref(effectiveUserId, 'collapsedCategories', {});
    });
    useEffect(() => {
        if (userId) {
            setCollapsedCategories(getUserPref(userId, 'collapsedCategories', {}));
        }
    }, [userId]);
    const [channelListContextMenu, setChannelListContextMenu] = useState(null);
    const [voiceUserMenu, setVoiceUserMenu] = useState(null);
    const [showCreateChannelModal, setShowCreateChannelModal] = useState(false);
    const [showCreateCategoryModal, setShowCreateCategoryModal] = useState(false);
    const [createChannelCategoryId, setCreateChannelCategoryId] = useState(null);
    const [activeDragItem, setActiveDragItem] = useState(null);
    const [dragOverChannelId, setDragOverChannelId] = useState(null);

    const convex = useConvex();

    // Permissions for move_members gating
    const myPermissions = useQuery(
        api.roles.getMyPermissions,
        userId ? { userId } : "skip"
    ) || {};

    // DnD sensors
    const sensors = useSensors(
        useSensor(PointerSensor, { activationConstraint: { distance: 5 } })
    );

    // Unread tracking
    const channelIds = React.useMemo(() => [
        ...channels.map(c => c._id),
        ...dmChannels.map(dm => dm.channel_id)
    ], [channels, dmChannels]);
    const rawAllReadStates = useQuery(
        api.readState.getAllReadStates,
        userId ? { userId } : "skip"
    );
    const rawLatestTimestamps = useQuery(
        api.readState.getLatestMessageTimestamps,
        channelIds.length > 0 ? { channelIds } : "skip"
    );
    const allReadStates = rawAllReadStates || [];
    const latestTimestamps = rawLatestTimestamps || [];
    const unreadQueriesLoaded = rawAllReadStates !== undefined && rawLatestTimestamps !== undefined;

    const unreadChannels = React.useMemo(() => {
        const set = new Set();
        const readMap = new Map();
        for (const rs of allReadStates) {
            readMap.set(rs.channelId, rs.lastReadTimestamp);
        }
        for (const lt of latestTimestamps) {
            const lastRead = readMap.get(lt.channelId);
            if (lastRead === undefined || lt.latestTimestamp > lastRead) {
                set.add(lt.channelId);
            }
        }
        return set;
    }, [allReadStates, latestTimestamps]);

    const unreadDMs = React.useMemo(() =>
        dmChannels.filter(dm =>
            unreadChannels.has(dm.channel_id) &&
            !(view === 'me' && activeDMChannel?.channel_id === dm.channel_id)
        ),
        [dmChannels, unreadChannels, view, activeDMChannel]
    );

    const { connectToVoice, activeChannelId: voiceChannelId, connectionState, disconnectVoice, activeChannelName: voiceChannelName, voiceStates, room, activeSpeakers, setScreenSharing, isPersonallyMuted, togglePersonalMute, isMuted: selfMuted, toggleMute, serverMute, disconnectUser, isServerMuted, serverSettings, getUserVolume, setUserVolume, isReceivingScreenShareAudio } = useVoice();

    const prevUnreadDMsRef = useRef(null);

    useEffect(() => {
        if (!unreadQueriesLoaded) return;

        const currentIds = new Set(
            dmChannels.filter(dm => unreadChannels.has(dm.channel_id)).map(dm => dm.channel_id)
        );

        if (prevUnreadDMsRef.current === null) {
            prevUnreadDMsRef.current = currentIds;
            return;
        }

        for (const id of currentIds) {
            if (!prevUnreadDMsRef.current.has(id)) {
                if (!isReceivingScreenShareAudio) {
                    const audio = new Audio(PingSound);
                    audio.volume = 0.5;
                    audio.play().catch(() => {});
                }
                break;
            }
        }

        prevUnreadDMsRef.current = currentIds;
    }, [dmChannels, unreadChannels, unreadQueriesLoaded, isReceivingScreenShareAudio]);

    const onRenameChannel = () => {};

    const onDeleteChannel = (id) => {
        if (activeChannel === id) onSelectChannel(null);
    };

    const handleStartCreate = () => {
        setIsCreating(true);
        setNewChannelName('');
        setNewChannelType('text');
    };

    const handleSubmitCreate = async (e) => {
        if (e) e.preventDefault();

        if (!newChannelName.trim()) {
            setIsCreating(false);
            return;
        }

        const name = newChannelName.trim();
        const userId = localStorage.getItem('userId');

        if (!userId) {
            alert("Please login first.");
            setIsCreating(false);
            return;
        }

        try {
            const { id: channelId } = await convex.mutation(api.channels.create, { name, type: newChannelType });
            const keyHex = randomHex(32);

            try {
                await encryptKeyForUsers(convex, channelId, keyHex, crypto);
            } catch (keyErr) {
                console.error("Critical: Failed to distribute keys", keyErr);
                alert("Channel created but key distribution failed.");
            }
        } catch (err) {
            console.error(err);
            alert("Failed to create channel: " + err.message);
        } finally {
            setIsCreating(false);
        }
    };

    const handleCreateInvite = async () => {
        const userId = localStorage.getItem('userId');
        if (!userId) {
            alert("Error: No User ID found. Please login again.");
            return;
        }

        const generalChannel = channels.find(c => c.name === 'general');
        const targetChannelId = generalChannel ? generalChannel._id : activeChannel;

        if (!targetChannelId) {
            alert("No channel selected.");
            return;
        }

        const targetKey = channelKeys?.[targetChannelId];

        if (!targetKey) {
            alert("Error: You don't have the key for this channel yet, so you can't invite others.");
            return;
        }

        try {
            const inviteCode = crypto.randomUUID();
            const inviteSecret = randomHex(32);

            const payload = JSON.stringify({ [targetChannelId]: targetKey });
            const encrypted = await crypto.encryptData(payload, inviteSecret);
            const blob = JSON.stringify({ c: encrypted.content, t: encrypted.tag, iv: encrypted.iv });

            await convex.mutation(api.invites.create, {
                code: inviteCode,
                encryptedPayload: blob,
                createdBy: userId,
                keyVersion: 1
            });

            const baseUrl = import.meta.env.VITE_APP_URL || window.location.origin;
            const link = `${baseUrl}/#/register?code=${inviteCode}&key=${inviteSecret}`;
            navigator.clipboard.writeText(link);
            alert(`Invite Link Copied to Clipboard!\n\n${link}`);
        } catch (e) {
            console.error("Invite Error:", e);
            alert("Failed to create invite. See console.");
        }
    };

    const handleScreenShareSelect = async (selection) => {
        if (!room) return;

        try {
            if (room.localParticipant.isScreenShareEnabled) {
                await room.localParticipant.setScreenShareEnabled(false);
            }

            let stream;
            try {
                stream = await navigator.mediaDevices.getUserMedia(getScreenCaptureConstraints(selection));
            } catch (audioErr) {
                // Audio capture may fail (e.g. macOS/Linux) — retry video-only
                if (selection.shareAudio) {
                    console.warn("Audio capture failed, falling back to video-only:", audioErr.message);
                    stream = await navigator.mediaDevices.getUserMedia(getScreenCaptureConstraints({ ...selection, shareAudio: false }));
                } else {
                    throw audioErr;
                }
            }

            const track = stream.getVideoTracks()[0];
            if (!track) return;

            await room.localParticipant.publishTrack(track, {
                name: 'screen_share',
                source: Track.Source.ScreenShare
            });

            // Publish audio track if present (system audio from desktop capture)
            const audioTrack = stream.getAudioTracks()[0];
            if (audioTrack) {
                await room.localParticipant.publishTrack(audioTrack, {
                    name: 'screen_share_audio',
                    source: Track.Source.ScreenShareAudio
                });
            }

            if (!isReceivingScreenShareAudio) new Audio(screenShareStartSound).play();
            setScreenSharing(true);

            track.onended = () => {
                // Clean up audio track when video track ends
                if (audioTrack) {
                    audioTrack.stop();
                    room.localParticipant.unpublishTrack(audioTrack);
                }
                setScreenSharing(false);
                room.localParticipant.setScreenShareEnabled(false).catch(console.error);
            };
        } catch (err) {
            console.error("Error sharing screen:", err);
            alert("Failed to share screen: " + err.message);
        }
    };

    const handleScreenShareClick = () => {
        if (room?.localParticipant.isScreenShareEnabled) {
            // Clean up any screen share audio tracks before stopping
            for (const pub of room.localParticipant.trackPublications.values()) {
                const source = pub.source ? pub.source.toString().toLowerCase() : '';
                const name = pub.trackName ? pub.trackName.toLowerCase() : '';
                if (source === 'screen_share_audio' || name === 'screen_share_audio') {
                    if (pub.track) pub.track.stop();
                    room.localParticipant.unpublishTrack(pub.track);
                }
            }
            room.localParticipant.setScreenShareEnabled(false);
            if (!isReceivingScreenShareAudio) new Audio(screenShareStopSound).play();
            setScreenSharing(false);
        } else {
            setIsScreenShareModalOpen(true);
        }
    };

    const handleChannelClick = (channel) => {
        if (channel.type === 'voice') {
            if (voiceChannelId !== channel._id) {
                connectToVoice(channel._id, channel.name, localStorage.getItem('userId'));
            }
            onSelectChannel(channel._id);
        } else {
            onSelectChannel(channel._id);
        }
    };

    const renderDMView = () => (
        <div className="channel-list" style={{ flex: 1, overflowY: 'auto', display: 'flex', flexDirection: 'column', backgroundColor: 'var(--bg-tertiary)', borderLeft: '1px solid var(--app-frame-border)', borderTop: '1px solid var(--app-frame-border)', borderRadius: '8px 0 0 0' }}>
            <DMList
                dmChannels={dmChannels}
                activeDMChannel={activeDMChannel}
                onSelectDM={(dm) => setActiveDMChannel(dm === 'friends' ? null : dm)}
                onOpenDM={onOpenDM}
                voiceStates={voiceStates}
            />
        </div>
    );

    const renderVoiceUsers = (channel) => {
        const users = voiceStates[channel._id];
        if (channel.type !== 'voice' || !users?.length) return null;

        return (
            <div style={{ marginLeft: 32, marginBottom: 8 }}>
                {users.map(user => (
                    <DraggableVoiceUser
                        key={user.userId}
                        userId={user.userId}
                        channelId={channel._id}
                        disabled={!myPermissions.move_members}
                    >
                        <div
                            className="voice-user-item"
                            style={{ display: 'flex', alignItems: 'center', marginBottom: 2 }}
                            onContextMenu={(e) => {
                                e.preventDefault();
                                e.stopPropagation();
                                window.dispatchEvent(new Event('close-context-menus'));
                                setVoiceUserMenu({ x: e.clientX, y: e.clientY, user });
                            }}
                        >
                            <Avatar
                                username={user.username}
                                avatarUrl={user.avatarUrl}
                                size={24}
                                style={{
                                    marginRight: 8,
                                    boxShadow: activeSpeakers.has(user.userId) ? ACTIVE_SPEAKER_SHADOW : 'none'
                                }}
                            />
                            <span style={{ color: 'var(--header-secondary)', fontSize: 14 }}>{user.username}</span>
                            <div style={{ display: 'flex', marginLeft: 'auto', gap: 4, alignItems: 'center', marginRight: "16px" }}>
                                {user.isScreenSharing && <div style={liveBadgeStyle}>Live</div>}
                                {user.isServerMuted ? (
                                    <ColoredIcon src={serverMuteIcon} color={SERVER_MUTE_RED} size="14px" />
                                ) : isPersonallyMuted(user.userId) ? (
                                    <ColoredIcon src={personalMuteIcon} color="var(--header-secondary)" size="14px" />
                                ) : (user.isMuted || user.isDeafened) ? (
                                    <ColoredIcon src={mutedIcon} color="var(--header-secondary)" size="14px" />
                                ) : null}
                                {user.isDeafened && (
                                    <ColoredIcon src={defeanedIcon} color="var(--header-secondary)" size="14px" />
                                )}
                            </div>
                        </div>
                    </DraggableVoiceUser>
                ))}
            </div>
        );
    };

    const renderCollapsedVoiceUsers = (channel) => {
        const users = voiceStates[channel._id];
        if (channel.type !== 'voice' || !users?.length) return null;

        return (
            <div
                className={`channel-item ${voiceChannelId === channel._id ? 'voice-active' : ''}`}
                onClick={() => handleChannelClick(channel)}
                style={{ position: 'relative', display: 'flex', alignItems: 'center', paddingRight: '8px' }}
            >
                <div style={{ marginRight: 6 }}>
                    <ColoredIcon src={voiceIcon} size="16px" color={VOICE_ACTIVE_COLOR} />
                </div>
                <div style={{ display: 'flex', alignItems: 'center' }}>
                    {users.map(user => (
                        <div key={user.userId} style={{ marginRight: -6, position: 'relative', zIndex: 1 }}>
                            <Avatar
                                username={user.username}
                                avatarUrl={user.avatarUrl}
                                size={24}
                                style={{
                                    boxShadow: activeSpeakers.has(user.userId) ? ACTIVE_SPEAKER_SHADOW : 'none',
                                    borderRadius: '50%',
                                }}
                            />
                        </div>
                    ))}
                </div>
            </div>
        );
    };

    const toggleCategory = useCallback((cat) => {
        setCollapsedCategories(prev => {
            const next = { ...prev, [cat]: !prev[cat] };
            setUserPref(userId, 'collapsedCategories', next, settings);
            return next;
        });
    }, [userId, settings]);

    const handleAddChannelToCategory = useCallback((groupId) => {
        setCreateChannelCategoryId(groupId === '__uncategorized__' ? null : groupId);
        setShowCreateChannelModal(true);
    }, []);

    // Group channels by categoryId
    const groupedChannels = React.useMemo(() => {
        const groups = [];
        const channelsByCategory = new Map();

        channels.forEach(ch => {
            const catId = ch.categoryId || '__uncategorized__';
            if (!channelsByCategory.has(catId)) channelsByCategory.set(catId, []);
            channelsByCategory.get(catId).push(ch);
        });

        // Sort channels within each category by position
        for (const [, list] of channelsByCategory) {
            list.sort((a, b) => (a.position ?? 0) - (b.position ?? 0));
        }

        // Add uncategorized at top
        const uncategorized = channelsByCategory.get('__uncategorized__');
        if (uncategorized?.length) {
            groups.push({ id: '__uncategorized__', name: 'Channels', channels: uncategorized });
        }

        // Add categories in position order
        for (const cat of (categories || [])) {
            groups.push({ id: cat._id, name: cat.name, channels: channelsByCategory.get(cat._id) || [] });
        }

        return groups;
    }, [channels, categories]);

    // DnD items
    const categoryDndIds = React.useMemo(() => groupedChannels.map(g => `category-${g.id}`), [groupedChannels]);

    const handleDragStart = (event) => {
        const { active } = event;
        const activeType = active.data.current?.type;
        if (activeType === 'category') {
            const catId = active.id.replace('category-', '');
            const group = groupedChannels.find(g => g.id === catId);
            setActiveDragItem({ type: 'category', name: group?.name || '' });
        } else if (activeType === 'channel') {
            const chId = active.id.replace('channel-', '');
            const ch = channels.find(c => c._id === chId);
            setActiveDragItem({ type: 'channel', channel: ch });
        } else if (activeType === 'voice-user') {
            const targetUserId = active.data.current.userId;
            const sourceChannelId = active.data.current.channelId;
            const users = voiceStates[sourceChannelId];
            const user = users?.find(u => u.userId === targetUserId);
            setActiveDragItem({ type: 'voice-user', user, sourceChannelId });
        }
    };

    const handleDragOver = (event) => {
        const { active, over } = event;
        if (!active?.data.current || active.data.current.type !== 'voice-user') {
            setDragOverChannelId(null);
            return;
        }
        if (over) {
            // Check if hovering over a voice channel (channel item or its DnD wrapper)
            const overType = over.data.current?.type;
            if (overType === 'channel') {
                const chId = over.id.replace('channel-', '');
                const ch = channels.find(c => c._id === chId);
                if (ch?.type === 'voice') {
                    setDragOverChannelId(ch._id);
                    return;
                }
            }
        }
        setDragOverChannelId(null);
    };

    const handleDragEnd = async (event) => {
        setActiveDragItem(null);
        setDragOverChannelId(null);
        const { active, over } = event;
        if (!over || active.id === over.id) return;

        const activeType = active.data.current?.type;
        const overType = over.data.current?.type;

        // Handle voice-user drag
        if (activeType === 'voice-user') {
            if (overType !== 'channel') return;
            const targetChId = over.id.replace('channel-', '');
            const targetChannel = channels.find(c => c._id === targetChId);
            if (!targetChannel || targetChannel.type !== 'voice') return;
            const sourceChannelId = active.data.current.channelId;
            if (sourceChannelId === targetChId) return;
            try {
                await convex.mutation(api.voiceState.moveUser, {
                    actorUserId: userId,
                    targetUserId: active.data.current.userId,
                    targetChannelId: targetChId,
                });
            } catch (e) {
                console.error('Failed to move voice user:', e);
            }
            return;
        }

        if (activeType === 'category' && overType === 'category') {
            // Reorder categories
            const oldIndex = groupedChannels.findIndex(g => `category-${g.id}` === active.id);
            const newIndex = groupedChannels.findIndex(g => `category-${g.id}` === over.id);
            if (oldIndex === -1 || newIndex === -1) return;

            // Build reordered array (only real categories, skip uncategorized)
            const reordered = [...groupedChannels];
            const [moved] = reordered.splice(oldIndex, 1);
            reordered.splice(newIndex, 0, moved);

            const updates = reordered
                .filter(g => g.id !== '__uncategorized__')
                .map((g, i) => ({ id: g.id, position: i * 1000 }));

            if (updates.length > 0) {
                try {
                    await convex.mutation(api.categories.reorder, { updates });
                } catch (e) {
                    console.error('Failed to reorder categories:', e);
                }
            }
        } else if (activeType === 'channel') {
            const activeChId = active.id.replace('channel-', '');

            if (overType === 'channel') {
                const overChId = over.id.replace('channel-', '');
                const activeChannel = channels.find(c => c._id === activeChId);
                const overChannel = channels.find(c => c._id === overChId);
                if (!activeChannel || !overChannel) return;

                const targetCategoryId = overChannel.categoryId;
                const targetGroup = groupedChannels.find(g => g.id === (targetCategoryId || '__uncategorized__'));
                if (!targetGroup) return;

                // Build new order for the target category
                const targetChannels = [...targetGroup.channels];

                // Remove active channel if it's already in this category
                const existingIdx = targetChannels.findIndex(c => c._id === activeChId);
                if (existingIdx !== -1) targetChannels.splice(existingIdx, 1);

                // Insert at the position of the over channel
                const overIdx = targetChannels.findIndex(c => c._id === overChId);
                targetChannels.splice(overIdx, 0, activeChannel);

                const updates = targetChannels.map((ch, i) => ({
                    id: ch._id,
                    categoryId: targetCategoryId,
                    position: i * 1000,
                }));

                try {
                    await convex.mutation(api.channels.reorderChannels, { updates });
                } catch (e) {
                    console.error('Failed to reorder channels:', e);
                }
            } else if (overType === 'category') {
                // Drop channel onto a category header — move it to end of that category
                const targetCatId = over.id.replace('category-', '');
                const targetCategoryId = targetCatId === '__uncategorized__' ? undefined : targetCatId;
                const targetGroup = groupedChannels.find(g => g.id === targetCatId);
                const maxPos = (targetGroup?.channels || []).reduce((max, c) => Math.max(max, c.position ?? 0), -1000);

                try {
                    await convex.mutation(api.channels.moveChannel, {
                        id: activeChId,
                        categoryId: targetCategoryId,
                        position: maxPos + 1000,
                    });
                } catch (e) {
                    console.error('Failed to move channel:', e);
                }
            }
        }
    };

    const renderServerView = () => (
        <div style={{ flex: 1, overflowY: 'auto', display: 'flex', flexDirection: 'column', backgroundColor: 'var(--bg-tertiary)', borderLeft: '1px solid var(--app-frame-border)', borderTop: '1px solid var(--app-frame-border)', borderRadius: '8px 0 0 0' }}>
            <div className="server-header" style={{ borderBottom: '1px solid var(--app-frame-border)' }}>
                <span className="server-header-name" onClick={() => setIsServerSettingsOpen(true)}>{serverName}</span>
                <button className="server-header-invite" onClick={handleCreateInvite} title="Invite People">
                    <img src={inviteUserIcon} alt="Invite" />
                </button>
            </div>

            <div className="channel-list" style={{ flex: 1, overflowY: 'auto', backgroundColor: 'var(--bg-tertiary)' }} onContextMenu={(e) => {
                if (!e.target.closest('.channel-item') && !e.target.closest('.channel-category-header')) {
                    e.preventDefault();
                    window.dispatchEvent(new Event('close-context-menus'));
                    setChannelListContextMenu({ x: e.clientX, y: e.clientY });
                }
            }}>
                {isCreating && (
                    <div style={{ padding: '0 8px', marginBottom: '4px' }}>
                         <form onSubmit={handleSubmitCreate}>
                            <div style={{ display: 'flex', gap: 4, marginBottom: 4 }}>
                                <label style={{ color: newChannelType==='text'?'white':'var(--interactive-normal)', fontSize: 10, cursor: 'pointer' }} onClick={()=>setNewChannelType('text')}>
                                    Text
                                </label>
                                <label style={{ color: newChannelType==='voice'?'white':'var(--interactive-normal)', fontSize: 10, cursor: 'pointer' }} onClick={()=>setNewChannelType('voice')}>
                                    Voice
                                </label>
                            </div>
                            <input
                                autoFocus
                                type="text"
                                placeholder={`new-${newChannelType}-channel`}
                                value={newChannelName}
                                onChange={(e) => setNewChannelName(e.target.value)}
                                style={{
                                    width: '100%',
                                    background: 'var(--bg-tertiary)',
                                    border: '1px solid var(--brand-experiment)',
                                    borderRadius: '4px',
                                    color: 'var(--text-normal)',
                                    padding: '4px 8px',
                                    fontSize: '14px',
                                    outline: 'none'
                                }}
                            />
                         </form>
                         <div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2, textAlign: 'right' }}>
                             Press Enter to Create {newChannelType === 'voice' && '(Voice)'}
                         </div>
                    </div>
                )}

                <DndContext
                    sensors={sensors}
                    collisionDetection={closestCenter}
                    onDragStart={handleDragStart}
                    onDragOver={handleDragOver}
                    onDragEnd={handleDragEnd}
                >
                    <SortableContext items={categoryDndIds} strategy={verticalListSortingStrategy}>
                        {groupedChannels.map(group => {
                            const channelDndIds = group.channels.map(ch => `channel-${ch._id}`);
                            return (
                                <SortableCategory key={group.id} id={`category-${group.id}`}>
                                    <CategoryHeader
                                        group={group}
                                        groupId={group.id}
                                        collapsed={collapsedCategories[group.id]}
                                        onToggle={toggleCategory}
                                        onAddChannel={handleAddChannelToCategory}
                                    />
                                    {(() => {
                                        const isCollapsed = collapsedCategories[group.id];
                                        const visibleChannels = isCollapsed
                                            ? group.channels.filter(ch =>
                                                ch._id === activeChannel ||
                                                (ch.type === 'voice' && voiceStates[ch._id]?.length > 0)
                                              )
                                            : group.channels;
                                        if (visibleChannels.length === 0) return null;
                                        const visibleDndIds = visibleChannels.map(ch => `channel-${ch._id}`);
                                        return (
                                        <SortableContext items={visibleDndIds} strategy={verticalListSortingStrategy}>
                                            {visibleChannels.map(channel => {
                                                const isUnread = activeChannel !== channel._id && unreadChannels.has(channel._id);
                                                return (
                                                    <SortableChannel key={channel._id} id={`channel-${channel._id}`}>
                                                        {(channelDragListeners) => (
                                                        <React.Fragment>
                                                            {!(isCollapsed && channel.type === 'voice' && voiceStates[channel._id]?.length > 0) && <div
                                                                className={`channel-item ${activeChannel === channel._id ? 'active' : ''} ${voiceChannelId === channel._id ? 'voice-active' : ''} ${dragOverChannelId === channel._id ? 'voice-drop-target' : ''}`}
                                                                onClick={() => handleChannelClick(channel)}
                                                                {...channelDragListeners}
                                                                style={{
                                                                    position: 'relative',
                                                                    display: 'flex',
                                                                    justifyContent: 'space-between',
                                                                    alignItems: 'center',
                                                                    paddingRight: '8px'
                                                                }}
                                                            >
                                                                {isUnread && <div className="channel-unread-indicator" />}
                                                                <div style={{ display: 'flex', alignItems: 'center', overflow: 'hidden', flex: 1 }}>
                                                                    {channel.type === 'voice' ? (
                                                                         <div style={{ marginRight: 6 }}>
                                                                             <ColoredIcon
                                                                                src={voiceIcon}
                                                                                size="16px"
                                                                                color={voiceStates[channel._id]?.length > 0 ? VOICE_ACTIVE_COLOR : "var(--interactive-normal)"}
                                                                             />
                                                                         </div>
                                                                    ) : (
                                                                        <span style={{ color: 'var(--interactive-normal)', marginRight: '6px', flexShrink: 0 }}>#</span>
                                                                    )}
                                                                    <span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', ...(isUnread ? { color: 'var(--header-primary)', fontWeight: 600 } : {}) }}>
                                                                        {channel.name}{serverSettings?.afkChannelId === channel._id ? ' (AFK)' : ''}
                                                                    </span>
                                                                </div>

                                                                <button
                                                                    className="channel-settings-icon"
                                                                    onClick={(e) => {
                                                                        e.stopPropagation();
                                                                        setEditingChannel(channel);
                                                                    }}
                                                                    style={{
                                                                        background: 'transparent',
                                                                        border: 'none',
                                                                        cursor: 'pointer',
                                                                        padding: '2px 4px',
                                                                        display: 'flex', alignItems: 'center',
                                                                    }}
                                                                >
                                                                    <ColoredIcon src={settingsIcon} color="var(--interactive-normal)" size="14px" />
                                                                </button>
                                                            </div>}
                                                            {isCollapsed
                                                                ? renderCollapsedVoiceUsers(channel)
                                                                : renderVoiceUsers(channel)}
                                                        </React.Fragment>
                                                        )}
                                                    </SortableChannel>
                                                );
                                            })}
                                        </SortableContext>
                                        );
                                    })()}
                                </SortableCategory>
                            );
                        })}
                    </SortableContext>

                    <DragOverlay>
                        {activeDragItem?.type === 'channel' && activeDragItem.channel && (
                            <div className="drag-overlay-channel">
                                {activeDragItem.channel.type === 'voice' ? (
                                    <ColoredIcon src={voiceIcon} size="16px" color="var(--interactive-normal)" />
                                ) : (
                                    <span style={{ color: 'var(--interactive-normal)', marginRight: '6px' }}>#</span>
                                )}
                                <span>{activeDragItem.channel.name}</span>
                            </div>
                        )}
                        {activeDragItem?.type === 'category' && (
                            <div className="drag-overlay-category">
                                {activeDragItem.name}
                            </div>
                        )}
                        {activeDragItem?.type === 'voice-user' && activeDragItem.user && (
                            <div className="drag-overlay-voice-user">
                                <Avatar
                                    username={activeDragItem.user.username}
                                    avatarUrl={activeDragItem.user.avatarUrl}
                                    size={24}
                                    style={{ marginRight: 8 }}
                                />
                                <span>{activeDragItem.user.username}</span>
                            </div>
                        )}
                    </DragOverlay>
                </DndContext>
            </div>
        </div>
    );

    return (
        <div className="sidebar" style={{ display: 'flex', flexDirection: 'column' }}>
            <div style={{ display: 'flex', flex: 1, minHeight: 0 }}>
                <div className="server-list">
                    <div className="server-item-wrapper">
                        <div className={`server-pill ${view === 'me' ? 'active' : ''}`} />
                        <Tooltip text="Direct Messages" position="right">
                            <div
                                className={`server-icon ${view === 'me' ? 'active' : ''}`}
                                onClick={() => onViewChange('me')}
                                style={{
                                     backgroundColor: view === 'me' ? 'var(--brand-experiment)' : 'var(--bg-primary)',
                                     color: view === 'me' ? '#fff' : 'var(--text-normal)',
                                     cursor: 'pointer'
                                }}
                            >
                                <svg width="28" height="20" viewBox="0 0 28 20">
                                    <path fill="currentColor" d="M23.0212 1.67671C21.3107 0.879656 19.5079 0.318797 17.6584 0C17.4062 0.461742 17.1749 0.934541 16.9708 1.4184C15.0172 1.11817 13.0907 1.11817 11.1372 1.4184C10.9331 0.934541 10.7018 0.461742 10.4496 0C8.59007 0.318797 6.78726 0.879656 5.0768 1.67671C1.65217 6.84883 0.706797 11.8997 1.166 16.858C3.39578 18.5135 5.56066 19.5165 7.6473 20.1417C8.16912 19.4246 8.63212 18.6713 9.02347 17.8863C8.25707 17.5929 7.52187 17.2415 6.82914 16.8374C7.0147 16.6975 7.19515 16.5518 7.36941 16.402C11.66 18.396 16.4523 18.396 20.7186 16.402C20.8953 16.5518 21.0758 16.6975 21.2613 16.8374C20.5663 17.2435 19.8288 17.5947 19.0624 17.8863C19.4537 18.6713 19.9167 19.4246 20.4385 20.1417C22.5276 19.5165 24.6925 18.5135 26.9223 16.858C27.4684 11.2365 25.9961 6.22055 23.0212 1.67671ZM9.68041 13.6383C8.39754 13.6383 7.34085 12.4456 7.34085 10.994C7.34085 9.5424 8.37555 8.34973 9.68041 8.34973C10.9893 8.34973 12.0395 9.5424 12.0175 10.994C12.0175 12.4456 10.9893 13.6383 9.68041 13.6383ZM18.3161 13.6383C17.0332 13.6383 15.9765 12.4456 15.9765 10.994C15.9765 9.5424 17.0112 8.34973 18.3161 8.34973C19.625 8.34973 20.6752 9.5424 20.6532 10.994C20.6532 12.4456 19.625 13.6383 18.3161 13.6383Z"/>
                                </svg>
                            </div>
                        </Tooltip>
                    </div>

                    {unreadDMs.map(dm => (
                        <div key={dm.channel_id} className="server-item-wrapper">
                            <div className={`server-pill ${
                                view === 'me' && activeDMChannel?.channel_id === dm.channel_id ? 'active' : ''
                            }`} />
                            <Tooltip text={dm.other_username} position="right">
                                <div
                                    className={`server-icon dm-server-icon ${
                                        view === 'me' && activeDMChannel?.channel_id === dm.channel_id ? 'active' : ''
                                    }`}
                                    onClick={() => {
                                        setActiveDMChannel(dm);
                                        onViewChange('me');
                                    }}
                                >
                                    <Avatar
                                        username={dm.other_username}
                                        avatarUrl={dm.other_user_avatar_url}
                                        size={48}
                                    />
                                    <div className="dm-notification-dot" />
                                </div>
                            </Tooltip>
                        </div>
                    ))}

                    <div className="server-separator" />

                    <div className="server-item-wrapper">
                        <div className={`server-pill ${view === 'server' ? 'active' : ''}`} />
                        <Tooltip text={serverName} position="right">
                            <div
                                className={`server-icon ${view === 'server' ? 'active' : ''}`}
                                onClick={() => onViewChange('server')}
                                style={{ cursor: 'pointer' }}
                            >
                                {serverIconUrl ? (
                                    <img src={serverIconUrl} alt={serverName} style={{ width: 48, height: 48, objectFit: 'cover', borderRadius: 'inherit' }} />
                                ) : (
                                    serverName.substring(0, 2)
                                )}
                            </div>
                        </Tooltip>
                    </div>
                </div>

                {view === 'me' ? renderDMView() : renderServerView()}
            </div>

            {(connectionState === 'connected' || connectionState === 'connecting') && (
                <div style={{
                    backgroundColor: 'var(--panel-bg)',
                    borderRadius: '8px 8px 0px 0px',
                    padding: 'calc(16px - 8px + 4px)',
                    margin: '8px 8px 0px 8px',
                    display: 'flex',
                    flexDirection: 'column',
                    borderBottom: '1px solid hsla(240, 4%, 60.784%, 0.039)'
                }}>
                    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4 }}>
                        <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                            <svg className={connectionState === 'connecting' ? 'voice-connecting-icon' : ''} width="18" height="18" viewBox="0 0 24 24" fill="none">
                                <rect x="2" y="16" width="4" height="6" rx="1" fill={connectionState === 'connected' ? '#43b581' : '#faa61a'} />
                                <rect x="8" y="12" width="4" height="10" rx="1" fill={connectionState === 'connected' ? '#43b581' : '#faa61a'} />
                                <rect x="14" y="7" width="4" height="15" rx="1" fill={connectionState === 'connected' ? '#43b581' : '#faa61a'} opacity={connectionState === 'connecting' ? 0.4 : 1} />
                                <rect x="20" y="2" width="4" height="20" rx="1" fill={connectionState === 'connected' ? '#43b581' : '#faa61a'} opacity={connectionState === 'connecting' ? 0.4 : 1} />
                            </svg>
                            <div style={{ color: connectionState === 'connected' ? '#43b581' : '#faa61a', fontWeight: 'bold', fontSize: 13 }}>
                                {connectionState === 'connected' ? 'Voice Connected' : 'Voice Connecting'}
                            </div>
                        </div>
                        <button
                             onClick={disconnectVoice}
                             title="Disconnect"
                             style={{
                                 background: 'transparent', border: 'none', cursor: 'pointer', padding: '0', display: 'flex', justifyContent: 'center'
                             }}
                        >
                             <ColoredIcon src={disconnectIcon} color="var(--header-secondary)" size="20px" />
                        </button>
                    </div>
                    <div style={{ color: 'var(--text-normal)', fontSize: 12, marginBottom: 4 }}>{dmChannels?.some(dm => dm.channel_id === voiceChannelId) ? `Call with ${voiceChannelName}` : `${voiceChannelName} / ${serverName}`}</div>
                    {connectionState === 'connected' && (
                        <>
                            <div style={{ marginBottom: 8 }}><VoiceTimer /></div>
                            <div style={{ display: 'flex', gap: 4 }}>
                                 <button onClick={() => room?.localParticipant.setCameraEnabled(!room.localParticipant.isCameraEnabled)} title="Turn On Camera" style={voicePanelButtonStyle}>
                                    <ColoredIcon src={cameraIcon} color="var(--header-secondary)" size="20px" />
                                </button>
                                 <button onClick={handleScreenShareClick} title="Share Screen" style={voicePanelButtonStyle}>
                                     <ColoredIcon src={screenIcon} color="var(--header-secondary)" size="20px" />
                                </button>
                            </div>
                        </>
                    )}
                </div>
            )}

            <UserControlPanel username={username} userId={userId} />

            {editingChannel && (
                <ChannelSettingsModal
                    channel={editingChannel}
                    onClose={() => setEditingChannel(null)}
                    onRename={onRenameChannel}
                    onDelete={onDeleteChannel}
                />
            )}
            {isServerSettingsOpen && (
                <ServerSettingsModal onClose={() => setIsServerSettingsOpen(false)} />
            )}
            {isScreenShareModalOpen && (
                <ScreenShareModal
                    onClose={() => setIsScreenShareModalOpen(false)}
                    onSelectSource={handleScreenShareSelect}
                />
            )}
            {channelListContextMenu && (
                <ChannelListContextMenu
                    x={channelListContextMenu.x}
                    y={channelListContextMenu.y}
                    onClose={() => setChannelListContextMenu(null)}
                    onCreateChannel={() => {
                        setCreateChannelCategoryId(null);
                        setShowCreateChannelModal(true);
                    }}
                    onCreateCategory={() => setShowCreateCategoryModal(true)}
                />
            )}
            {voiceUserMenu && (
                <VoiceUserContextMenu
                    x={voiceUserMenu.x}
                    y={voiceUserMenu.y}
                    user={voiceUserMenu.user}
                    onClose={() => setVoiceUserMenu(null)}
                    isSelf={voiceUserMenu.user.userId === userId}
                    isMuted={voiceUserMenu.user.userId === userId ? selfMuted : isPersonallyMuted(voiceUserMenu.user.userId)}
                    onMute={() => voiceUserMenu.user.userId === userId ? toggleMute() : togglePersonalMute(voiceUserMenu.user.userId)}
                    isServerMuted={isServerMuted(voiceUserMenu.user.userId)}
                    onServerMute={() => serverMute(voiceUserMenu.user.userId, !isServerMuted(voiceUserMenu.user.userId))}
                    hasPermission={!!myPermissions.mute_members}
                    onDisconnect={() => disconnectUser(voiceUserMenu.user.userId)}
                    hasDisconnectPermission={!!myPermissions.move_members}
                    onMessage={() => {
                        onOpenDM(voiceUserMenu.user.userId, voiceUserMenu.user.username);
                        onViewChange('me');
                    }}
                    userVolume={getUserVolume(voiceUserMenu.user.userId)}
                    onVolumeChange={(vol) => setUserVolume(voiceUserMenu.user.userId, vol)}
                />
            )}
            {showCreateChannelModal && (
                <CreateChannelModal
                    categoryId={createChannelCategoryId}
                    onClose={() => setShowCreateChannelModal(false)}
                    onSubmit={async (name, type, catId) => {
                        const userId = localStorage.getItem('userId');
                        if (!userId) { alert("Please login first."); return; }
                        try {
                            const createArgs = { name, type };
                            if (catId) createArgs.categoryId = catId;
                            const { id: channelId } = await convex.mutation(api.channels.create, createArgs);
                            const keyHex = randomHex(32);
                            try { await encryptKeyForUsers(convex, channelId, keyHex, crypto); }
                            catch (keyErr) { console.error("Critical: Failed to distribute keys", keyErr); alert("Channel created but key distribution failed."); }
                        } catch (err) { console.error(err); alert("Failed to create channel: " + err.message); }
                    }}
                />
            )}
            {showCreateCategoryModal && (
                <CreateCategoryModal
                    onClose={() => setShowCreateCategoryModal(false)}
                    onSubmit={async (name) => {
                        try {
                            await convex.mutation(api.categories.create, { name });
                        } catch (err) {
                            console.error(err);
                            alert("Failed to create category: " + err.message);
                        }
                    }}
                />
            )}
        </div>
    );
};

// Category header component (extracted for DnD drag handle)
const CategoryHeader = React.memo(({ group, groupId, collapsed, onToggle, onAddChannel, dragListeners }) => (
    <div className="channel-category-header" onClick={() => onToggle(groupId)} {...(dragListeners || {})}>
        <span className="category-label">{group.name}</span>
        <div className={`category-chevron ${collapsed ? 'collapsed' : ''}`}>
            <ColoredIcon src={categoryCollapsedIcon} color="currentColor" size="12px" />
        </div>
        <button className="category-add-btn" onClick={(e) => { e.stopPropagation(); onAddChannel(groupId); }} title="Create Channel">
            +
        </button>
    </div>
));

export default Sidebar;
