const PAGE_SIZE = 12;
const MAX_FILE_BYTES = 5 * 1024 * 1024;
const MAX_IMAGE_PIXELS = 20_000_000;
const VIEW_DWELL_MS = 1000;
const VIEW_THRESHOLD = 0.9;
const VIEW_DEDUPE_MS = 24 * 60 * 60 * 1000;
const VIEW_DEDUPE_PREFIX = 'meme-viewed:';
const NEXT_PAGE_LOADER_MIN_MS = 300;
const SAFE_TYPES = new Set(['image/png', 'image/jpeg']);

const grid = document.querySelector('#meme-grid');
const feedLoader = document.querySelector('#feed-loader');
const feedLoaderLabel = document.querySelector('#feed-loader-label');
const uploadModal = document.querySelector('#upload-modal');
const uploadForm = document.querySelector('#upload-form');
const fileInput = document.querySelector('#meme-input');
const fileLabel = document.querySelector('#file-label');
const formStatus = document.querySelector('#form-status');
const addMemeButton = document.querySelector('#add-meme-button');
const lightbox = document.querySelector('#lightbox');
const lightboxImage = document.querySelector('#lightbox-image');
const lightboxCopy = document.querySelector('#lightbox-copy');
const lightboxDownload = document.querySelector('#lightbox-download');
const lightboxShare = document.querySelector('#lightbox-share');
const lightboxId = document.querySelector('#lightbox-id');
const lightboxViewCount = document.querySelector('#lightbox-view-count');
const lightboxDownloadCount = document.querySelector('#lightbox-download-count');
const petModal = document.querySelector('#pet-modal');
const loreModal = document.querySelector('#lore-modal');
const loreText = document.querySelector('#lore-text');
const loreCopy = document.querySelector('#lore-copy');
const loreShare = document.querySelector('#lore-share');
const shareModal = document.querySelector('#share-modal');
const shareTitle = document.querySelector('#share-title');
const shareLinkLabel = document.querySelector('#share-link-label');
const shareLink = document.querySelector('#share-link');
const shareStatus = document.querySelector('#share-status');
const copyShareLinkButton = document.querySelector('#copy-share-link');
const scrollIndicator = document.querySelector('#scroll-indicator');
const actionStatus = document.querySelector('#action-status');

let currentPage = 0;
let totalPages = 1;
let isLoading = false;
let latestValidationRun = 0;
let activeShareTrigger = null;
let shareStatusTimer = null;
let actionStatusTimer = null;
let loreContent = '';
const viewedThisSession = new Set();
const pendingViewTimers = new Map();
const copiedFeedbackTimers = new WeakMap();

document.querySelector('#open-upload').addEventListener('click', () => {
  formStatus.textContent = '';
  uploadModal.showModal();
});
document.querySelector('#close-upload').addEventListener('click', closeUpload);
document.querySelector('#cancel-upload').addEventListener('click', closeUpload);
document.querySelector('#close-lightbox').addEventListener('click', () => lightbox.close());
document.querySelector('#close-share').addEventListener('click', () => shareModal.close());
document.querySelector('#open-pet').addEventListener('click', () => petModal.showModal());
document.querySelector('#close-pet').addEventListener('click', () => petModal.close());
document.querySelector('#open-lore').addEventListener('click', openLoreDialog);
document.querySelector('#close-lore').addEventListener('click', () => loreModal.close());
copyShareLinkButton.addEventListener('click', copyActiveShareLink);
lightboxShare.addEventListener('click', async () => {
  await openMemeShareDialog(lightboxShare.dataset.shareId, lightboxShare);
});
lightboxCopy.addEventListener('click', () => copyImage(lightboxCopy.dataset.imageUrl, lightboxCopy));
loreCopy.addEventListener('click', copyLore);
loreShare.addEventListener('click', () => openShareDialog({
  path: '/lore',
  title: 'SHARE_LORE',
  label: 'LORE_LINK',
  triggerButton: loreShare
}));

fileInput.addEventListener('change', async () => {
  const validationRun = ++latestValidationRun;
  const file = fileInput.files?.[0];
  fileLabel.textContent = file ? file.name : 'SELECT PNG / JPEG';
  formStatus.textContent = file ? 'INSPECTING_IMAGE...' : '';
  const validationError = await validateClientFile(file);
  if (validationRun === latestValidationRun) {
    formStatus.textContent = validationError || '';
  }
});

uploadForm.addEventListener('submit', async (event) => {
  event.preventDefault();
  const file = fileInput.files?.[0];
  const validationError = await validateClientFile(file);
  if (validationError) {
    formStatus.textContent = validationError;
    return;
  }

  addMemeButton.disabled = true;
  formStatus.textContent = 'UPLOADING...';

  try {
    const formData = new FormData();
    formData.append('meme', file);
    const response = await fetch('/api/memes', { method: 'POST', body: formData });
    const payload = await response.json();
    if (!response.ok) throw new Error(payload.error || 'Upload failed.');
    if (payload.meme?.status === 'approved') {
      closeUpload();
      await loadMemes(1, { reset: true });
    } else {
      uploadForm.reset();
      fileLabel.textContent = 'SELECT PNG / JPEG';
      formStatus.textContent = payload.message || 'Upload queued for admin review.';
    }
  } catch (error) {
    formStatus.textContent = error.message.toUpperCase();
  } finally {
    addMemeButton.disabled = false;
  }
});

grid.addEventListener('click', async (event) => {
  const copyButton = event.target.closest('[data-copy-image-url]');
  if (copyButton) {
    await copyImage(copyButton.dataset.copyImageUrl, copyButton);
    return;
  }

  const shareButton = event.target.closest('[data-share-id]');
  if (shareButton) {
    await openMemeShareDialog(shareButton.dataset.shareId, shareButton);
    return;
  }

  const viewButton = event.target.closest('[data-view-id]');
  if (!viewButton) return;
  const card = viewButton.closest('.meme-card');
  await openLightbox({
    id: viewButton.dataset.viewId,
    url: viewButton.dataset.viewUrl,
    downloadUrl: viewButton.dataset.downloadUrl,
    alt: card.querySelector('img').alt,
    viewCount: Number.parseInt(viewButton.dataset.viewCount || '0', 10),
    downloadCount: Number.parseInt(viewButton.dataset.downloadCount || '0', 10)
  });
});

const observer = new IntersectionObserver((entries) => {
  if (entries.some((entry) => entry.isIntersecting)) {
    loadNextPage();
  }
}, { rootMargin: '420px 0px' });

const viewObserver = 'IntersectionObserver' in window
  ? new IntersectionObserver(handleViewIntersections, { threshold: [VIEW_THRESHOLD] })
  : null;

connectLiveCounters();
updateScrollIndicator();
window.addEventListener('scroll', updateScrollIndicator, { passive: true });
window.addEventListener('resize', updateScrollIndicator);
refreshStatus();
window.setInterval(refreshStatus, 5000);
await openSharedContentFromUrl();
await loadMemes(1, { reset: true });
observer.observe(feedLoader);

async function loadNextPage() {
  if (isLoading || currentPage >= totalPages) return;
  await loadMemes(currentPage + 1);
}

async function loadMemes(page, options = {}) {
  if (isLoading) return;
  isLoading = true;
  const loaderStartedAt = performance.now();
  setLoader('FETCHING_NEXT_BLOCK...', true);

  try {
    const response = await fetch(`/api/memes?page=${page}&pageSize=${PAGE_SIZE}`);
    const payload = await response.json();
    if (!response.ok) throw new Error(payload.error || 'Feed error.');

    currentPage = payload.page;
    totalPages = payload.totalPages;
    renderMemes(payload.memes, { append: !options.reset });
    if (page > 1) {
      const remainingLoaderTime = NEXT_PAGE_LOADER_MIN_MS - (performance.now() - loaderStartedAt);
      if (remainingLoaderTime > 0) await delay(remainingLoaderTime);
    }
    setLoader(currentPage < totalPages ? 'SCROLL_FOR_NEXT_BLOCK' : 'STREAM_SYNCHRONIZED', false);
    updateScrollIndicator();
  } catch {
    if (options.reset) grid.innerHTML = `<div class="empty-state">FEED_ERROR</div>`;
    setLoader('FEED_ERROR', false);
  } finally {
    grid.ariaBusy = 'false';
    isLoading = false;
  }
}

function delay(milliseconds) {
  return new Promise((resolve) => window.setTimeout(resolve, milliseconds));
}

function renderMemes(memes, options = {}) {
  if (!options.append && memes.length === 0) {
    unobserveViewedImages();
    grid.innerHTML = `<div class="empty-state">NO_MEMES_IN_STREAM</div>`;
    return;
  }

  const cards = memes.map((meme, index) => {
    const article = document.createElement('article');
    article.className = 'meme-card';
    article.dataset.memeId = meme.id;
    article.innerHTML = `
      <div class="card-head">
        <div class="card-id">
          <span class="node active node-score-${scoreBucket(meme.moderationScore)}" tabindex="0" data-tooltip="MEME_CONSENSUS_SCORE: ${formatCount(meme.moderationScore)} / 100" title="MEME_CONSENSUS_SCORE: ${formatCount(meme.moderationScore)} / 100" aria-label="MEME_CONSENSUS_SCORE: ${formatCount(meme.moderationScore)} out of 100"></span>
          <span class="card-meta">ID: ${shortId(meme.id)}</span>
        </div>
        <span class="card-meta age">${relativeAge(meme.createdAt)}</span>
      </div>
      <div class="image-frame">
        <img src="${meme.url}" alt="Uploaded meme ${shortId(meme.id)}" loading="lazy" data-view-observe-id="${meme.id}">
      </div>
      <div class="card-actions">
        <div class="stats">
          <span class="card-stat" data-counter-id="${meme.id}" data-counter-kind="view"><span class="material-symbols-outlined" aria-hidden="true">visibility</span><span data-count>${formatCount(meme.viewCount)}</span></span>
          <span class="card-stat" data-counter-id="${meme.id}" data-counter-kind="download"><span class="material-symbols-outlined" aria-hidden="true">download</span><span data-count>${formatCount(meme.downloadCount)}</span></span>
        </div>
        <div class="action-group">
          <button class="text-action icon-only-action" type="button" data-copy-image-url="${meme.url}" aria-label="Copy meme image" title="Copy meme image"><span class="material-symbols-outlined" aria-hidden="true">content_copy</span></button>
          <a class="text-action icon-only-action" href="${meme.downloadUrl}" aria-label="Download meme" title="Download meme"><span class="material-symbols-outlined" aria-hidden="true">download</span></a>
          <button class="text-action icon-only-action" type="button" data-share-id="${meme.id}" aria-label="Share meme" title="Share meme"><span class="material-symbols-outlined" aria-hidden="true">share</span></button>
          <button class="text-action icon-only-action" type="button" data-view-id="${meme.id}" data-view-url="${meme.url}" data-download-url="${meme.downloadUrl}" data-view-count="${meme.viewCount}" data-download-count="${meme.downloadCount}" aria-label="View full meme" title="View full meme"><span class="material-symbols-outlined" aria-hidden="true">visibility</span></button>
        </div>
      </div>
    `;
    if (options.append && index === 0) article.tabIndex = -1;
    return article;
  });

  if (options.append) {
    grid.append(...cards);
  } else {
    unobserveViewedImages();
    grid.replaceChildren(...cards);
  }
  observeViewedImages(cards);
}

async function openSharedContentFromUrl() {
  const requestedDialog = new URLSearchParams(window.location.search).get('open');
  if (window.location.pathname === '/' && requestedDialog === 'pet') {
    petModal.showModal();
    window.history.replaceState({}, '', '/');
    return;
  }

  if (window.location.pathname === '/lore' || window.location.pathname === '/lore/') {
    await openLoreDialog();
    return;
  }

  const match = window.location.pathname.match(/^\/meme\/([a-f0-9]{64})\/?$/);
  if (!match) return;

  try {
    const response = await fetch(`/api/memes/${match[1]}`);
    const payload = await response.json();
    if (!response.ok) throw new Error(payload.error || 'Meme not found.');
    await openLightbox({
      ...payload.meme,
      alt: `Shared meme ${shortId(payload.meme.id)}`
    });
  } catch {
    window.location.replace('/');
  }
}

async function openLightbox({ id, url, downloadUrl, alt, viewCount = 0, downloadCount = 0 }) {
  lightboxImage.src = url;
  lightboxImage.alt = alt;
  lightboxCopy.dataset.imageUrl = url;
  lightboxDownload.href = downloadUrl;
  lightboxShare.dataset.shareId = id;
  lightboxViewCount.dataset.counterId = id;
  lightboxViewCount.dataset.counterKind = 'view';
  lightboxViewCount.querySelector('[data-count]').textContent = formatCount(viewCount);
  lightboxDownloadCount.dataset.counterId = id;
  lightboxDownloadCount.dataset.counterKind = 'download';
  lightboxDownloadCount.querySelector('[data-count]').textContent = formatCount(downloadCount);
  lightboxId.textContent = `ID: 0x${id.toUpperCase()}`;
  setActionStatus('', lightboxCopy);
  if (!lightbox.open) lightbox.showModal();
  await recordViewOnce(id);
}

async function openMemeShareDialog(id, triggerButton = null) {
  if (!isMemeId(id)) return;
  return openShareDialog({
    path: `/meme/${id}`,
    title: 'SHARE_MEME',
    label: 'MEME_LINK',
    triggerButton
  });
}

async function openShareDialog({ path, title, label, triggerButton = null }) {
  activeShareTrigger = triggerButton;
  shareTitle.textContent = title;
  shareLinkLabel.textContent = label;
  shareLink.value = new URL(path, window.location.origin).href;
  window.clearTimeout(shareStatusTimer);
  shareStatusTimer = null;
  shareStatus.textContent = '';
  resetCopiedFeedback(copyShareLinkButton);
  if (!shareModal.open) shareModal.showModal();
}

async function copyActiveShareLink() {
  if (!shareLink.value) return;
  copyShareLinkButton.disabled = true;
  try {
    await copyText(shareLink.value);
    shareStatus.textContent = 'LINK_COPIED_TO_CLIPBOARD';
    showCopiedFeedback(copyShareLinkButton, 'Share link copied');
    if (activeShareTrigger) showCopiedFeedback(activeShareTrigger, 'Share link copied');
    window.clearTimeout(shareStatusTimer);
    shareStatusTimer = window.setTimeout(() => {
      shareStatus.textContent = '';
      shareStatusTimer = null;
    }, 2600);
  } catch {
    shareLink.focus();
    shareLink.select();
    shareStatus.textContent = 'COPY_UNAVAILABLE — SELECT_AND_COPY_THE_LINK';
  } finally {
    copyShareLinkButton.disabled = false;
  }
}

async function openLoreDialog() {
  setActionStatus('', loreCopy);
  if (!loreModal.open) loreModal.showModal();
  if (loreContent) return;

  try {
    const response = await fetch('/assets/meme-protocol-lore.txt');
    if (!response.ok) throw new Error('Lore unavailable.');
    loreContent = await response.text();
    loreText.textContent = loreContent;
  } catch {
    loreText.textContent = 'LORE_TRANSMISSION_UNAVAILABLE';
    setActionStatus('LORE_TRANSMISSION_UNAVAILABLE', loreCopy);
  }
}

async function copyLore() {
  if (!loreContent) await openLoreDialog();
  if (!loreContent) return;

  loreCopy.disabled = true;
  try {
    await copyText(loreContent);
    showCopiedFeedback(loreCopy, 'Lore copied');
    setActionStatus('LORE_COPIED_TO_CLIPBOARD', loreCopy);
  } catch {
    setActionStatus('LORE_COPY_UNAVAILABLE — USE_DOWNLOAD', loreCopy);
  } finally {
    loreCopy.disabled = false;
  }
}

async function copyImage(url, button) {
  if (!url || !button) return;
  button.disabled = true;
  setActionStatus('', button);

  try {
    if (!navigator.clipboard?.write || typeof ClipboardItem === 'undefined') {
      throw new Error('Image clipboard unavailable.');
    }
    const png = fetch(url)
      .then((response) => {
        if (!response.ok) throw new Error('Image fetch failed.');
        return response.blob();
      })
      .then(convertImageBlobToPng);
    await navigator.clipboard.write([new ClipboardItem({ 'image/png': png })]);
    showCopiedFeedback(button, 'Meme image copied');
    setActionStatus('MEME_IMAGE_COPIED_TO_CLIPBOARD', button);
  } catch {
    setActionStatus('IMAGE_COPY_UNAVAILABLE — USE_DOWNLOAD', button);
  } finally {
    button.disabled = false;
  }
}

async function convertImageBlobToPng(blob) {
  const canvas = document.createElement('canvas');
  const context = canvas.getContext('2d');
  if (!context) throw new Error('Canvas unavailable.');

  let image;
  let cleanup = () => {};
  if ('createImageBitmap' in window) {
    image = await createImageBitmap(blob);
    cleanup = () => image.close();
  } else {
    const objectUrl = URL.createObjectURL(blob);
    image = await loadImage(objectUrl);
    cleanup = () => URL.revokeObjectURL(objectUrl);
  }

  try {
    canvas.width = image.width;
    canvas.height = image.height;
    context.drawImage(image, 0, 0);
    return await new Promise((resolve, reject) => {
      canvas.toBlob((png) => {
        if (png) resolve(png);
        else reject(new Error('PNG conversion failed.'));
      }, 'image/png');
    });
  } finally {
    cleanup();
  }
}

function loadImage(url) {
  return new Promise((resolve, reject) => {
    const image = new Image();
    image.onload = () => resolve(image);
    image.onerror = () => reject(new Error('Image decode failed.'));
    image.src = url;
  });
}

function showCopiedFeedback(button, copiedLabel = 'Copied') {
  const icon = button.querySelector('.material-symbols-outlined');
  const label = button.querySelector('[data-copy-label]');
  if (!icon) return;

  button.dataset.defaultIcon ||= icon.textContent.trim();
  if (label) button.dataset.defaultLabel ||= label.textContent.trim();
  button.dataset.defaultAriaLabel ||= button.getAttribute('aria-label') || '';
  button.dataset.defaultTitle ||= button.getAttribute('title') || '';

  icon.textContent = 'check';
  if (label) label.textContent = 'COPIED';
  if (button.dataset.defaultAriaLabel) button.setAttribute('aria-label', copiedLabel);
  if (button.dataset.defaultTitle) button.setAttribute('title', copiedLabel);
  button.classList.add('is-copied');

  window.clearTimeout(copiedFeedbackTimers.get(button));
  copiedFeedbackTimers.set(button, window.setTimeout(() => {
    resetCopiedFeedback(button);
  }, 2600));
}

function resetCopiedFeedback(button) {
  const icon = button.querySelector('.material-symbols-outlined');
  const label = button.querySelector('[data-copy-label]');
  window.clearTimeout(copiedFeedbackTimers.get(button));
  copiedFeedbackTimers.delete(button);
  if (icon && button.dataset.defaultIcon) icon.textContent = button.dataset.defaultIcon;
  if (label && button.dataset.defaultLabel) label.textContent = button.dataset.defaultLabel;
  if (button.dataset.defaultAriaLabel) button.setAttribute('aria-label', button.dataset.defaultAriaLabel);
  if (button.dataset.defaultTitle) button.setAttribute('title', button.dataset.defaultTitle);
  button.classList.remove('is-copied');
}

async function copyText(value) {
  if (navigator.clipboard?.writeText) {
    await navigator.clipboard.writeText(value);
    return;
  }

  const fallback = document.createElement('textarea');
  fallback.value = value;
  fallback.setAttribute('readonly', '');
  fallback.className = 'clipboard-fallback';
  document.body.append(fallback);
  fallback.select();
  const copied = document.execCommand('copy');
  fallback.remove();
  if (!copied) throw new Error('Clipboard unavailable.');
}

function setActionStatus(message, triggerButton = null) {
  window.clearTimeout(actionStatusTimer);
  for (const status of document.querySelectorAll('[data-action-status], #action-status')) {
    status.textContent = '';
  }
  const target = triggerButton?.closest('dialog')?.querySelector('[data-action-status]') || actionStatus;
  target.textContent = message;
  if (!message) return;
  actionStatusTimer = window.setTimeout(() => {
    target.textContent = '';
    actionStatusTimer = null;
  }, 3200);
}

async function validateClientFile(file) {
  if (!file) return 'SELECT A FILE.';
  if (!SAFE_TYPES.has(file.type)) return 'ONLY PNG AND JPEG ARE ACCEPTED.';
  if (file.size > MAX_FILE_BYTES) return 'IMAGE EXCEEDS 5MB.';

  try {
    const dimensions = await readImageDimensions(file);
    if (dimensions.width > 6000 || dimensions.height > 6000) return 'IMAGE EDGE EXCEEDS 6000PX.';
    if (dimensions.width * dimensions.height > MAX_IMAGE_PIXELS) return 'IMAGE EXCEEDS 20MP.';
  } catch {
    return 'IMAGE COULD NOT BE INSPECTED.';
  }
  return '';
}

function readImageDimensions(file) {
  return new Promise((resolve, reject) => {
    const image = new Image();
    const url = URL.createObjectURL(file);
    image.onload = () => {
      URL.revokeObjectURL(url);
      resolve({ width: image.naturalWidth, height: image.naturalHeight });
    };
    image.onerror = () => {
      URL.revokeObjectURL(url);
      reject(new Error('invalid image'));
    };
    image.src = url;
  });
}

function closeUpload() {
  uploadForm.reset();
  fileLabel.textContent = 'SELECT PNG / JPEG';
  formStatus.textContent = '';
  uploadModal.close();
}

function handleViewIntersections(entries) {
  for (const entry of entries) {
    const id = entry.target.dataset.viewObserveId;
    if (!id || wasRecentlyViewed(id)) {
      stopViewTimer(id);
      if (wasRecentlyViewed(id)) viewObserver.unobserve(entry.target);
      continue;
    }

    if (entry.isIntersecting && entry.intersectionRatio >= VIEW_THRESHOLD) {
      startViewTimer(id, entry.target);
    } else {
      stopViewTimer(id);
    }
  }
}

function observeViewedImages(cards) {
  if (!viewObserver) return;
  for (const card of cards) {
    const image = card.querySelector('[data-view-observe-id]');
    if (image && !wasRecentlyViewed(image.dataset.viewObserveId)) viewObserver.observe(image);
  }
}

function unobserveViewedImages() {
  for (const timer of pendingViewTimers.values()) window.clearTimeout(timer);
  pendingViewTimers.clear();
  if (!viewObserver) return;
  for (const image of grid.querySelectorAll('[data-view-observe-id]')) {
    viewObserver.unobserve(image);
  }
}

function startViewTimer(id, target) {
  if (pendingViewTimers.has(id)) return;
  const timer = window.setTimeout(async () => {
    pendingViewTimers.delete(id);
    if (wasRecentlyViewed(id)) {
      viewObserver.unobserve(target);
      return;
    }
    await recordViewOnce(id);
    viewObserver.unobserve(target);
  }, VIEW_DWELL_MS);
  pendingViewTimers.set(id, timer);
}

function stopViewTimer(id) {
  const timer = pendingViewTimers.get(id);
  if (!timer) return;
  window.clearTimeout(timer);
  pendingViewTimers.delete(id);
}

async function recordViewOnce(id) {
  if (!isMemeId(id)) return;
  if (wasRecentlyViewed(id)) return;
  rememberViewed(id);
  try {
    const response = await fetch(`/api/memes/${id}/view`, { method: 'POST' });
    const payload = await response.json();
    if (!response.ok) throw new Error(payload.error || 'View update failed.');
    updateCounters(payload.meme);
  } catch {
    forgetViewed(id);
    // Live counter updates are best-effort; the stream and lightbox should still work.
  }
}

function isMemeId(id) {
  return typeof id === 'string' && /^[a-f0-9]{64}$/.test(id);
}

function wasRecentlyViewed(id) {
  if (viewedThisSession.has(id)) return true;
  try {
    const viewedAt = Number.parseInt(localStorage.getItem(`${VIEW_DEDUPE_PREFIX}${id}`) || '0', 10);
    if (Number.isFinite(viewedAt) && Date.now() - viewedAt < VIEW_DEDUPE_MS) return true;
  } catch {
    // Storage can be unavailable in hardened browser modes; session memory still dedupes.
  }
  return false;
}

function rememberViewed(id) {
  viewedThisSession.add(id);
  try {
    localStorage.setItem(`${VIEW_DEDUPE_PREFIX}${id}`, String(Date.now()));
  } catch {
    // localStorage is optional; no upload or view flow depends on it.
  }
}

function forgetViewed(id) {
  viewedThisSession.delete(id);
  try {
    localStorage.removeItem(`${VIEW_DEDUPE_PREFIX}${id}`);
  } catch {
    // Optional storage cleanup.
  }
}

function connectLiveCounters() {
  if (!('EventSource' in window)) return;
  const source = new EventSource('/api/events');
  source.addEventListener('metric', (event) => {
    updateCounters(JSON.parse(event.data));
  });
}

function updateCounters(meme) {
  for (const element of document.querySelectorAll(`[data-counter-id="${meme.id}"]`)) {
    const count = element.querySelector('[data-count]');
    if (!count) continue;
    if (element.dataset.counterKind === 'view') {
      count.textContent = formatCount(meme.viewCount);
    }
    if (element.dataset.counterKind === 'download') {
      count.textContent = formatCount(meme.downloadCount);
    }
  }
  for (const button of document.querySelectorAll(`[data-view-id="${meme.id}"]`)) {
    button.dataset.viewCount = String(meme.viewCount);
    button.dataset.downloadCount = String(meme.downloadCount);
  }
}

function setLoader(label, spinning) {
  feedLoaderLabel.textContent = label;
  feedLoader.classList.toggle('is-spinning', spinning);
}

function shortId(id) {
  return `0x${id.slice(0, 4).toUpperCase()}...${id.slice(-4).toUpperCase()}`;
}

function relativeAge(value) {
  const elapsed = Math.max(1, Date.now() - new Date(value).getTime());
  const minutes = Math.floor(elapsed / 60000);
  if (minutes < 60) return `${minutes}M AGO`;
  const hours = Math.floor(minutes / 60);
  if (hours < 24) return `${hours}H AGO`;
  return `${Math.floor(hours / 24)}D AGO`;
}

function formatCount(value) {
  const count = Number.isFinite(value) ? value : 0;
  if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M`;
  if (count >= 1_000) return `${(count / 1_000).toFixed(1)}K`;
  return String(count);
}

function scoreBucket(value) {
  const score = Number.isFinite(value) ? value : 50;
  if (score >= 90) return 5;
  if (score >= 75) return 4;
  if (score >= 60) return 3;
  if (score >= 40) return 2;
  if (score >= 20) return 1;
  return 0;
}

async function refreshStatus() {
  const started = performance.now();
  try {
    const response = await fetch('/api/status', { cache: 'no-store' });
    const status = await response.json();
    if (!response.ok) throw new Error('status failed');
    setStat('status', `${status.ok ? 'ONLINE' : 'DEGRADED'} ${formatUptime(status.uptimeSeconds)}`);
    setStat('latency', `${Math.max(1, Math.round(performance.now() - started))}MS`);
    setStat('nodes', formatCount(status.liveClients));
    setStat('memes', formatCount(status.memeCount));
  } catch {
    setStat('status', 'OFFLINE');
    setStat('latency', '--MS');
    setStat('nodes', '--');
    setStat('memes', '--');
  }
}

function setStat(name, value) {
  for (const element of document.querySelectorAll(`[data-stat="${name}"]`)) {
    element.textContent = value;
  }
}

function formatUptime(value) {
  const seconds = Math.max(0, Number.isFinite(value) ? value : 0);
  const minutes = Math.floor(seconds / 60);
  if (minutes < 1) return `UP ${seconds}S`;
  if (minutes < 60) return `UP ${minutes}M`;

  const hours = Math.floor(minutes / 60);
  const remainingMinutes = minutes % 60;
  if (hours < 24) return `UP ${hours}H${remainingMinutes > 0 ? ` ${remainingMinutes}M` : ''}`;

  const days = Math.floor(hours / 24);
  const remainingHours = hours % 24;
  return `UP ${days}D${remainingHours > 0 ? ` ${remainingHours}H` : ''}`;
}

function updateScrollIndicator() {
  const segments = [...scrollIndicator.querySelectorAll('span')];
  const maxScroll = Math.max(1, document.documentElement.scrollHeight - window.innerHeight);
  const progress = Math.min(1, Math.max(0, window.scrollY / maxScroll));
  const activeIndex = Math.min(segments.length - 1, Math.round(progress * (segments.length - 1)));
  segments.forEach((segment, index) => {
    segment.classList.toggle('active', index === activeIndex);
  });
}
