const USERNAME_KEY = 'scrumPoker.username';
const ROOM_SESSION_KEY_PREFIX = 'scrumPoker.roomSession.';

const roomID = document.body.dataset.roomId;
const params = new URLSearchParams(window.location.search);

const roomSkeleton = document.getElementById('room-skeleton');
const roomGrid = document.getElementById('room-grid');
const roomTitle = document.getElementById('room-title');
const revealModeLabel = document.getElementById('reveal-mode-label');
const roundStateLabel = document.getElementById('round-state-label');
const votingBoard = document.getElementById('voting-board');
const summaryBody = document.getElementById('summary-body');
const summaryAverage = document.getElementById('summary-average');
const summaryRecommended = document.getElementById('summary-recommended');
const participantList = document.getElementById('participant-list');
const adminControls = document.getElementById('admin-controls');
const revealBtn = document.getElementById('reveal-btn');
const resetBtn = document.getElementById('reset-btn');
const shareLinkInput = document.getElementById('share-link');
const shareAdminToggle = document.getElementById('share-admin-toggle');
const votesCounter = document.getElementById('votes-counter');
const roomMessage = document.getElementById('room-message');
const changeNameBtn = document.getElementById('change-name-btn');
const terminalLogOutput = document.getElementById('terminal-log-output');
const TERMINAL_WINDOW_ID = 'terminal-tool-window';

const joinPanel = document.getElementById('join-panel');
const joinForm = document.getElementById('join-form');
const joinUsernameInput = document.getElementById('join-username');
const joinRoleInput = document.getElementById('join-role');
const joinPasswordInput = document.getElementById('join-password');
const joinAdminTokenInput = document.getElementById('join-admin-token');
const joinError = document.getElementById('join-error');
let participantID = params.get('participantId') || '';
let sessionToken = params.get('sessionToken') || '';
let adminToken = params.get('adminToken') || '';
const prefillUsername = params.get('username') || '';
let eventSource = null;
let latestLinks = { participantLink: '', adminLink: '' };
let latestAdminLogs = [];
let latestRole = joinRoleInput.value || 'participant';

const savedUsername = localStorage.getItem(USERNAME_KEY) || '';
joinUsernameInput.value = prefillUsername || savedUsername;
joinAdminTokenInput.value = adminToken;

function roomSessionStorageKey() {
    return `${ROOM_SESSION_KEY_PREFIX}${roomID}`;
}

function persistRoomSession() {
    if (!participantID || !sessionToken) {
        localStorage.removeItem(roomSessionStorageKey());
        return;
    }

    localStorage.setItem(roomSessionStorageKey(), JSON.stringify({
        participantId: participantID,
        sessionToken,
    }));
}

function loadRoomSessionFromStorage() {
    try {
        const raw = localStorage.getItem(roomSessionStorageKey());
        if (!raw) {
            return;
        }
        const parsed = JSON.parse(raw);
        if (!participantID && typeof parsed.participantId === 'string') {
            participantID = parsed.participantId;
        }
        if (!sessionToken && typeof parsed.sessionToken === 'string') {
            sessionToken = parsed.sessionToken;
        }
    } catch (_err) {
        localStorage.removeItem(roomSessionStorageKey());
    }
}

if (!participantID || !sessionToken) {
    loadRoomSessionFromStorage();
}
persistRoomSession();

if (!window.CardUI || typeof window.CardUI.appendFace !== 'function') {
    throw new Error('CardUI is not loaded. Ensure /static/js/cards.js is included before room.js.');
}


function setJoinError(message) {
    if (!message) {
        joinError.classList.add('hidden');
        joinError.textContent = '';
        return;
    }

    joinError.classList.remove('hidden');
    joinError.textContent = message;
}

function updateURL() {
    const next = new URL(window.location.href);
    next.searchParams.delete('username');
    next.searchParams.delete('participantId');
    next.searchParams.delete('sessionToken');

    if (adminToken) {
        next.searchParams.set('adminToken', adminToken);
    } else {
        next.searchParams.delete('adminToken');
    }

    window.history.replaceState({}, '', next.toString());
}

function activateRoomView() {
    document.body.classList.remove('prejoin');
    roomSkeleton.classList.add('hidden');
    roomGrid.classList.remove('hidden');
    joinPanel.classList.add('hidden');
}

function setRoomMessage(message) {
    roomMessage.textContent = message;
}

async function joinRoom({ username, role, password, participantIdOverride }) {
    const activeParticipantID = participantIdOverride || participantID;
    const rejoinParticipantID = activeParticipantID && sessionToken ? activeParticipantID : '';

    const response = await fetch(`/api/rooms/${encodeURIComponent(roomID)}/join`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            participantId: rejoinParticipantID,
            sessionToken,
            username,
            role,
            password,
            adminToken,
        }),
    });

    const data = await response.json();
    if (!response.ok) {
        throw new Error(data.error || 'Unable to join room.');
    }

    participantID = data.participantId;
    sessionToken = data.sessionToken;
    localStorage.setItem(USERNAME_KEY, data.username);
    persistRoomSession();
    updateURL();
    setJoinError('');
    return data;
}

function renderParticipants(participants, isRevealed) {
    participantList.innerHTML = '';

    const visibleParticipants = participants.filter((participant) => participant.connected);
    visibleParticipants.forEach((participant) => {
        const item = document.createElement('li');
        item.className = 'participant-item';

        const name = document.createElement('span');
        name.className = 'participant-name';
        let label = participant.username;
        if (participant.id === participantID) {
            label += ' (You)';
        }
        if (participant.isAdmin) {
            label += ' [Admin]';
            name.classList.add('is-admin');
        }
        name.textContent = label;

        const status = document.createElement('span');
        if (participant.role === 'viewer') {
            status.textContent = 'Viewer';
        } else if (!participant.hasVoted) {
            status.textContent = 'Voting...';
        } else if (isRevealed) {
            status.textContent = participant.voteValue || '-';
        } else {
            status.textContent = participant.voteValue ? `Voted (${participant.voteValue})` : 'Voted';
        }

        item.appendChild(name);
        item.appendChild(status);
        participantList.appendChild(item);
    });
}

function parseNumericVote(value) {
    if (!/^-?\d+(\.\d+)?$/.test(value)) {
        return null;
    }
    return Number(value);
}

function calculateSummary(state) {
    const rows = new Map();
    const numericVotes = [];

    state.participants.forEach((participant) => {
        if (!participant.connected) {
            return;
        }

        if (participant.role !== 'participant' || !participant.hasVoted || !participant.voteValue) {
            return;
        }

        if (!rows.has(participant.voteValue)) {
            rows.set(participant.voteValue, []);
        }
        rows.get(participant.voteValue).push(participant.username);

        const numeric = parseNumericVote(participant.voteValue);
        if (numeric !== null) {
            numericVotes.push(numeric);
        }
    });

    let average = null;
    if (numericVotes.length > 0) {
        average = numericVotes.reduce((acc, value) => acc + value, 0) / numericVotes.length;
    }

    const deckNumeric = state.cards
        .map(parseNumericVote)
        .filter((value) => value !== null)
        .sort((a, b) => a - b);

    let recommended = null;
    if (average !== null && deckNumeric.length > 0) {
        recommended = deckNumeric.find((value) => value >= average) ?? deckNumeric[deckNumeric.length - 1];
    }

    return { rows, average, recommended };
}

function renderSummary(state) {
    if (!state.revealed) {
        summaryBody.innerHTML = '<tr><td colspan="2">Results hidden until reveal.</td></tr>';
        summaryAverage.textContent = 'Average: -';
        summaryRecommended.textContent = 'Recommended: -';
        return;
    }

    const { rows, average, recommended } = calculateSummary(state);
    summaryBody.innerHTML = '';

    if (rows.size === 0) {
        summaryBody.innerHTML = '<tr><td colspan="2">No votes submitted.</td></tr>';
    } else {
        state.cards.forEach((cardValue) => {
            const users = rows.get(cardValue);
            if (!users || users.length === 0) {
                return;
            }

            const row = document.createElement('tr');
            const left = document.createElement('td');
            const right = document.createElement('td');
            left.textContent = cardValue;
            right.textContent = users.join(', ');
            row.appendChild(left);
            row.appendChild(right);
            summaryBody.appendChild(row);
        });
    }

    summaryAverage.textContent = average === null ? 'Average: -' : `Average: ${average.toFixed(2)}`;
    summaryRecommended.textContent = recommended === null ? 'Recommended: -' : `Recommended: ${recommended}`;
}

function renderCards(cards, participants, isRevealed, allowVoteChange) {
    const self = participants.find((participant) => participant.id === participantID && participant.connected);
    const canVote = self && self.role === 'participant' && (allowVoteChange || !self.hasVoted);
    const selfVote = self ? self.voteValue : '';

    votingBoard.innerHTML = '';

    cards.forEach((value) => {
        const card = document.createElement('button');
        card.type = 'button';
        card.className = 'vote-card';
        card.setAttribute('aria-label', `Vote ${value}`);
        window.CardUI.appendFace(card, value);

        if (selfVote === value && !isRevealed) {
            card.classList.add('is-selected');
        }

        card.disabled = !canVote;
        card.addEventListener('click', () => {
            card.classList.remove('impact');
            void card.offsetWidth;
            card.classList.add('impact');
            spawnVoteParticles(card);
            castVote(value);
        });

        votingBoard.appendChild(card);
    });
}

function spawnVoteParticles(card) {
    const particleCount = 12;
    for (let i = 0; i < particleCount; i += 1) {
        const particle = document.createElement('span');
        particle.className = 'vote-particle';

        const angle = (Math.PI * 2 * i) / particleCount + (Math.random() * 0.4 - 0.2);
        const distance = 18 + Math.random() * 26;
        const dx = Math.cos(angle) * distance;
        const dy = Math.sin(angle) * distance;

        particle.style.setProperty('--tx', `${dx.toFixed(2)}px`);
        particle.style.setProperty('--ty', `${dy.toFixed(2)}px`);
        particle.style.setProperty('--hue', `${Math.floor(Math.random() * 80) + 90}`);

        particle.addEventListener('animationend', () => particle.remove(), { once: true });
        card.appendChild(particle);
    }
}

function formatLogTime(raw) {
    const parsed = new Date(raw);
    if (Number.isNaN(parsed.getTime())) {
        return raw || '-';
    }
    return parsed.toLocaleString();
}

function renderTerminalLogs(logs) {
    terminalLogOutput.innerHTML = '';
    if (!Array.isArray(logs) || logs.length === 0) {
        const emptyLine = document.createElement('div');
        emptyLine.className = 'terminal-log-line';
        emptyLine.textContent = '[system] no activity recorded yet';
        terminalLogOutput.appendChild(emptyLine);
        return;
    }

    logs.forEach((item) => {
        const line = document.createElement('div');
        line.className = 'terminal-log-line';
        line.textContent = `[${formatLogTime(item.at)}] ${item.message}`;
        terminalLogOutput.appendChild(line);
    });
    terminalLogOutput.scrollTop = terminalLogOutput.scrollHeight;
}

function renderState(state) {
    roomTitle.textContent = `${state.roomName} (${state.roomId})`;
    revealModeLabel.textContent = `Reveal mode: ${state.revealMode}`;
    roundStateLabel.textContent = state.revealed ? 'Cards revealed' : 'Cards hidden';

    renderParticipants(state.participants, state.revealed);
    const allowVoteChange = state.allowVoteChange !== false;
    renderCards(state.cards, state.participants, state.revealed, allowVoteChange);
    renderSummary(state);

    const self = state.participants.find((participant) => participant.id === participantID && participant.connected);
    if (self && self.role) {
        latestRole = self.role;
    }

    latestLinks = state.links || { participantLink: '', adminLink: '' };
    updateShareLink();

    if (typeof window.setUIWindowAccess === 'function') {
        window.setUIWindowAccess({ admin: state.viewerIsAdmin });
    }

    if (state.viewerIsAdmin) {
        adminControls.classList.remove('hidden');
    } else {
        adminControls.classList.add('hidden');
    }

    latestAdminLogs = Array.isArray(state.adminLogs) ? state.adminLogs : [];
    if (state.viewerIsAdmin && typeof window.isUIWindowOpen === 'function' && window.isUIWindowOpen(TERMINAL_WINDOW_ID)) {
        renderTerminalLogs(latestAdminLogs);
    }

    const votedCount = state.participants.filter((p) => p.connected && p.role === 'participant' && p.hasVoted).length;
    const totalParticipants = state.participants.filter((p) => p.connected && p.role === 'participant').length;
    votesCounter.textContent = `Votes: ${votedCount}/${totalParticipants}`;
}

function updateShareLink() {
    const useAdmin = shareAdminToggle.checked && latestLinks.adminLink;
    const raw = useAdmin ? latestLinks.adminLink : latestLinks.participantLink;
    shareLinkInput.value = raw ? `${window.location.origin}${raw}` : '';
}

function connectSSE() {
    if (eventSource) {
        eventSource.close();
    }

    eventSource = new EventSource(`/api/rooms/${encodeURIComponent(roomID)}/events?participantId=${encodeURIComponent(participantID)}&sessionToken=${encodeURIComponent(sessionToken)}`);
    eventSource.addEventListener('state', (event) => {
        try {
            const payload = JSON.parse(event.data);
            renderState(payload);
            activateRoomView();
            setRoomMessage('Connected.');
        } catch (_err) {
            setRoomMessage('Failed to parse room update.');
        }
    });

    eventSource.onerror = () => {
        setRoomMessage('Connection interrupted. Retrying...');
    };
}

async function castVote(card) {
    if (!participantID || !sessionToken) {
        return;
    }

    try {
        const response = await fetch(`/api/rooms/${encodeURIComponent(roomID)}/vote`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ participantId: participantID, sessionToken, card }),
        });

        if (!response.ok) {
            const data = await response.json();
            setRoomMessage(data.error || 'Vote rejected.');
        }
    } catch (_err) {
        setRoomMessage('Network error while casting vote.');
    }
}

async function adminAction(action) {
    if (!participantID || !sessionToken) {
        return;
    }

    try {
        const response = await fetch(`/api/rooms/${encodeURIComponent(roomID)}/${action}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ participantId: participantID, sessionToken }),
        });

        if (!response.ok) {
            const data = await response.json();
            setRoomMessage(data.error || `Unable to ${action}.`);
        }
    } catch (_err) {
        setRoomMessage('Network error while sending admin action.');
    }
}

async function changeName() {
    if (!participantID || !sessionToken) {
        return;
    }

    const current = joinUsernameInput.value.trim() || localStorage.getItem(USERNAME_KEY) || '';
    const next = window.prompt('Enter your new name:', current);
    if (next === null) {
        return;
    }

    const username = next.trim();
    if (!username) {
        setRoomMessage('Name cannot be empty.');
        return;
    }

    if (username === current) {
        return;
    }

    try {
        const result = await joinRoom({
            username,
            role: latestRole || joinRoleInput.value || 'participant',
            password: joinPasswordInput.value,
            participantIdOverride: participantID,
        });
        joinUsernameInput.value = result.username;
        setRoomMessage('Name updated.');
    } catch (err) {
        setRoomMessage(err.message || 'Unable to change name.');
    }
}

revealBtn.addEventListener('click', () => adminAction('reveal'));
resetBtn.addEventListener('click', () => adminAction('reset'));
shareAdminToggle.addEventListener('change', updateShareLink);
changeNameBtn.addEventListener('click', () => {
    void changeName();
});
document.addEventListener('click', (event) => {
    const openBtn = event.target.closest('[data-role="open-window"]');
    if (!openBtn || openBtn.dataset.target !== TERMINAL_WINDOW_ID) {
        return;
    }

    requestAnimationFrame(() => {
        if (typeof window.isUIWindowOpen === 'function' && window.isUIWindowOpen(TERMINAL_WINDOW_ID)) {
            renderTerminalLogs(latestAdminLogs);
        }
    });
});

joinForm.addEventListener('submit', async (event) => {
    event.preventDefault();

    const username = joinUsernameInput.value.trim();
    if (!username) {
        setJoinError('Username is required.');
        return;
    }

    adminToken = joinAdminTokenInput.value.trim();

    try {
        await joinRoom({
            username,
            role: joinRoleInput.value,
            password: joinPasswordInput.value,
            participantIdOverride: participantID,
        });
        connectSSE();
    } catch (err) {
        if (participantID || sessionToken) {
            participantID = '';
            sessionToken = '';
            persistRoomSession();
            updateURL();
        }
        setJoinError(err.message);
    }
});

async function tryAutoJoinExistingParticipant() {
    if (!participantID || !sessionToken) {
        return;
    }

    const username = joinUsernameInput.value.trim() || prefillUsername || 'host';

    try {
        await joinRoom({
            username,
            role: 'participant',
            password: joinPasswordInput.value,
            participantIdOverride: participantID,
        });
        connectSSE();
    } catch (_err) {
        participantID = '';
        sessionToken = '';
        persistRoomSession();
        updateURL();
    }
}

window.addEventListener('pagehide', () => {
    if (!participantID || !sessionToken) {
        return;
    }

    const payload = JSON.stringify({ participantId: participantID, sessionToken });
    navigator.sendBeacon(`/api/rooms/${encodeURIComponent(roomID)}/leave`, new Blob([payload], { type: 'application/json' }));
});

void tryAutoJoinExistingParticipant();
