import { useState, useEffect, useCallback, useRef, useMemo, lazy, Suspense } from 'react';
import type { TransitionEvent } from 'react';
import { useTranslation } from 'react-i18next';
import { useTracks, RoomAudioRenderer, useLocalParticipant, useRoomContext, useParticipants, useConnectionState } from '@livekit/components-react';
import { isTrackReference } from '@livekit/components-react';
import { Track } from 'livekit-client';
import { VideoGrid } from './VideoGrid/VideoGrid';
import { FocusLayout } from './FocusLayout/FocusLayout';
import { ControlBar } from './ControlBar/ControlBar';
import { ChatPanel } from './ChatPanel/ChatPanel';
import { useJoinLeaveSound, useScreenShareSound } from './hooks/useJoinLeaveSound';
import { useMicSound, useTalkingWhileMuted } from './hooks/useMicSounds';
import { playMuteSound, playUnmuteSound } from '../../lib/sounds';
import { useIosAudioKeepAlive } from './hooks/useIosAudioKeepAlive';
import { useRoomChat } from './hooks/useRoomChat';
import { useFileTransfers } from './hooks/useFileTransfers';
import { usePushToTalk } from './hooks/usePushToTalk';
import { useAudioProcessor } from './hooks/useAudioProcessor';
import { useCameraProcessor } from './hooks/useCameraProcessor';
import { usePersistedVolumes } from '../../hooks/usePersistedVolumes';
import { useRoomKeyboard } from '../../hooks/useRoomKeyboard';
import { useLocalControls } from './hooks/useLocalControls';
import { useScreenShare, getScreenShareQuality } from './hooks/useScreenShare';
import type { ScreenShareQuality } from './hooks/useScreenShare';
import { useAvatarCache } from './hooks/useAvatarCache';
import { useRoomStatusSync } from './hooks/useRoomStatusSync';
import { useRoomSystemMessages } from './hooks/useRoomSystemMessages';
import { useAdminActions } from './hooks/useAdminActions';
import { useParticipantVolumeSync } from './hooks/useParticipantVolumeSync';
import { useChatNotifications } from './hooks/useChatNotifications';
import { useReconnectingBanner } from './hooks/useReconnectingBanner';
import { RoomUIProvider } from './RoomUIContext';
import type { AppSettings } from '../settings/types';
import { IS_ELECTRON as isElectron } from '../../lib/env';

const ScreenShareModal = lazy(() => import('./ScreenShareModal/ScreenShareModal').then(m => ({ default: m.ScreenShareModal })));
const SCREEN_SHARE_TRACKS = [Track.Source.ScreenShare];

interface RoomShellProps {
    name: string;  // display name — used to call setName on join/change
    myAvatarUrl?: string | null;
    onIdentityAssigned?: (identity: string) => void;
    onOpenSettings?: () => void;
    settings: AppSettings;
    onUpdateSettings: (patch: Partial<AppSettings>) => void;
    volumes: Map<string, number>;
    onVolumeChange: (identity: string, vol: number) => void;
}

/** Below this width the chat stays the existing floating/fullscreen overlay; at or above it, chat docks as a proper reflowing column. */
const DESKTOP_CHAT_QUERY = '(min-width: 900px)';

export function RoomShell({ name, myAvatarUrl, onIdentityAssigned, onOpenSettings, settings, onUpdateSettings, volumes, onVolumeChange }: RoomShellProps) {
    const { t } = useTranslation();
    const [chatOpen, setChatOpen] = useState(false);
    const [audioMuted, setAudioMuted] = useState(false);
    const [screenShareModalOpen, setScreenShareModalOpen] = useState(false);
    const [focusedIdentity, setFocusedIdentity] = useState<string | null>(null);
    const [screenShareError, setScreenShareError] = useState(false);
    const screenShareErrorTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
    const micWasEnabledRef = useRef(false);

    // Desktop: chat docks as a reflowing column (animated width). Mobile/narrow: existing overlay.
    const [isDesktopChat, setIsDesktopChat] = useState(() =>
        typeof window !== 'undefined' && window.matchMedia(DESKTOP_CHAT_QUERY).matches);
    useEffect(() => {
        const mq = window.matchMedia(DESKTOP_CHAT_QUERY);
        const onChange = () => setIsDesktopChat(mq.matches);
        mq.addEventListener('change', onChange);
        return () => mq.removeEventListener('change', onChange);
    }, []);

    // Keep the docked ChatPanel mounted through its ~200ms close animation so the slide-out
    // shows real content sliding away, rather than the box shrinking around empty space.
    const [chatMounted, setChatMounted] = useState(false);
    useEffect(() => { if (chatOpen) setChatMounted(true); }, [chatOpen]);
    const handleDockTransitionEnd = useCallback((e: TransitionEvent<HTMLDivElement>) => {
        if (e.target !== e.currentTarget || e.propertyName !== 'width') return;
        if (!chatOpen) setChatMounted(false);
    }, [chatOpen]);

    const showScreenShareError = useCallback(() => {
        setScreenShareError(true);
        if (screenShareErrorTimerRef.current) clearTimeout(screenShareErrorTimerRef.current);
        screenShareErrorTimerRef.current = setTimeout(() => setScreenShareError(false), 8000);
    }, []);

    useEffect(() => () => {
        if (screenShareErrorTimerRef.current) clearTimeout(screenShareErrorTimerRef.current);
    }, []);

    // Screen share volumes persisted separately
    const { volumes: screenVolumes, handleVolumeChange: handleScreenVolumeChange } =
        usePersistedVolumes('lala_screen_volumes');

    const { localParticipant, isMicrophoneEnabled } = useLocalParticipant();
    const participants = useParticipants();
    const room = useRoomContext();
    const connectionState = useConnectionState();

    // Keep LiveKit display name in sync with app display name
    useEffect(() => {
        localParticipant.setName(name);
    }, [name, localParticipant]);

    const avatarCache = useAvatarCache({
        room,
        participants,
        localParticipant,
        myAvatarUrl,
        onIdentityAssigned,
    });

    useRoomStatusSync({
        room,
        participants,
        audioMuted,
    });

    const { messages, send, isSending } = useRoomChat();
    const { fileTransfers, sendFile, cancelSend } = useFileTransfers(room, localParticipant);

    const { chatEntries, addSystem, suppressLeave } = useRoomSystemMessages(room, t, settings, messages, fileTransfers);

    const adminProps = useAdminActions({
        room,
        participants,
        localParticipant,
        t,
        settings,
        addSystem,
        suppressLeave,
    });

    usePushToTalk(settings.pushToTalk, settings.pushToTalkKey);
    useAudioProcessor({ noiseSuppressionMode: settings.noiseSuppressionMode, silenceGate: settings.silenceGate });
    useCameraProcessor(settings.cameraEffect);

    const { mic, cam } = useLocalControls();
    const { enabled: screenEnabled, start: startScreen, stop: stopScreen } = useScreenShare();

    useJoinLeaveSound(settings.soundJoinLeave ?? true);
    useScreenShareSound(settings.soundScreenShare ?? true);
    useMicSound(settings.soundMicFeedback ?? true);
    useTalkingWhileMuted(settings.audioInputDeviceId || undefined, settings.soundTalkingWhileMuted ?? true);
    useIosAudioKeepAlive(true);

    useParticipantVolumeSync({ participants, volumes, onVolumeChange, screenVolumes });

    const { unreadCount, resetUnread } = useChatNotifications(settings, messages, chatOpen, localParticipant.identity, t, fileTransfers);

    const handleOpenChat = useCallback(() => {
        setChatOpen((v) => {
            if (!v) resetUnread();
            return !v;
        });
    }, [resetUnread]);

    const handleFullscreen = useCallback(() => {
        if (!document.fullscreenEnabled) return;
        if (document.fullscreenElement) {
            document.exitFullscreen();
        } else {
            document.documentElement.requestFullscreen();
        }
    }, []);


    // Wayland detection: on Wayland, the XDG portal handles source selection natively,
    // so we skip our custom source picker and don't call setScreenShareSource IPC.
    const [isWayland, setIsWayland] = useState(false);
    useEffect(() => {
        if (!isElectron) return;
        window.electronAPI!.getAppInfo().then(info => {
            if (info.isWayland) setIsWayland(true);
        }).catch(() => {});
        // isElectron is a module-level constant — intentionally not a dependency
    }, []);

    const handleScreenShareToggle = useCallback(() => {
        if (screenEnabled) {
            stopScreen();
        } else if (settings.screenShareSkipDialog && (!isElectron || isWayland)) {
            console.log('[Lala] Screen share: skip dialog');
            startScreen(getScreenShareQuality(settings.screenShareFpsIdx, settings.screenShareBrIdx)).catch(showScreenShareError);
        } else {
            console.log('[Lala] Screen share: opening modal (isElectron:', isElectron, ', isWayland:', isWayland, ')');
            setScreenShareModalOpen(true);
        }
        // isElectron is a module-level constant — intentionally not a dependency
    }, [screenEnabled, startScreen, stopScreen, isWayland, settings.screenShareSkipDialog, settings.screenShareFpsIdx, settings.screenShareBrIdx, showScreenShareError]);

    const handleScreenShareModalConfirm = useCallback((quality: ScreenShareQuality, sourceId?: string, audio?: boolean) => {
        setScreenShareModalOpen(false);
        startScreen(quality, sourceId, audio).catch(showScreenShareError);
    }, [startScreen, showScreenShareError]);

    // Single owner of the deafen mute/unmute sound: when deafening also toggles
    // the mic, useMicSound already plays a sound for that mic state transition —
    // playing one here too would double up. Only play it directly here when the
    // mic doesn't change (already off while deafening, or was never re-enabled
    // while undeafening), since nothing else will provide that feedback.
    const handleToggleAudio = useCallback(() => {
        setAudioMuted((prev) => {
            const nextMuted = !prev;
            if (nextMuted) {
                micWasEnabledRef.current = isMicrophoneEnabled;
                if (isMicrophoneEnabled) localParticipant.setMicrophoneEnabled(false);
                else playMuteSound();
            } else {
                if (micWasEnabledRef.current) localParticipant.setMicrophoneEnabled(true);
                else playUnmuteSound();
            }
            const meta = JSON.stringify({ deafened: nextMuted });
            localParticipant.setMetadata(meta).catch(() => {
                setTimeout(() => localParticipant.setMetadata(meta).catch(console.warn), 1500);
            });
            return nextMuted;
        });
    }, [isMicrophoneEnabled, localParticipant]);

    useRoomKeyboard({
        onMicToggle: mic.toggle,
        onCamToggle: cam.toggle,
        onDeafen: handleToggleAudio,
        onChat: handleOpenChat,
        onFullscreen: handleFullscreen,
        onScreenShare: handleScreenShareToggle,
        onEscape: () => setChatOpen(false),
        pttKey: settings.pushToTalk ? settings.pushToTalkKey : undefined,
        enabled: settings.shortcutsEnabled ?? true,
        keyMic: settings.keyMic,
        keyCam: settings.keyCam,
        keyDeafen: settings.keyDeafen,
        keyChat: settings.keyChat,
        keyFullscreen: settings.keyFullscreen,
        keyScreenShare: settings.keyScreenShare,
    });

    const screenTracks = useTracks(SCREEN_SHARE_TRACKS);
    const isScreenSharing = screenTracks.some(
        (t) => isTrackReference(t) && t.publication.isSubscribed && !t.publication.isMuted,
    );

    const roomUIValue = useMemo(() => ({
        audioMuted,
        avatarCache,
        volumes,
        onVolumeChange,
        screenVolumes,
        onScreenVolumeChange: handleScreenVolumeChange,
        admin: adminProps,
        settings,
        onOpenSettings,
    }), [audioMuted, avatarCache, volumes, onVolumeChange, screenVolumes, handleScreenVolumeChange, adminProps, settings, onOpenSettings]);

    const showReconnecting = useReconnectingBanner(connectionState);

    return (
        <div style={{ display: 'flex', flexDirection: 'column', height: '100%', position: 'relative' }}>
            <RoomAudioRenderer muted={audioMuted} />

            {showReconnecting && (
                <div className="reconnecting-banner">
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="reconnecting-spinner">
                        <path d="M21 12a9 9 0 1 1-6.22-8.56" />
                    </svg>
                    {t('room.reconnecting')}
                </div>
            )}

            {isDesktopChat ? (
                // Desktop: chat docks as a reflowing flex column beside the stage. Only the dock's
                // width is animated (200ms ease-out); the stage reflows as a natural side effect of
                // flexbox, and the fixed-width panel inside means its own content never reflows
                // mid-slide. See chat-panel.css .cp-dock for the full rationale.
                <div className="cp-stage-row" style={{ paddingTop: 'env(safe-area-inset-top, 0px)', paddingBottom: 'calc(80px + env(safe-area-inset-bottom, 0px))' }}>
                    <div className="cp-stage">
                        <RoomUIProvider value={roomUIValue}>
                            {isScreenSharing || focusedIdentity
                                ? <FocusLayout
                                    initialIdentity={focusedIdentity}
                                    onExit={focusedIdentity && !isScreenSharing ? () => setFocusedIdentity(null) : undefined}
                                />
                                : <VideoGrid onFocusTile={setFocusedIdentity} onCamEnabled={setFocusedIdentity} />
                            }
                        </RoomUIProvider>
                    </div>
                    <div
                        className={`cp-dock${chatOpen ? ' cp-dock-open' : ''}`}
                        onTransitionEnd={handleDockTransitionEnd}
                    >
                        {chatMounted && (
                            <ChatPanel
                                entries={chatEntries}
                                send={send}
                                isSending={isSending}
                                onClose={() => setChatOpen(false)}
                                onSendFile={sendFile}
                                onCancelFile={cancelSend}
                            />
                        )}
                    </div>
                </div>
            ) : (
                <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', minHeight: 0, paddingTop: 'env(safe-area-inset-top, 0px)', paddingBottom: 'calc(80px + env(safe-area-inset-bottom, 0px))' }}>
                    <RoomUIProvider value={roomUIValue}>
                        {isScreenSharing || focusedIdentity
                            ? <FocusLayout
                                initialIdentity={focusedIdentity}
                                onExit={focusedIdentity && !isScreenSharing ? () => setFocusedIdentity(null) : undefined}
                            />
                            : <VideoGrid onFocusTile={setFocusedIdentity} onCamEnabled={setFocusedIdentity} />
                        }
                    </RoomUIProvider>
                </div>
            )}

            {!isDesktopChat && chatOpen && (
                <ChatPanel
                    entries={chatEntries}
                    send={send}
                    isSending={isSending}
                    onClose={() => setChatOpen(false)}
                    onSendFile={sendFile}
                    onCancelFile={cancelSend}
                    overlay
                />
            )}

            {screenShareModalOpen && (
                <Suspense fallback={null}>
                    <ScreenShareModal
                        settings={settings}
                        onUpdateSettings={onUpdateSettings}
                        onConfirm={handleScreenShareModalConfirm}
                        onCancel={() => setScreenShareModalOpen(false)}
                        skipSourcePicker={isWayland}
                    />
                </Suspense>
            )}

            {screenShareError && (
                <div className="screen-share-error-toast" role="alert">
                    <span>{t('room.screenShareFailed')}</span>
                    <button
                        className="screen-share-error-toast-close"
                        onClick={() => setScreenShareError(false)}
                        title={t('room.dismiss')}
                    >
                        ✕
                    </button>
                </div>
            )}

            <div style={{ position: 'absolute', bottom: 0, left: 0, right: 0 }}>
                <ControlBar
                    audioMuted={audioMuted}
                    onToggleAudio={handleToggleAudio}
                    chatOpen={chatOpen}
                    onToggleChat={handleOpenChat}
                    unreadCount={unreadCount}
                    onScreenShareClick={handleScreenShareToggle}
                />
            </div>
        </div>
    );
}
