import { api } from "./api.js";
import { t } from "./i18n.js";
import { state } from "./state.js";
import { $, toast } from "./dom.js";
import { renderAdminLinker, renderAdminVoteStatus } from "./admin-ui.js";
import { getUiRuntime } from "./ui-runtime.js";
import { linkedPeerIds, linkRootId, sortByName } from "./ui-utils.js";
import { buildCard } from "./suggestions-ui.js";
export function renderVotes() {
const list = $("vote-list");
if (!list) return;
const prevScroll = list.scrollTop;
list.innerHTML = "";
const votesMap = Object.fromEntries(
state.myVotes.map((v) => [v.suggestionId, v.score]),
);
sortByName(state.allSuggestions).forEach((s) => {
const canEdit = !!state.me?.isAdmin || s.isOwner;
const lockTitle = state.phase !== "Suggest" && !state.me?.isAdmin;
const li = buildCard(s, {
showAuthor: true,
allowEdit: canEdit,
allowDelete: !!state.me?.isAdmin,
lockTitle,
});
const hasVote = Object.prototype.hasOwnProperty.call(votesMap, s.id);
const current = hasVote ? votesMap[s.id] : 5;
const displayScore = hasVote ? current : "—";
const displayEmoji = hasVote ? scoreToEmoji(current) : "⚠️";
const linkedIds = linkedPeerIds(s);
const rootId = linkRootId(s);
const footer = document.createElement("div");
footer.className = "vote-controls";
footer.innerHTML = `
${state.votesFinal ? t("vote.missingFinalWarn") : t("vote.missingWarn")}
${displayScore}
${displayEmoji}
`;
li.querySelector(".card-body").appendChild(footer);
list.appendChild(li);
});
updatePhaseNav();
updateMissingBadgeFromDom();
list.scrollTop = prevScroll;
list.querySelectorAll("input[type=range]").forEach((input) => {
input.addEventListener("input", (e) => {
if (state.votesFinal) return;
const val = Number(e.target.value);
const id = e.target.dataset.id;
$("score-" + id).textContent = val;
const emojiEl = $("emoji-" + id);
if (emojiEl) emojiEl.textContent = scoreToEmoji(val);
const warn = $("warn-" + id);
if (warn) warn.classList.remove("hidden");
e.target.dataset.pending = "1";
syncLinkedSliders(e.target, val);
updateMissingBadgeFromDom();
});
input.addEventListener("change", async (e) => {
if (state.votesFinal) return;
const suggestionId = Number(e.target.dataset.id);
const score = Number(e.target.value);
const prevScore = votesMap[suggestionId];
const linkedIds = (e.target.dataset.linked || "")
.split(",")
.filter(Boolean)
.map((x) => Number(x));
const resetUi = () => {
const label = $("score-" + suggestionId);
const emoji = $("emoji-" + suggestionId);
const warn = $("warn-" + suggestionId);
const fallbackValue = prevScore ?? 5;
const fallbackDisplay = prevScore ?? "—";
const fallbackEmoji = prevScore != null ? scoreToEmoji(prevScore) : "⚠️";
e.target.value = fallbackValue;
if (label) label.textContent = fallbackDisplay;
if (emoji) emoji.textContent = fallbackEmoji;
if (warn) warn.classList.remove("hidden");
};
try {
await api.vote(suggestionId, score);
toast(t("vote.saved"));
delete e.target.dataset.pending;
const warn = $("warn-" + suggestionId);
if (warn) warn.classList.add("hidden");
linkedIds.forEach((id) => {
const peerWarn = $("warn-" + id);
if (peerWarn) peerWarn.classList.add("hidden");
const peerSlider = document.querySelector(`input[type=range][data-id="${id}"]`);
if (peerSlider) delete peerSlider.dataset.pending;
});
await getUiRuntime().loadVoteData();
updateMissingBadgeFromDom();
} catch (err) {
delete e.target.dataset.pending;
resetUi();
toast(err.message, true);
}
});
});
}
export function syncVoteScores() {
const votesMap = Object.fromEntries(
state.myVotes.map((v) => [v.suggestionId, v.score]),
);
Object.entries(votesMap).forEach(([id, score]) => {
const slider = document.querySelector(
`input[type=range][data-id="${id}"]`,
);
const scoreLabel = $("score-" + id);
const emoji = $("emoji-" + id);
const warn = $("warn-" + id);
if (slider && score != null) {
slider.value = score;
if (scoreLabel) scoreLabel.textContent = score;
if (emoji) emoji.textContent = scoreToEmoji(score);
if (warn) warn.classList.add("hidden");
delete slider.dataset.pending;
}
});
document
.querySelectorAll("input[type=range][data-id]")
.forEach((slider) => {
const id = slider.dataset.id;
if (Object.prototype.hasOwnProperty.call(votesMap, Number(id)))
return;
const scoreLabel = $("score-" + id);
const emoji = $("emoji-" + id);
const warn = $("warn-" + id);
if (scoreLabel) scoreLabel.textContent = "—";
if (emoji) emoji.textContent = neutralEmoji();
if (warn) warn.classList.remove("hidden");
});
}
export function scoreToEmoji(score) {
if (score == null || Number.isNaN(score)) return neutralEmoji();
if (score < 1) return "😡";
if (score <= 3) return "😠";
if (score <= 6) return "😐";
if (score <= 8) return "🙂";
if (score <= 9) return "😃";
return "🤩";
}
export function neutralEmoji() {
return "😐";
}
function missingVotesCount() {
const total = state.allSuggestions?.length ?? 0;
const votedIds = new Set(state.myVotes?.map((v) => v.suggestionId));
const missing = total - votedIds.size;
return missing < 0 ? 0 : missing;
}
function updateMissingBadgeFromDom() {
const badge = $("vote-missing");
if (!badge) return;
if (state.votesFinal || state.phase !== "Vote") {
badge.classList.add("hidden");
return;
}
const missing = missingVotesCount();
badge.classList.toggle("hidden", missing === 0);
}
function syncLinkedSliders(sourceEl, value) {
const linkedAttr = sourceEl?.dataset?.linked;
if (!linkedAttr) return;
const ids = linkedAttr.split(",").filter(Boolean);
ids.forEach((id) => {
const slider = document.querySelector(`input[type=range][data-id="${id}"]`);
if (!slider || slider === sourceEl) return;
slider.value = value;
const scoreLabel = $("score-" + id);
if (scoreLabel) scoreLabel.textContent = value;
const emojiEl = $("emoji-" + id);
if (emojiEl) emojiEl.textContent = scoreToEmoji(Number(value));
const warn = $("warn-" + id);
if (warn) warn.classList.remove("hidden");
slider.dataset.pending = "1";
});
}
export function updatePhaseNav() {
const isAdmin = !!state.me?.isAdmin;
const phase = state.phase;
const showNav = (id, visible) => {
const el = $(id);
if (el) el.classList.toggle("hidden", !visible);
};
showNav("nav-suggest", phase === "Suggest");
showNav("nav-vote", phase === "Vote");
const jokerBtn = $("open-joker-modal");
if (jokerBtn) {
const showJoker = phase === "Vote" && state.hasJoker;
jokerBtn.classList.toggle("hidden", !showJoker);
jokerBtn.disabled = !showJoker;
}
const finalizeBtn = $("finalize-votes");
if (finalizeBtn) {
finalizeBtn.textContent = state.votesFinal ? t("vote.unfinalize") : t("vote.finalize");
}
const voteMissingBadge = $("vote-missing");
if (voteMissingBadge) {
const missing = missingVotesCount();
const showMissing = !state.votesFinal && missing > 0;
voteMissingBadge.classList.toggle("hidden", !showMissing);
voteMissingBadge.textContent = t("vote.missingFooter");
}
const waitAdmin = $("vote-wait-admin");
if (waitAdmin) {
const show = state.votesFinal && phase === "Vote" && !state.resultsOpen;
waitAdmin.classList.toggle("hidden", !show);
waitAdmin.textContent = t("vote.waitAdmin");
}
const voteStatusText = $("vote-status-text");
if (voteStatusText) {
voteStatusText.textContent = state.votesFinal ? t("nav.voteFinalized") : t("nav.voteHint");
}
renderAdminVoteStatus();
renderAdminLinker();
updateMissingBadgeFromDom();
const backButtons = ["nav-vote-prev"];
backButtons.forEach((id) => {
const btn = $(id);
if (btn) btn.classList.toggle("hidden", !isAdmin);
});
const voteNext = $("nav-vote-next");
if (voteNext) {
const locked = !state.resultsOpen && !isAdmin;
voteNext.disabled = locked;
voteNext.textContent = locked ? t("nav.waitingForResults") : t("nav.next");
}
const adminResultsToggle = $("results-open");
if (adminResultsToggle) {
adminResultsToggle.checked = !!state.resultsOpen;
}
}