const USERNAME_KEY = 'scrumPoker.username';

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 roomStatus = document.getElementById('room-status');

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 adminToken = params.get('adminToken') || '';
let eventSource = null;
let latestLinks = { participantLink: '', adminLink: '' };

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


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);
    if (participantID) {
        next.searchParams.set('participantId', participantID);
    } else {
        next.searchParams.delete('participantId');
    }

    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');
}

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

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

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

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

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

        const name = document.createElement('span');
        let label = participant.username;
        if (participant.id === participantID) {
            label += ' (You)';
        }
        if (participant.isAdmin) {
            label += ' [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.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) {
    const self = participants.find((participant) => participant.id === participantID);
    const canVote = self && self.role === 'participant';
    const selfVote = self ? self.voteValue : '';

    votingBoard.innerHTML = '';

    cards.forEach((value) => {
        const card = document.createElement('button');
        card.type = 'button';
        card.className = 'vote-card';
        card.textContent = 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');
            castVote(value);
        });

        votingBoard.appendChild(card);
    });
}

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);
    renderCards(state.cards, state.participants, state.revealed);
    renderSummary(state);

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

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

    const votedCount = state.participants.filter((p) => p.role === 'participant' && p.hasVoted).length;
    const totalParticipants = state.participants.filter((p) => p.role === 'participant').length;
    roomStatus.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)}`);
    eventSource.addEventListener('state', (event) => {
        try {
            const payload = JSON.parse(event.data);
            renderState(payload);
            activateRoomView();
        } catch (_err) {
            roomStatus.textContent = 'Failed to parse room update.';
        }
    });

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

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

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

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

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

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

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

revealBtn.addEventListener('click', () => adminAction('reveal'));
resetBtn.addEventListener('click', () => adminAction('reset'));
shareAdminToggle.addEventListener('change', updateShareLink);

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) {
            participantID = '';
            updateURL();
        }
        setJoinError(err.message);
    }
});

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

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