/**
 * PAGI Chat - Multi-User Chat Demo
 *
 * Vanilla JavaScript frontend for the PAGI chat showcase application.
 * Demonstrates WebSocket, SSE, and HTTP API integration.
 */

(function() {
    'use strict';

    // ===== State =====
    const state = {
        username: '',
        userId: '',
        sessionId: '',          // Persistent session ID for reconnection
        currentRoom: 'general',
        rooms: {},
        users: {},
        ws: null,
        sse: null,
        typingTimeout: null,
        isTyping: false,
        reconnectAttempts: 0,
        maxReconnectDelay: 30000,   // Max 30 seconds between attempts
        pingInterval: null,
        pingIntervalMs: 10000,      // Send ping every 10 seconds
        lastPongTime: 0,            // Track last pong received
        heartbeatCheckInterval: null,
        heartbeatTimeoutMs: 35000,  // Consider connection dead if no pong in 35s
        lastMsgId: 0                // Track last received message ID for catch-up
    };

    // ===== DOM Elements =====
    const elements = {
        // Screens
        loginScreen: document.getElementById('login-screen'),
        chatScreen: document.getElementById('chat-screen'),

        // Login
        loginForm: document.getElementById('login-form'),
        usernameInput: document.getElementById('username'),

        // Sidebar
        themeToggle: document.getElementById('theme-toggle'),
        connectionStatus: document.getElementById('connection-status'),
        displayName: document.getElementById('display-name'),
        userAvatar: document.getElementById('user-avatar'),
        roomsList: document.getElementById('rooms-list'),
        usersList: document.getElementById('users-list'),
        userCount: document.getElementById('user-count'),
        createRoomBtn: document.getElementById('create-room-btn'),

        // Stats
        statUsers: document.getElementById('stat-users'),
        statRooms: document.getElementById('stat-rooms'),
        statUptime: document.getElementById('stat-uptime'),

        // Chat
        currentRoomName: document.getElementById('current-room-name'),
        typingIndicator: document.getElementById('typing-indicator'),
        leaveRoomBtn: document.getElementById('leave-room-btn'),
        messagesContainer: document.getElementById('messages-container'),
        messages: document.getElementById('messages'),
        messageForm: document.getElementById('message-form'),
        messageInput: document.getElementById('message-input'),

        // Modal
        createRoomModal: document.getElementById('create-room-modal'),
        createRoomForm: document.getElementById('create-room-form'),
        roomNameInput: document.getElementById('room-name'),
        cancelCreateRoom: document.getElementById('cancel-create-room'),

        // Toast
        toastContainer: document.getElementById('toast-container')
    };

    // ===== Theme Management =====
    function initTheme() {
        const savedTheme = localStorage.getItem('theme') || 'light';
        document.documentElement.setAttribute('data-theme', savedTheme);
        updateThemeIcon(savedTheme);
    }

    function toggleTheme() {
        const current = document.documentElement.getAttribute('data-theme');
        const next = current === 'dark' ? 'light' : 'dark';
        document.documentElement.setAttribute('data-theme', next);
        localStorage.setItem('theme', next);
        updateThemeIcon(next);
    }

    function updateThemeIcon(theme) {
        const sunIcon = elements.themeToggle.querySelector('.icon-sun');
        const moonIcon = elements.themeToggle.querySelector('.icon-moon');
        if (theme === 'dark') {
            sunIcon.classList.add('hidden');
            moonIcon.classList.remove('hidden');
        } else {
            sunIcon.classList.remove('hidden');
            moonIcon.classList.add('hidden');
        }
    }

    // ===== Connection Status =====
    function setConnectionStatus(status, extraInfo = '') {
        const el = elements.connectionStatus;
        const text = el.querySelector('.status-text');

        el.classList.remove('connected', 'disconnected');

        switch (status) {
            case 'connected':
                el.classList.add('connected');
                text.textContent = 'Connected';
                break;
            case 'disconnected':
                el.classList.add('disconnected');
                text.textContent = 'Disconnected';
                break;
            case 'connecting':
                text.textContent = 'Connecting...';
                break;
            case 'reconnecting':
                text.textContent = `Reconnecting...`;
                break;
        }
    }

    // ===== Toast Notifications =====
    function showToast(message, type = 'info') {
        const toast = document.createElement('div');
        toast.className = `toast ${type}`;
        toast.textContent = message;

        elements.toastContainer.appendChild(toast);

        setTimeout(() => {
            toast.style.animation = 'slideIn 0.3s ease reverse';
            setTimeout(() => toast.remove(), 300);
        }, 3000);
    }

    // ===== Exponential Backoff =====
    function calculateReconnectDelay() {
        // Formula: min(1000 * 2^attempts + random(0, 1000), 30000)
        const baseDelay = 1000 * Math.pow(2, state.reconnectAttempts);
        const jitter = Math.random() * 1000;
        return Math.min(baseDelay + jitter, state.maxReconnectDelay);
    }

    // ===== Session Management =====
    function getOrCreateSessionId() {
        let sessionId = localStorage.getItem('chat-session-id');
        if (!sessionId) {
            sessionId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
            localStorage.setItem('chat-session-id', sessionId);
        }
        return sessionId;
    }

    function clearSession() {
        localStorage.removeItem('chat-session-id');
        state.sessionId = '';
        state.lastMsgId = 0;
    }

    // ===== WebSocket Connection =====
    function connectWebSocket() {
        const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';

        // Get or create persistent session ID
        state.sessionId = getOrCreateSessionId();

        // Build connection URL with session info for resume support
        const params = new URLSearchParams({
            name: state.username,
            session: state.sessionId,
            lastMsgId: state.lastMsgId.toString()
        });
        const wsUrl = `${protocol}//${window.location.host}/ws/chat?${params}`;

        setConnectionStatus(state.reconnectAttempts > 0 ? 'reconnecting' : 'connecting');

        state.ws = new WebSocket(wsUrl);

        state.ws.onopen = () => {
            setConnectionStatus('connected');
            state.reconnectAttempts = 0;
            state.lastPongTime = Date.now();

            // Start keepalive ping interval
            if (state.pingInterval) {
                clearInterval(state.pingInterval);
            }
            state.pingInterval = setInterval(() => {
                if (state.ws && state.ws.readyState === WebSocket.OPEN) {
                    sendMessage({ type: 'ping' });
                }
            }, state.pingIntervalMs);

            // Start heartbeat timeout check
            if (state.heartbeatCheckInterval) {
                clearInterval(state.heartbeatCheckInterval);
            }
            state.heartbeatCheckInterval = setInterval(() => {
                const timeSinceLastPong = Date.now() - state.lastPongTime;
                if (timeSinceLastPong > state.heartbeatTimeoutMs) {
                    console.warn('Heartbeat timeout - connection appears dead, reconnecting...');
                    if (state.ws) {
                        state.ws.close();
                    }
                }
            }, 5000); // Check every 5 seconds
        };

        state.ws.onclose = (event) => {
            setConnectionStatus('disconnected');

            // Clear intervals
            if (state.pingInterval) {
                clearInterval(state.pingInterval);
                state.pingInterval = null;
            }
            if (state.heartbeatCheckInterval) {
                clearInterval(state.heartbeatCheckInterval);
                state.heartbeatCheckInterval = null;
            }

            // Always try to reconnect with exponential backoff
            state.reconnectAttempts++;
            const delay = calculateReconnectDelay();
            console.log(`Reconnecting in ${Math.round(delay)}ms (attempt ${state.reconnectAttempts})...`);
            setConnectionStatus('reconnecting');
            setTimeout(connectWebSocket, delay);
        };

        state.ws.onerror = (error) => {
            console.error('WebSocket error:', error);
        };

        state.ws.onmessage = (event) => {
            // Any message resets the heartbeat timer
            state.lastPongTime = Date.now();

            try {
                const data = JSON.parse(event.data);
                handleWebSocketMessage(data);
            } catch (e) {
                console.error('Failed to parse message:', e);
            }
        };
    }

    function handleWebSocketMessage(data) {
        // Track message IDs for catch-up on reconnect
        if (data.id && data.id > state.lastMsgId) {
            state.lastMsgId = data.id;
        }

        switch (data.type) {
            case 'connected':
                // New connection - store session info
                state.userId = data.user_id;
                state.sessionId = data.session_id;
                state.username = data.name;
                localStorage.setItem('chat-session-id', data.session_id);
                updateUserInfo();
                updateRoomsList(data.rooms.map(name => ({ name, users: 0 })));
                break;

            case 'resumed':
                // Session resumed after reconnect
                state.userId = data.session_id;  // session_id is the user_id
                state.sessionId = data.session_id;
                state.username = data.name;
                localStorage.setItem('chat-session-id', data.session_id);
                updateUserInfo();

                // Restore room state
                state.rooms = {};
                data.rooms.forEach(room => state.rooms[room] = true);

                // Apply missed messages
                if (data.missedMessages) {
                    for (const [room, messages] of Object.entries(data.missedMessages)) {
                        if (room === state.currentRoom) {
                            messages.forEach(msg => {
                                if (msg.type === 'system') {
                                    addSystemMessage(msg.text, false);
                                } else {
                                    addMessage(msg, false);
                                }
                                // Track highest message ID
                                if (msg.id && msg.id > state.lastMsgId) {
                                    state.lastMsgId = msg.id;
                                }
                            });
                            scrollToBottom();
                        }
                    }
                }

                updateRoomsList(data.rooms.map(name => ({ name, users: 0 })));
                showToast('Session resumed', 'success');
                break;

            case 'joined':
                state.rooms[data.room] = true;
                state.currentRoom = data.room;
                updateCurrentRoom();
                renderMessages(data.history || []);
                // Track highest message ID from history
                if (data.history) {
                    data.history.forEach(msg => {
                        if (msg.id && msg.id > state.lastMsgId) {
                            state.lastMsgId = msg.id;
                        }
                    });
                }
                updateUsersList(data.users || []);
                updateRoomsList();
                break;

            case 'left':
                delete state.rooms[data.room];
                // Switch to general if we left current room
                if (state.currentRoom === data.room) {
                    state.currentRoom = 'general';
                    updateCurrentRoom();
                    sendMessage({ type: 'get_history', room: 'general' });
                }
                updateRoomsList();
                break;

            case 'message':
            case 'action':
                // Track message ID for catch-up
                if (data.id && data.id > state.lastMsgId) {
                    state.lastMsgId = data.id;
                }
                if (data.room === state.currentRoom) {
                    addMessage(data);
                    // Clear typing indicator for this user since they sent a message
                    updateTypingIndicator(data.from, false);
                }
                break;

            case 'system':
                if (data.room === state.currentRoom) {
                    addSystemMessage(data.text);
                }
                break;

            case 'user_joined':
                if (data.room === state.currentRoom) {
                    addSystemMessage(`${data.user} joined the room`);
                    updateUsersList(data.users || []);
                }
                updateRoomsList();
                break;

            case 'user_left':
                if (data.room === state.currentRoom) {
                    addSystemMessage(`${data.user} left the room`);
                    updateUsersList(data.users || []);
                }
                updateRoomsList();
                break;

            case 'typing':
                if (data.room === state.currentRoom) {
                    updateTypingIndicator(data.user, data.typing);
                }
                break;

            case 'pm':
                addPrivateMessage(data, 'received');
                showToast(`Private message from ${data.from}`, 'info');
                break;

            case 'pm_sent':
                addPrivateMessage({ from: 'You', to: data.to, text: data.text, ts: data.ts }, 'sent');
                break;

            case 'nick_changed':
                if (data.old_name === state.username) {
                    state.username = data.new_name;
                    updateUserInfo();
                    showToast(`You are now known as ${data.new_name}`, 'success');
                } else if (data.room === state.currentRoom) {
                    addSystemMessage(`${data.old_name} is now known as ${data.new_name}`);
                    updateUsersList(data.users || []);
                }
                break;

            case 'room_list':
                updateRoomsList(data.rooms);
                break;

            case 'user_list':
                if (data.room === state.currentRoom) {
                    updateUsersList(data.users);
                }
                break;

            case 'history':
                if (data.room === state.currentRoom) {
                    renderMessages(data.messages || []);
                }
                break;

            case 'error':
                showToast(data.message, 'error');
                break;

            case 'pong':
            case 'server_ping':
                // Keepalive messages - no action needed
                break;
        }
    }

    function sendMessage(data) {
        if (state.ws && state.ws.readyState === WebSocket.OPEN) {
            state.ws.send(JSON.stringify(data));
        }
    }

    // ===== SSE Connection =====
    function connectSSE() {
        state.sse = new EventSource('/events');

        state.sse.addEventListener('stats', (event) => {
            try {
                const stats = JSON.parse(event.data);
                updateStats(stats);
            } catch (e) {
                console.error('Failed to parse stats:', e);
            }
        });

        state.sse.addEventListener('user_connected', (event) => {
            try {
                const data = JSON.parse(event.data);
                elements.statUsers.textContent = data.count;
            } catch (e) {}
        });

        state.sse.addEventListener('user_disconnected', (event) => {
            try {
                const data = JSON.parse(event.data);
                elements.statUsers.textContent = data.count;
            } catch (e) {}
        });

        state.sse.onerror = () => {
            // SSE will auto-reconnect
        };
    }

    function updateStats(stats) {
        elements.statUsers.textContent = stats.users_online;
        elements.statRooms.textContent = stats.rooms_count;
        elements.statUptime.textContent = formatUptime(stats.uptime);
    }

    function formatUptime(seconds) {
        if (seconds < 60) return `${seconds}s`;
        if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
        if (seconds < 86400) return `${Math.floor(seconds / 3600)}h`;
        return `${Math.floor(seconds / 86400)}d`;
    }

    // ===== UI Updates =====
    function updateUserInfo() {
        elements.displayName.textContent = state.username;
        elements.userAvatar.textContent = state.username.charAt(0).toUpperCase();
    }

    function updateCurrentRoom() {
        elements.currentRoomName.textContent = `#${state.currentRoom}`;
        elements.leaveRoomBtn.classList.toggle('hidden', state.currentRoom === 'general');
        elements.messages.innerHTML = '';

        // Highlight active room
        document.querySelectorAll('#rooms-list li').forEach(li => {
            li.classList.toggle('active', li.dataset.room === state.currentRoom);
        });
    }

    function updateRoomsList(rooms) {
        if (rooms) {
            state.roomsData = rooms;
        }

        const roomsData = state.roomsData || [];
        elements.roomsList.innerHTML = roomsData.map(room => `
            <li data-room="${escapeHtml(room.name)}"
                class="${room.name === state.currentRoom ? 'active' : ''}">
                <span>#${escapeHtml(room.name)}</span>
                <span class="room-users">${room.users}</span>
            </li>
        `).join('');

        // Add click handlers
        elements.roomsList.querySelectorAll('li').forEach(li => {
            li.addEventListener('click', () => {
                const roomName = li.dataset.room;
                if (roomName !== state.currentRoom) {
                    if (!state.rooms[roomName]) {
                        sendMessage({ type: 'join', room: roomName });
                    } else {
                        state.currentRoom = roomName;
                        updateCurrentRoom();
                        sendMessage({ type: 'get_history', room: roomName });
                        sendMessage({ type: 'get_users', room: roomName });
                    }
                }
            });
        });
    }

    function updateUsersList(users) {
        state.users = {};
        users.forEach(u => state.users[u.id] = u);

        elements.userCount.textContent = users.length;
        elements.usersList.innerHTML = users.map(user => `
            <li data-user-id="${escapeHtml(user.id)}">
                <span class="user-avatar" style="width: 24px; height: 24px; font-size: 0.75rem;">
                    ${escapeHtml(user.name.charAt(0).toUpperCase())}
                </span>
                <span>${escapeHtml(user.name)}${user.id === state.userId ? ' (you)' : ''}</span>
                ${user.typing ? '<span class="typing-dot"></span>' : ''}
            </li>
        `).join('');
    }

    function updateTypingIndicator(user, isTyping) {
        // Track who's typing
        if (!state.typingUsers) state.typingUsers = new Set();

        if (isTyping) {
            state.typingUsers.add(user);
        } else {
            state.typingUsers.delete(user);
        }

        const typingList = Array.from(state.typingUsers);
        if (typingList.length === 0) {
            elements.typingIndicator.classList.add('hidden');
        } else if (typingList.length === 1) {
            elements.typingIndicator.textContent = `${typingList[0]} is typing...`;
            elements.typingIndicator.classList.remove('hidden');
        } else if (typingList.length <= 3) {
            elements.typingIndicator.textContent = `${typingList.join(', ')} are typing...`;
            elements.typingIndicator.classList.remove('hidden');
        } else {
            elements.typingIndicator.textContent = 'Several people are typing...';
            elements.typingIndicator.classList.remove('hidden');
        }
    }

    // ===== Message Rendering =====
    function renderMessages(messages) {
        elements.messages.innerHTML = '';
        messages.forEach(msg => {
            if (msg.type === 'system') {
                addSystemMessage(msg.text, false);
            } else {
                addMessage(msg, false);
            }
        });
        scrollToBottom();
    }

    function addMessage(data, scroll = true) {
        const isOwn = data.from === state.username;
        const msgEl = document.createElement('div');
        msgEl.className = `message ${isOwn ? 'own' : 'other'} ${data.type === 'action' ? 'action' : ''}`;

        if (data.type === 'action') {
            msgEl.innerHTML = `<span class="message-text">${escapeHtml(data.text)}</span>`;
        } else {
            msgEl.innerHTML = `
                <div class="message-header">
                    <span class="message-author">${escapeHtml(data.from)}</span>
                    <span class="message-time">${formatTime(data.ts)}</span>
                </div>
                <div class="message-text">${formatMessageText(data.text)}</div>
            `;
        }

        elements.messages.appendChild(msgEl);
        if (scroll) scrollToBottom();
    }

    function addSystemMessage(text, scroll = true) {
        const msgEl = document.createElement('div');
        msgEl.className = 'message system';
        msgEl.innerHTML = `<span class="message-text">${escapeHtml(text)}</span>`;

        elements.messages.appendChild(msgEl);
        if (scroll) scrollToBottom();
    }

    function addPrivateMessage(data, direction) {
        const msgEl = document.createElement('div');
        msgEl.className = `message pm ${direction === 'sent' ? 'own' : 'other'}`;

        const label = direction === 'sent' ? `To ${data.to}` : `From ${data.from}`;

        msgEl.innerHTML = `
            <div class="message-header">
                <span class="message-author">[PM] ${escapeHtml(label)}</span>
                <span class="message-time">${formatTime(data.ts)}</span>
            </div>
            <div class="message-text">${formatMessageText(data.text)}</div>
        `;

        elements.messages.appendChild(msgEl);
        scrollToBottom();
    }

    function scrollToBottom() {
        elements.messagesContainer.scrollTop = elements.messagesContainer.scrollHeight;
    }

    function formatTime(ts) {
        if (!ts) return '';
        const date = new Date(ts * 1000);
        return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
    }

    function formatMessageText(text) {
        // Escape HTML first
        text = escapeHtml(text);

        // Convert newlines to <br> for multiline messages (like /help output)
        text = text.replace(/\n/g, '<br>');

        // Simple URL detection
        text = text.replace(
            /(https?:\/\/[^\s<]+)/g,
            '<a href="$1" target="_blank" rel="noopener">$1</a>'
        );

        return text;
    }

    function escapeHtml(text) {
        if (!text) return '';
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }

    // ===== Typing Indicator =====
    function handleTyping() {
        if (!state.isTyping) {
            state.isTyping = true;
            sendMessage({
                type: 'typing',
                room: state.currentRoom,
                typing: true
            });
        }

        // Clear existing timeout
        if (state.typingTimeout) {
            clearTimeout(state.typingTimeout);
        }

        // Stop typing after 2 seconds of inactivity
        state.typingTimeout = setTimeout(() => {
            state.isTyping = false;
            sendMessage({
                type: 'typing',
                room: state.currentRoom,
                typing: false
            });
        }, 2000);
    }

    // ===== Event Handlers =====
    function initEventHandlers() {
        // Theme toggle
        elements.themeToggle.addEventListener('click', toggleTheme);

        // Visibility change - send ping when tab becomes visible
        // This helps prevent disconnects from browser throttling background tabs
        document.addEventListener('visibilitychange', () => {
            if (document.visibilityState === 'visible') {
                // Tab became visible - send immediate ping to keep connection alive
                if (state.ws && state.ws.readyState === WebSocket.OPEN) {
                    sendMessage({ type: 'ping' });
                }
            }
        });

        // Login form
        elements.loginForm.addEventListener('submit', (e) => {
            e.preventDefault();
            const username = elements.usernameInput.value.trim();
            if (username) {
                state.username = username;
                elements.loginScreen.classList.add('hidden');
                elements.chatScreen.classList.remove('hidden');
                connectWebSocket();
                connectSSE();
            }
        });

        // Message form
        elements.messageForm.addEventListener('submit', (e) => {
            e.preventDefault();
            const text = elements.messageInput.value.trim();
            if (text) {
                sendMessage({
                    type: 'message',
                    room: state.currentRoom,
                    text: text
                });
                elements.messageInput.value = '';

                // Stop typing indicator
                if (state.typingTimeout) {
                    clearTimeout(state.typingTimeout);
                }
                state.isTyping = false;
            }
        });

        // Typing detection
        elements.messageInput.addEventListener('input', handleTyping);

        // Leave room button
        elements.leaveRoomBtn.addEventListener('click', () => {
            sendMessage({ type: 'leave', room: state.currentRoom });
        });

        // Create room button
        elements.createRoomBtn.addEventListener('click', () => {
            elements.createRoomModal.classList.remove('hidden');
            elements.roomNameInput.focus();
        });

        // Cancel create room
        elements.cancelCreateRoom.addEventListener('click', () => {
            elements.createRoomModal.classList.add('hidden');
            elements.roomNameInput.value = '';
        });

        // Modal backdrop click
        elements.createRoomModal.querySelector('.modal-backdrop').addEventListener('click', () => {
            elements.createRoomModal.classList.add('hidden');
            elements.roomNameInput.value = '';
        });

        // Create room form
        elements.createRoomForm.addEventListener('submit', (e) => {
            e.preventDefault();
            const roomName = elements.roomNameInput.value.trim().toLowerCase();
            if (roomName) {
                sendMessage({ type: 'join', room: roomName });
                elements.createRoomModal.classList.add('hidden');
                elements.roomNameInput.value = '';
            }
        });

        // Keyboard shortcuts
        document.addEventListener('keydown', (e) => {
            // Escape to close modal
            if (e.key === 'Escape') {
                elements.createRoomModal.classList.add('hidden');
            }

            // Focus message input when typing
            if (e.target === document.body && !e.ctrlKey && !e.metaKey && !e.altKey) {
                if (e.key.length === 1 && !elements.loginScreen.classList.contains('hidden') === false) {
                    elements.messageInput.focus();
                }
            }
        });
    }

    // ===== Initialization =====
    function init() {
        initTheme();
        initEventHandlers();

        // Focus username input
        elements.usernameInput.focus();

        // Auto-fill username from localStorage if available
        const savedUsername = localStorage.getItem('chat-username');
        if (savedUsername) {
            elements.usernameInput.value = savedUsername;
        }

        // Save username on login
        elements.loginForm.addEventListener('submit', () => {
            localStorage.setItem('chat-username', elements.usernameInput.value.trim());
        });
    }

    // Start the app
    document.addEventListener('DOMContentLoaded', init);
})();