const API_URL = 'https://fb.srv.medeba-media.de/api';
const LOGIN_PAGE = 'login.html';

let initialViewParam = null;
try {
  const params = new URLSearchParams(window.location.search);
  initialViewParam = params.get('view');
} catch (error) {
  console.warn('Konnte view-Parameter nicht auslesen:', error);
}

let focusPostIdParam = null;
let focusPostUrlParam = null;
let focusNormalizedUrl = '';
let focusHandled = false;
let initialTabOverride = null;
let focusTabAdjusted = null;

let currentProfile = null;
let currentTab = 'pending';
let posts = [];
let includeExpiredPosts = false;
let profilePollTimer = null;
const MERGE_MAX_SELECTION = 2;
let mergeMode = false;
const mergeSelection = new Set();
const UPDATES_RECONNECT_DELAY = 5000;
let updatesEventSource = null;
let updatesReconnectTimer = null;
let updatesStreamHealthy = false;
let updatesShouldResyncOnConnect = false;

const MAX_PROFILES = 5;
const PROFILE_NAMES = {
  1: 'Profil 1',
  2: 'Profil 2',
  3: 'Profil 3',
  4: 'Profil 4',
  5: 'Profil 5'
};

function isValidProfileNumber(value) {
  return Number.isInteger(value) && value >= 1 && value <= MAX_PROFILES;
}

function redirectToLogin() {
  try {
    const redirect = encodeURIComponent(window.location.href);
    window.location.href = `${LOGIN_PAGE}?redirect=${redirect}`;
  } catch (_error) {
    window.location.href = LOGIN_PAGE;
  }
}

async function ensureAuthenticated() {
  try {
    const response = await fetch(`${API_URL}/session`, { credentials: 'include' });
    if (response.status === 401) {
      redirectToLogin();
      return false;
    }
    return true;
  } catch (error) {
    console.warn('Konnte Auth-Status nicht prüfen:', error);
    redirectToLogin();
    return false;
  }
}
 
function apiFetch(url, options = {}) {
  const config = {
    ...options,
    credentials: 'include'
  };

  if (options && options.headers) {
    config.headers = { ...options.headers };
  }

  return fetch(url, config).then((response) => {
    if (response.status === 401) {
      redirectToLogin();
      throw new Error('Nicht angemeldet');
    }
    return response;
  });
}

function showToast(message, type = 'info') {
  const toast = document.createElement('div');
  toast.style.cssText = `
    position: fixed;
    bottom: 84px;
    right: 24px;
    background: ${type === 'error' ? '#e74c3c' : type === 'success' ? '#42b72a' : '#1877f2'};
    color: white;
    padding: 12px 20px;
    border-radius: 8px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    font-size: 14px;
    z-index: 999999;
    max-width: 350px;
    animation: appToastSlideIn 0.3s ease-out;
    pointer-events: none;
  `;
  toast.textContent = message;

  if (!document.getElementById('app-toast-styles')) {
    const style = document.createElement('style');
    style.id = 'app-toast-styles';
    style.textContent = `
      @keyframes appToastSlideIn {
        from {
          transform: translateX(400px);
          opacity: 0;
        }
        to {
          transform: translateX(0);
          opacity: 1;
        }
      }
      @keyframes appToastSlideOut {
        from {
          transform: translateX(0);
          opacity: 1;
        }
        to {
          transform: translateX(400px);
          opacity: 0;
        }
      }
    `;
    document.head.appendChild(style);
  }

  document.body.appendChild(toast);

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

function markAppReady() {
  if (document && document.body) {
    document.body.classList.remove('auth-pending');
  }
}

async function handleLogout() {
  try {
    await fetch(`${API_URL}/logout`, { method: 'POST', credentials: 'include' });
  } catch (error) {
    // ignore network errors on logout
  }
  redirectToLogin();
}

function bindLogoutButton() {
  const btn = document.getElementById('logoutBtn');
  if (!btn) {
    return;
  }
  btn.addEventListener('click', handleLogout);
}

function sortPostsByCreatedAt() {
  posts.sort((a, b) => toTimestamp(b.created_at, 0) - toTimestamp(a.created_at, 0));
}

function applyPostUpdateFromStream(post) {
  if (!post || !post.id) {
    return;
  }

  const index = posts.findIndex((item) => item.id === post.id);
  if (index !== -1) {
    posts[index] = post;
  } else {
    posts.push(post);
  }

  sortPostsByCreatedAt();

  if (manualPostMode === 'edit' && manualPostEditingId === post.id) {
    populateManualPostForm(post);
  }

  renderPosts();
}

function removePostFromCache(postId) {
  if (!postId) {
    return;
  }

  const index = posts.findIndex((item) => item.id === postId);
  if (index === -1) {
    return;
  }

  posts.splice(index, 1);

  if (
    manualPostMode === 'edit'
    && manualPostEditingId === postId
    && manualPostModal
    && manualPostModal.classList.contains('open')
  ) {
    closeManualPostModal();
  }

  renderPosts();
}

function handleBackendEvent(eventPayload) {
  if (!eventPayload || typeof eventPayload !== 'object') {
    return;
  }

  switch (eventPayload.type) {
    case 'post-upsert':
      if (eventPayload.post) {
        applyPostUpdateFromStream(eventPayload.post);
      }
      break;
    case 'post-deleted':
      if (eventPayload.postId) {
        removePostFromCache(eventPayload.postId);
      }
      break;
    case 'connected':
    case 'heartbeat':
    default:
      break;
  }
}

function scheduleUpdatesReconnect() {
  if (updatesReconnectTimer) {
    return;
  }
  updatesReconnectTimer = setTimeout(() => {
    updatesReconnectTimer = null;
    startUpdatesStream();
  }, UPDATES_RECONNECT_DELAY);
}

function startUpdatesStream() {
  if (typeof EventSource === 'undefined') {
    console.warn('EventSource wird von diesem Browser nicht unterstützt. Fallback auf Polling.');
    return;
  }

  if (updatesEventSource) {
    return;
  }

  const eventsUrl = `${API_URL}/events`;
  let eventSource;

  try {
    eventSource = new EventSource(eventsUrl, { withCredentials: true });
  } catch (error) {
    console.warn('Konnte Update-Stream nicht starten:', error);
    scheduleUpdatesReconnect();
    return;
  }

  updatesEventSource = eventSource;

  eventSource.addEventListener('open', () => {
    updatesStreamHealthy = true;
    if (updatesReconnectTimer) {
      clearTimeout(updatesReconnectTimer);
      updatesReconnectTimer = null;
    }
    if (updatesShouldResyncOnConnect) {
      updatesShouldResyncOnConnect = false;
      fetchPosts({ showLoader: false });
    }
    applyAutoRefreshSettings();
  });

  eventSource.addEventListener('message', (event) => {
    if (!event || typeof event.data !== 'string' || !event.data.trim()) {
      return;
    }
    let payload;
    try {
      payload = JSON.parse(event.data);
    } catch (error) {
      console.warn('Ungültige Daten vom Update-Stream erhalten:', error);
      return;
    }
    handleBackendEvent(payload);
  });

  eventSource.addEventListener('error', () => {
    if (updatesEventSource) {
      updatesEventSource.close();
      updatesEventSource = null;
    }
    if (!updatesShouldResyncOnConnect) {
      updatesShouldResyncOnConnect = true;
    }
    updatesStreamHealthy = false;
    applyAutoRefreshSettings();
    fetchPosts({ showLoader: false });
    scheduleUpdatesReconnect();
  });
}

const screenshotModal = document.getElementById('screenshotModal');
const screenshotModalContent = document.getElementById('screenshotModalContent');
const screenshotModalImage = document.getElementById('screenshotModalImage');
const screenshotModalClose = document.getElementById('screenshotModalClose');
const screenshotModalBackdrop = document.getElementById('screenshotModalBackdrop');
let screenshotModalLastFocus = null;
let screenshotModalPreviousOverflow = '';
let screenshotModalZoomed = false;

const manualPostForm = document.getElementById('manualPostForm');
const manualPostUrlInput = document.getElementById('manualPostUrl');
const manualPostTitleInput = document.getElementById('manualPostTitle');
const manualPostTargetSelect = document.getElementById('manualPostTarget');
const manualPostCreatorInput = document.getElementById('manualPostCreatorName');
const manualPostDeadlineInput = document.getElementById('manualPostDeadline');
const manualPostResetButton = document.getElementById('manualPostReset');
const manualPostMessage = document.getElementById('manualPostMessage');
const manualPostSubmitButton = document.getElementById('manualPostSubmitBtn');
const manualPostModal = document.getElementById('manualPostModal');
const manualPostModalContent = document.getElementById('manualPostModalContent');
const manualPostModalBackdrop = document.getElementById('manualPostModalBackdrop');
const manualPostModalClose = document.getElementById('manualPostModalClose');
const manualPostModalTitle = document.getElementById('manualPostModalTitle');
const openManualPostModalBtn = document.getElementById('openManualPostModalBtn');

const autoRefreshToggle = document.getElementById('autoRefreshToggle');
const autoRefreshIntervalSelect = document.getElementById('autoRefreshInterval');
const sortModeSelect = document.getElementById('sortMode');
const sortDirectionToggle = document.getElementById('sortDirectionToggle');
const bookmarkPanelToggle = document.getElementById('bookmarkPanelToggle');
const bookmarkPanel = document.getElementById('bookmarkPanel');
const bookmarkPanelClose = document.getElementById('bookmarkPanelClose');
const bookmarksList = document.getElementById('bookmarksList');
const bookmarkForm = document.getElementById('bookmarkForm');
const bookmarkNameInput = document.getElementById('bookmarkName');
const bookmarkQueryInput = document.getElementById('bookmarkQuery');
const bookmarkCancelBtn = document.getElementById('bookmarkCancelBtn');
const bookmarkQuickForm = document.getElementById('bookmarkQuickForm');
const bookmarkQuickQueryInput = document.getElementById('bookmarkQuickQuery');
const bookmarkQuickStatus = document.getElementById('bookmarkQuickStatus');
const bookmarkSearchInput = document.getElementById('bookmarkSearchInput');
const bookmarkSortSelect = document.getElementById('bookmarkSortSelect');
const bookmarkSortDirectionToggle = document.getElementById('bookmarkSortDirectionToggle');
const profileSelectElement = document.getElementById('profileSelect');
const includeExpiredToggle = document.getElementById('includeExpiredToggle');
const searchInput = document.getElementById('searchInput');
const searchClearBtn = document.getElementById('searchClearBtn');
const mergeControls = document.getElementById('mergeControls');
const mergeModeToggle = document.getElementById('mergeModeToggle');
const mergeSubmitBtn = document.getElementById('mergeSubmitBtn');
const pendingBulkControls = document.getElementById('pendingBulkControls');
const pendingBulkCountSelect = document.getElementById('pendingBulkCountSelect');
const pendingBulkOpenBtn = document.getElementById('pendingBulkOpenBtn');
const pendingAutoOpenToggle = document.getElementById('pendingAutoOpenToggle');
const pendingAutoOpenOverlay = document.getElementById('pendingAutoOpenOverlay');
const pendingAutoOpenOverlayPanel = document.getElementById('pendingAutoOpenOverlayPanel');
const pendingAutoOpenCountdown = document.getElementById('pendingAutoOpenCountdown');
const pendingBulkStatus = document.getElementById('pendingBulkStatus');
const postsScrollTopBtn = document.getElementById('postsScrollTopBtn');

const REFRESH_SETTINGS_KEY = 'trackerRefreshSettings';
const SORT_SETTINGS_KEY = 'trackerSortSettings';
const SORT_SETTINGS_LEGACY_KEY = 'trackerSortMode';
const FACEBOOK_TRACKING_PARAMS = ['__cft__', '__tn__', '__eep__', 'mibextid', 'set', 'comment_id', 'hoisted_section_header_type'];
const FACEBOOK_PROFILE_SEGMENT_BLOCKLIST = new Set(['reel', 'reels', 'watch', 'video.php', 'videos', 'photo.php', 'photos', 'story.php', 'permalink.php', 'share', 'posts']);
const VALID_SORT_MODES = new Set(['created', 'deadline', 'smart', 'lastCheck', 'lastChange']);
const DEFAULT_SORT_SETTINGS = { mode: 'created', direction: 'desc' };
const BOOKMARKS_BASE_URL = 'https://www.facebook.com/search/top';
const BOOKMARK_WINDOW_DAYS = 28;
const BOOKMARK_SUFFIXES = ['Gewinnspiel', 'gewinnen', 'verlosen'];
const DEFAULT_BOOKMARK_LABEL = 'Gewinnspiel / gewinnen / verlosen';
const BOOKMARK_PREFS_KEY = 'trackerBookmarkPreferences';
const INCLUDE_EXPIRED_STORAGE_KEY = 'trackerIncludeExpired';
const POSTS_SEARCH_STORAGE_KEY = 'trackerPostsSearchTerm';
const PENDING_BULK_COUNT_STORAGE_KEY = 'trackerPendingBulkCount';
const PENDING_AUTO_OPEN_STORAGE_KEY = 'trackerPendingAutoOpen';
const DEFAULT_PENDING_BULK_COUNT = 5;
const PENDING_AUTO_OPEN_DELAY_MS = 1500;
const PENDING_OPEN_COOLDOWN_STORAGE_KEY = 'trackerPendingOpenCooldown';
const PENDING_OPEN_COOLDOWN_MS = 40 * 60 * 1000;
const TAB_LABELS = {
  all: 'Alle Beiträge',
  pending: 'Offene Beiträge'
};
const SORT_MODE_LABELS = {
  created: 'Erstelldatum',
  deadline: 'Deadline',
  lastCheck: 'Letzte Teilnahme',
  lastChange: 'Letzte Änderung',
  smart: 'Smart (Dringlichkeit)'
};

function loadIncludeExpiredPreference() {
  try {
    const stored = localStorage.getItem(INCLUDE_EXPIRED_STORAGE_KEY);
    if (stored === 'true') {
      return true;
    }
    if (stored === 'false') {
      return false;
    }
  } catch (error) {
    console.warn('Konnte Anzeigepräferenz für abgelaufene Beiträge nicht laden:', error);
  }
  return false;
}

function persistIncludeExpiredPreference(value) {
  try {
    localStorage.setItem(INCLUDE_EXPIRED_STORAGE_KEY, value ? 'true' : 'false');
  } catch (error) {
    console.warn('Konnte Anzeigepräferenz für abgelaufene Beiträge nicht speichern:', error);
  }
}

function getPendingOpenCooldownStorageKey(profileNumber = currentProfile) {
  const safeProfile = isValidProfileNumber(profileNumber) ? profileNumber : null;
  if (!safeProfile) {
    return null;
  }
  return `${PENDING_OPEN_COOLDOWN_STORAGE_KEY}:${safeProfile}`;
}

function loadPendingOpenCooldownMap(profileNumber = currentProfile) {
  const storageKey = getPendingOpenCooldownStorageKey(profileNumber);
  if (!storageKey) {
    return {};
  }
  try {
    const raw = localStorage.getItem(storageKey);
    if (raw) {
      const parsed = JSON.parse(raw);
      if (parsed && typeof parsed === 'object') {
        const now = Date.now();
        const cleaned = {};
        Object.entries(parsed).forEach(([id, timestamp]) => {
          const value = Number(timestamp);
          if (Number.isFinite(value) && now - value < PENDING_OPEN_COOLDOWN_MS) {
            cleaned[id] = value;
          }
        });
        if (Object.keys(cleaned).length !== Object.keys(parsed).length) {
          localStorage.setItem(storageKey, JSON.stringify(cleaned));
        }
        return cleaned;
      }
    }
  } catch (error) {
    console.warn('Konnte Cooldown-Daten für offene Beiträge nicht laden:', error);
  }
  return {};
}

function persistPendingOpenCooldownMap(profileNumber, map) {
  const storageKey = getPendingOpenCooldownStorageKey(profileNumber);
  if (!storageKey) {
    return;
  }
  try {
    localStorage.setItem(storageKey, JSON.stringify(map || {}));
  } catch (error) {
    console.warn('Konnte Cooldown-Daten für offene Beiträge nicht speichern:', error);
  }
}

function isPendingOpenCooldownActive(postId) {
  if (!postId) {
    return false;
  }
  const timestamp = pendingOpenCooldownMap[postId];
  if (!timestamp) {
    return false;
  }
  const elapsed = Date.now() - timestamp;
  if (elapsed < PENDING_OPEN_COOLDOWN_MS) {
    return true;
  }
  delete pendingOpenCooldownMap[postId];
  persistPendingOpenCooldownMap(currentProfile, pendingOpenCooldownMap);
  return false;
}

function recordPendingOpen(postId) {
  if (!postId) {
    return;
  }
  pendingOpenCooldownMap[postId] = Date.now();
  persistPendingOpenCooldownMap(currentProfile, pendingOpenCooldownMap);
}

function loadPendingBulkCount() {
  try {
    const stored = localStorage.getItem(PENDING_BULK_COUNT_STORAGE_KEY);
    const value = parseInt(stored, 10);
    if (!Number.isNaN(value) && [1, 5, 10, 15, 20].includes(value)) {
      return value;
    }
  } catch (error) {
    console.warn('Konnte Bulk-Anzahl für offene Beiträge nicht laden:', error);
  }
  return DEFAULT_PENDING_BULK_COUNT;
}

function persistPendingBulkCount(value) {
  try {
    localStorage.setItem(PENDING_BULK_COUNT_STORAGE_KEY, String(value));
  } catch (error) {
    console.warn('Konnte Bulk-Anzahl für offene Beiträge nicht speichern:', error);
  }
}

function loadPendingAutoOpenEnabled() {
  try {
    return localStorage.getItem(PENDING_AUTO_OPEN_STORAGE_KEY) === '1';
  } catch (error) {
    console.warn('Konnte Auto-Öffnen-Status nicht laden:', error);
    return false;
  }
}

function persistPendingAutoOpenEnabled(enabled) {
  try {
    localStorage.setItem(PENDING_AUTO_OPEN_STORAGE_KEY, enabled ? '1' : '0');
  } catch (error) {
    console.warn('Konnte Auto-Öffnen-Status nicht speichern:', error);
  }
}

function loadPostsSearchTerm() {
  try {
    const stored = localStorage.getItem(POSTS_SEARCH_STORAGE_KEY);
    if (typeof stored === 'string') {
      return stored.slice(0, 240);
    }
  } catch (error) {
    console.warn('Konnte Suchbegriff der Posts-Ansicht nicht laden:', error);
  }
  return '';
}

function persistPostsSearchTerm(value) {
  try {
    const normalized = typeof value === 'string' ? value : '';
    if (!normalized) {
      localStorage.removeItem(POSTS_SEARCH_STORAGE_KEY);
      return;
    }
    localStorage.setItem(POSTS_SEARCH_STORAGE_KEY, normalized.slice(0, 240));
  } catch (error) {
    console.warn('Konnte Suchbegriff der Posts-Ansicht nicht speichern:', error);
  }
}

function updateIncludeExpiredToggleUI() {
  if (!includeExpiredToggle) {
    return;
  }
  includeExpiredToggle.checked = includeExpiredPosts;
}

includeExpiredPosts = loadIncludeExpiredPreference();
let postsSearchTerm = loadPostsSearchTerm();
let pendingBulkCount = loadPendingBulkCount();
let pendingAutoOpenEnabled = loadPendingAutoOpenEnabled();
let pendingAutoOpenTriggered = false;
let pendingAutoOpenTimerId = null;
let pendingAutoOpenCountdownIntervalId = null;
let pendingProcessingBatch = false;
let pendingOpenCooldownMap = loadPendingOpenCooldownMap(currentProfile);
let pendingBulkStatusResetTimerId = null;
let lastPendingBulkHint = { openableCount: 0, cooldownBlocked: 0 };

function updateIncludeExpiredToggleVisibility() {
  if (!includeExpiredToggle) {
    return;
  }
  const wrapper = includeExpiredToggle.closest('.search-filter-toggle');
  if (!wrapper) {
    return;
  }
  wrapper.style.display = currentTab === 'all' ? 'inline-flex' : 'none';
}

function resetMergeSelection() {
  mergeSelection.clear();
}

function updateMergeControlsUI() {
  if (!mergeControls) {
    return;
  }

  const isAllTab = currentTab === 'all';
  mergeControls.hidden = !isAllTab;
  mergeControls.style.display = isAllTab ? 'flex' : 'none';

  if (!isAllTab && mergeMode) {
    mergeMode = false;
    resetMergeSelection();
  }

  if (mergeModeToggle) {
    mergeModeToggle.disabled = !isAllTab;
    mergeModeToggle.classList.toggle('active', mergeMode);
    mergeModeToggle.textContent = mergeMode ? 'Merge-Modus: aktiv' : 'Merge-Modus';
  }

  if (mergeSubmitBtn) {
    const count = mergeSelection.size;
    mergeSubmitBtn.disabled = !mergeMode || !isAllTab || count !== MERGE_MAX_SELECTION;
    mergeSubmitBtn.textContent = `Beiträge mergen (${count}/${MERGE_MAX_SELECTION})`;
  }
}

function clearPendingBulkStatusResetTimer() {
  if (pendingBulkStatusResetTimerId) {
    clearTimeout(pendingBulkStatusResetTimerId);
    pendingBulkStatusResetTimerId = null;
  }
}

function buildPendingBulkHintText({ openableCount = 0, cooldownBlocked = 0 } = {}) {
  const openable = Math.max(0, openableCount || 0);
  const blocked = Math.max(0, cooldownBlocked || 0);
  if (blocked > 0) {
    return `Öffnungsbereit: ${openable} · Cooldown: ${blocked}`;
  }
  return `Öffnungsbereit: ${openable}`;
}

function applyPendingBulkHintStatus() {
  if (!pendingBulkStatus) {
    return;
  }
  if (currentTab !== 'pending') {
    pendingBulkStatus.textContent = '';
    pendingBulkStatus.classList.remove('bulk-status--hint', 'bulk-status--error');
    return;
  }
  pendingBulkStatus.textContent = buildPendingBulkHintText(lastPendingBulkHint);
  pendingBulkStatus.classList.add('bulk-status--hint');
  pendingBulkStatus.classList.remove('bulk-status--error');
}

function updatePendingBulkControls(filteredCount = 0, stats = null) {
  if (!pendingBulkControls) {
    return;
  }
  const isPendingTab = currentTab === 'pending';
  pendingBulkControls.hidden = !isPendingTab;
  pendingBulkControls.style.display = isPendingTab ? 'flex' : 'none';

  if (!isPendingTab) {
    clearPendingBulkStatusResetTimer();
    if (pendingBulkStatus) {
      pendingBulkStatus.textContent = '';
      pendingBulkStatus.classList.remove('bulk-status--hint', 'bulk-status--error');
    }
  }

  const normalizedStats = stats && typeof stats === 'object'
    ? {
      openableCount: Math.max(0, parseInt(stats.openableCount, 10) || 0),
      cooldownBlocked: Math.max(0, parseInt(stats.cooldownBlocked, 10) || 0)
    }
    : { openableCount: Math.max(0, filteredCount || 0), cooldownBlocked: 0 };
  lastPendingBulkHint = normalizedStats;

  if (pendingBulkOpenBtn) {
    pendingBulkOpenBtn.disabled = !isPendingTab || pendingProcessingBatch || normalizedStats.openableCount === 0;
  }

  if (isPendingTab) {
    applyPendingBulkHintStatus();
  }
}

function setPendingBulkStatus(message = '', isError = false) {
  clearPendingBulkStatusResetTimer();
  if (!pendingBulkStatus) {
    if (message) {
      showToast(message, isError ? 'error' : 'info');
    }
    return;
  }
  if (!message) {
    applyPendingBulkHintStatus();
    return;
  }
  pendingBulkStatus.textContent = message;
  pendingBulkStatus.classList.toggle('bulk-status--error', !!isError);
  pendingBulkStatus.classList.toggle('bulk-status--hint', !isError);
  showToast(message, isError ? 'error' : 'info');
  pendingBulkStatusResetTimerId = setTimeout(() => {
    pendingBulkStatusResetTimerId = null;
    applyPendingBulkHintStatus();
  }, 4000);
}

function initializeFocusParams() {
  try {
    const params = new URLSearchParams(window.location.search);
    const postIdParam = params.get('postId');
    const postUrlParam = params.get('postUrl');

    if (postIdParam && postIdParam.trim()) {
      focusPostIdParam = postIdParam.trim();
      initialTabOverride = initialTabOverride || 'all';
    }

    if (postUrlParam && postUrlParam.trim()) {
      focusPostUrlParam = postUrlParam.trim();
      const normalized = normalizeFacebookPostUrl(focusPostUrlParam);
      focusNormalizedUrl = normalized || focusPostUrlParam;
      initialTabOverride = initialTabOverride || 'all';
    }
    focusHandled = false;
    focusTabAdjusted = null;
  } catch (error) {
    console.warn('Konnte Fokus-Parameter nicht verarbeiten:', error);
  }
}

let autoRefreshTimer = null;
let autoRefreshSettings = {
  enabled: false,
  interval: 30000
};
let sortMode = DEFAULT_SORT_SETTINGS.mode;
let sortDirection = DEFAULT_SORT_SETTINGS.direction;
let isFetchingPosts = false;
let manualPostMode = 'create';
let manualPostEditingId = null;
let manualPostModalLastFocus = null;
let manualPostModalPreviousOverflow = '';
let activeDeadlinePicker = null;
let bookmarkPanelVisible = false;
let bookmarkOutsideHandler = null;
let bookmarkSearchTerm = '';
let bookmarkSortMode = 'recent';
let bookmarkSortDirection = 'desc';

function loadBookmarkPreferences() {
  try {
    const stored = localStorage.getItem(BOOKMARK_PREFS_KEY);
    if (stored) {
      const parsed = JSON.parse(stored);
      if (parsed && typeof parsed === 'object') {
        return parsed;
      }
    }
  } catch (error) {
    console.warn('Konnte Bookmark-Einstellungen nicht laden:', error);
  }
  return {};
}

function persistBookmarkPreferences() {
  try {
    const payload = {
      searchTerm: bookmarkSearchTerm,
      sortMode: bookmarkSortMode,
      sortDirection: bookmarkSortDirection
    };
    localStorage.setItem(BOOKMARK_PREFS_KEY, JSON.stringify(payload));
  } catch (error) {
    console.warn('Konnte Bookmark-Einstellungen nicht speichern:', error);
  }
}

const storedBookmarkPrefs = loadBookmarkPreferences();
if (storedBookmarkPrefs.searchTerm && typeof storedBookmarkPrefs.searchTerm === 'string') {
  bookmarkSearchTerm = storedBookmarkPrefs.searchTerm.trim();
}
if (storedBookmarkPrefs.sortMode === 'label' || storedBookmarkPrefs.sortMode === 'recent') {
  bookmarkSortMode = storedBookmarkPrefs.sortMode;
}
if (storedBookmarkPrefs.sortDirection === 'asc' || storedBookmarkPrefs.sortDirection === 'desc') {
  bookmarkSortDirection = storedBookmarkPrefs.sortDirection;
}

const INITIAL_POST_LIMIT = 10;
const POST_LOAD_INCREMENT = 10;
const tabVisibleCounts = {
  pending: INITIAL_POST_LIMIT,
  all: INITIAL_POST_LIMIT
};
const tabFilteredCounts = {
  pending: 0,
  all: 0
};
const tabDisplayCounts = {
  pending: 0,
  all: 0
};
let loadMoreObserver = null;
let observedLoadMoreElement = null;

function getProfileName(profileNumber) {
  if (!profileNumber) {
    return '';
  }
  return PROFILE_NAMES[profileNumber] || `Profil ${profileNumber}`;
}

function formatDateTime(value) {
  if (!value) {
    return '';
  }

  try {
    const date = value instanceof Date ? value : new Date(value);
    if (Number.isNaN(date.getTime())) {
      return '';
    }
    return date.toLocaleString('de-DE', { timeZone: 'Europe/Berlin' });
  } catch (error) {
    console.warn('Ungültiges Datum:', error);
    return '';
  }
}

function formatDeadline(value) {
  if (!value) {
    return 'Keine Deadline';
  }

  try {
    const date = value instanceof Date ? value : new Date(value);
    if (Number.isNaN(date.getTime())) {
      return 'Ungültige Deadline';
    }
    return date.toLocaleString('de-DE', { timeZone: 'Europe/Berlin' });
  } catch (error) {
    console.warn('Ungültige Deadline:', error);
    return 'Ungültige Deadline';
  }
}

function formatUrlForDisplay(url) {
  if (!url) {
    return '';
  }

  try {
    const parsed = new URL(url);
    const pathname = parsed.pathname === '/' ? '' : parsed.pathname;
    const search = parsed.search || '';
    return `${parsed.hostname}${pathname}${search}`;
  } catch (error) {
    return url;
  }
}

function deriveFacebookProfileUrl(rawValue) {
  if (typeof rawValue !== 'string') {
    return null;
  }

  const trimmed = rawValue.trim();
  if (!trimmed) {
    return null;
  }

  let parsed;
  try {
    parsed = new URL(trimmed);
  } catch (error) {
    try {
      parsed = new URL(trimmed, window.location.origin);
    } catch (innerError) {
      try {
        parsed = new URL(trimmed, 'https://www.facebook.com');
      } catch (fallbackError) {
        return null;
      }
    }
  }

  const hostname = parsed.hostname.toLowerCase();
  if (!hostname.endsWith('facebook.com')) {
    return null;
  }

  const origin = `${parsed.protocol}//${parsed.hostname}`;
  const normalizedPath = parsed.pathname.replace(/\/+$/, '') || '/';
  const segments = normalizedPath.split('/').filter(Boolean);
  const firstSegment = segments[0] || '';
  const lowerFirst = firstSegment.toLowerCase();
  const idParam = parsed.searchParams.get('id');

  if (lowerFirst === 'groups' && segments[1]) {
    return `${origin}/groups/${segments[1]}`;
  }

  if (lowerFirst === 'profile.php' && idParam) {
    return `${origin}/profile.php?id=${encodeURIComponent(idParam)}`;
  }

  if (idParam && (lowerFirst === 'story.php' || lowerFirst === 'permalink.php')) {
    return `${origin}/profile.php?id=${encodeURIComponent(idParam)}`;
  }

  if (lowerFirst === 'people' && segments[1] && segments[2]) {
    return `${origin}/people/${segments[1]}/${segments[2]}`;
  }

  if (firstSegment && !FACEBOOK_PROFILE_SEGMENT_BLOCKLIST.has(lowerFirst)) {
    return `${origin}/${firstSegment}`;
  }

  if (idParam) {
    return `${origin}/profile.php?id=${encodeURIComponent(idParam)}`;
  }

  return null;
}

function getProfileLinkForPost(post) {
  if (!post || typeof post !== 'object') {
    return null;
  }

  const candidates = [];

  if (typeof post.url === 'string') {
    candidates.push(post.url);
  }

  if (Array.isArray(post.alternate_urls)) {
    for (const alt of post.alternate_urls) {
      if (typeof alt === 'string') {
        candidates.push(alt);
      }
    }
  }

  for (const candidate of candidates) {
    const profileUrl = deriveFacebookProfileUrl(candidate);
    if (profileUrl) {
      return profileUrl;
    }
  }

  return null;
}

function toTimestamp(value, fallback = null) {
  if (!value) {
    return fallback;
  }

  const timestamp = value instanceof Date ? value.getTime() : new Date(value).getTime();
  if (Number.isNaN(timestamp)) {
    return fallback;
  }

  return timestamp;
}

function getSortTabKey(tab = currentTab) {
  if (tab === 'all') {
    return 'all';
  }
  return 'pending';
}

function normalizeSortMode(value) {
  if (VALID_SORT_MODES.has(value)) {
    return value;
  }
  return DEFAULT_SORT_SETTINGS.mode;
}

function normalizeSortDirection(value) {
  return value === 'asc' ? 'asc' : DEFAULT_SORT_SETTINGS.direction;
}

function getSortStorage() {
  try {
    const raw = localStorage.getItem(SORT_SETTINGS_KEY) || localStorage.getItem(SORT_SETTINGS_LEGACY_KEY);
    if (!raw) {
      return {};
    }
    const parsed = JSON.parse(raw);
    if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
      return parsed;
    }
  } catch (error) {
    console.warn('Konnte Sortierdaten nicht parsen:', error);
  }
  return {};
}

function persistSortStorage(storage) {
  try {
    localStorage.setItem(SORT_SETTINGS_KEY, JSON.stringify(storage));
    if (SORT_SETTINGS_KEY !== SORT_SETTINGS_LEGACY_KEY) {
      localStorage.removeItem(SORT_SETTINGS_LEGACY_KEY);
    }
  } catch (error) {
    console.warn('Konnte Sortierdaten nicht speichern:', error);
  }
}

function normalizeServerBookmark(entry) {
  if (!entry || typeof entry !== 'object') {
    return null;
  }

  const id = typeof entry.id === 'string' ? entry.id : null;
  const query = typeof entry.query === 'string' ? entry.query.trim() : '';
  const isDefault = entry.is_default === true || !query;
  if (!id || (!isDefault && !query)) {
    return null;
  }

  const label = typeof entry.label === 'string' && entry.label.trim()
    ? entry.label.trim()
    : (isDefault ? DEFAULT_BOOKMARK_LABEL : query);

  const normalizeDate = (value) => {
    if (!value) {
      return null;
    }
    const date = new Date(value);
    if (Number.isNaN(date.getTime())) {
      return null;
    }
    return date.toISOString();
  };

  return {
    id,
    label,
    query,
    created_at: normalizeDate(entry.created_at),
    updated_at: normalizeDate(entry.updated_at),
    last_clicked_at: normalizeDate(entry.last_clicked_at),
    deletable: entry.deletable === false ? false : !isDefault,
    isDefault
  };
}

function deduplicateBookmarks(list) {
  const seen = new Set();
  const deduped = [];

  list.forEach((bookmark) => {
    if (!bookmark || typeof bookmark.id !== 'string') {
      return;
    }
    const query = typeof bookmark.query === 'string' ? bookmark.query : '';
    const key = query.toLowerCase();
    if (seen.has(key)) {
      return;
    }
    seen.add(key);
    deduped.push(bookmark);
  });

  return deduped;
}

function sortBookmarksByRecency(list) {
  return [...list].sort((a, b) => {
    const aClick = a.last_clicked_at ? new Date(a.last_clicked_at).getTime() : -Infinity;
    const bClick = b.last_clicked_at ? new Date(b.last_clicked_at).getTime() : -Infinity;
    if (aClick !== bClick) {
      return bClick - aClick;
    }

    const aCreated = a.created_at ? new Date(a.created_at).getTime() : -Infinity;
    const bCreated = b.created_at ? new Date(b.created_at).getTime() : -Infinity;
    if (aCreated !== bCreated) {
      return bCreated - aCreated;
    }

    return a.label.localeCompare(b.label, 'de', { sensitivity: 'base' });
  });
}

function getBookmarkLabelForComparison(bookmark = {}) {
  const label = typeof bookmark.label === 'string' ? bookmark.label.trim() : '';
  const query = typeof bookmark.query === 'string' ? bookmark.query.trim() : '';
  return label || query || '';
}

function getBookmarkClickTimestamp(bookmark = {}) {
  if (bookmark.last_clicked_at) {
    const ts = new Date(bookmark.last_clicked_at).getTime();
    if (!Number.isNaN(ts)) {
      return ts;
    }
  }
  return 0;
}

function sortBookmarksForDisplay(list) {
  const items = [...list];
  if (bookmarkSortMode === 'label') {
    items.sort((a, b) => {
      const labelA = getBookmarkLabelForComparison(a);
      const labelB = getBookmarkLabelForComparison(b);
      const result = labelA.localeCompare(labelB, 'de', { sensitivity: 'base' });
      return bookmarkSortDirection === 'desc' ? -result : result;
    });
    return items;
  }

  items.sort((a, b) => {
    const diff = getBookmarkClickTimestamp(b) - getBookmarkClickTimestamp(a);
    if (diff !== 0) {
      return bookmarkSortDirection === 'desc' ? diff : -diff;
    }
    const fallback = getBookmarkLabelForComparison(a).localeCompare(
      getBookmarkLabelForComparison(b),
      'de',
      { sensitivity: 'base' }
    );
    return bookmarkSortDirection === 'desc' ? fallback : -fallback;
  });

  return items;
}

function filterBookmarksBySearch(list) {
  if (!bookmarkSearchTerm) {
    return [...list];
  }
  const term = bookmarkSearchTerm.toLowerCase();
  return list.filter((bookmark) => {
    const label = (bookmark.label || bookmark.query || '').toLowerCase();
    const query = (bookmark.query || '').toLowerCase();
    return label.includes(term) || query.includes(term);
  });
}

function updateBookmarkSortDirectionUI() {
  if (!bookmarkSortDirectionToggle) {
    return;
  }
  const isAsc = bookmarkSortDirection === 'asc';
  bookmarkSortDirectionToggle.setAttribute('aria-pressed', isAsc ? 'true' : 'false');
  bookmarkSortDirectionToggle.setAttribute('title', isAsc ? 'Älteste zuerst' : 'Neueste zuerst');
  const icon = bookmarkSortDirectionToggle.querySelector('.bookmark-sort__direction-icon');
  if (icon) {
    icon.textContent = isAsc ? '▲' : '▼';
  } else {
    bookmarkSortDirectionToggle.textContent = isAsc ? '▲' : '▼';
  }
}

const bookmarkState = {
  items: [],
  loaded: false,
  loading: false,
  error: null
};

let bookmarkFetchPromise = null;

function formatRelativeTimeFromNow(timestamp) {
  if (!timestamp) {
    return 'Noch nie geöffnet';
  }

  const date = new Date(timestamp);
  if (Number.isNaN(date.getTime())) {
    return 'Zuletzt: unbekannt';
  }

  const diffMs = Date.now() - date.getTime();
  if (diffMs < 0) {
    return 'gerade eben';
  }
  const diffSeconds = Math.floor(diffMs / 1000);
  if (diffSeconds < 45) {
    return 'vor wenigen Sekunden';
  }
  const diffMinutes = Math.floor(diffSeconds / 60);
  if (diffMinutes < 60) {
    return `vor ${diffMinutes} ${diffMinutes === 1 ? 'Minute' : 'Minuten'}`;
  }
  const diffHours = Math.floor(diffMinutes / 60);
  if (diffHours < 24) {
    return `vor ${diffHours} ${diffHours === 1 ? 'Stunde' : 'Stunden'}`;
  }
  const diffDays = Math.floor(diffHours / 24);
  if (diffDays < 31) {
    return `vor ${diffDays} ${diffDays === 1 ? 'Tag' : 'Tagen'}`;
  }
  const diffMonths = Math.floor(diffDays / 30);
  if (diffMonths < 12) {
    return `vor ${diffMonths} ${diffMonths === 1 ? 'Monat' : 'Monaten'}`;
  }
  const diffYears = Math.floor(diffMonths / 12);
  return `vor ${diffYears} ${diffYears === 1 ? 'Jahr' : 'Jahren'}`;
}

function upsertBookmarkInState(bookmark) {
  const normalized = normalizeServerBookmark(bookmark);
  if (!normalized) {
    return;
  }

  const lowerQuery = normalized.query.toLowerCase();
  const existingIndex = bookmarkState.items.findIndex((item) => {
    if (!item || typeof item.id !== 'string') {
      return false;
    }
    const itemQuery = typeof item.query === 'string' ? item.query.toLowerCase() : '';
    return item.id === normalized.id || itemQuery === lowerQuery;
  });

  if (existingIndex >= 0) {
    bookmarkState.items[existingIndex] = { ...bookmarkState.items[existingIndex], ...normalized };
  } else {
    bookmarkState.items.push(normalized);
  }

  bookmarkState.items = deduplicateBookmarks(sortBookmarksByRecency(bookmarkState.items));
}

function removeBookmarkFromState(bookmarkId) {
  if (!bookmarkId) {
    return;
  }
  bookmarkState.items = bookmarkState.items.filter((bookmark) => bookmark.id !== bookmarkId);
}

async function refreshBookmarks(options = {}) {
  const { force = false } = options;
  if (bookmarkFetchPromise && !force) {
    return bookmarkFetchPromise;
  }

  bookmarkFetchPromise = (async () => {
    bookmarkState.loading = true;
    if (!bookmarkState.loaded || force) {
      renderBookmarks();
    }

    try {
      const response = await apiFetch(`${API_URL}/bookmarks`);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }

      const data = await response.json();
      const normalized = Array.isArray(data)
        ? data.map(normalizeServerBookmark).filter(Boolean)
        : [];

      const finalList = deduplicateBookmarks(sortBookmarksByRecency(normalized));

      bookmarkState.items = finalList;
      bookmarkState.loaded = true;
      bookmarkState.loading = false;
      bookmarkState.error = null;
      renderBookmarks();

      return bookmarkState.items;
    } catch (error) {
      console.warn('Konnte Bookmarks nicht laden:', error);
      bookmarkState.error = 'Bookmarks konnten nicht geladen werden.';
      bookmarkState.loading = false;
      if (!bookmarkState.loaded) {
        bookmarkState.items = [];
      }
      renderBookmarks();
      throw error;
    } finally {
      bookmarkFetchPromise = null;
    }
  })();

  return bookmarkFetchPromise;
}

function formatFacebookDateParts(date) {
  const year = date.getFullYear();
  const month = date.getMonth() + 1;
  const day = date.getDate();
  const monthLabel = `${year}-${month}`;
  const dayLabel = `${year}-${month}-${day}`;
  return {
    year: String(year),
    monthLabel,
    dayLabel
  };
}

function buildBookmarkFiltersParam() {
  const now = new Date();
  const endYear = now.getFullYear();

  // start_month = Monat von (heute - BOOKMARK_WINDOW_DAYS), auf Monatsanfang (ohne Padding)
  const windowAgo = new Date();
  windowAgo.setDate(windowAgo.getDate() - BOOKMARK_WINDOW_DAYS);
  const startYear = windowAgo.getFullYear();
  const startMonthNum = windowAgo.getMonth() + 1; // 1..12
  const startMonthLabel = `${startYear}-${startMonthNum}`; // z.B. "2025-9"
  const startDayLabel   = `${startMonthLabel}-1`; // z.B. "2025-9-1"

  // Ende = Jahresende (ohne Padding), Jahre immer aktuelles Jahr als String
  const endMonthLabel = `${endYear}-12`;
  const endDayLabel   = `${endYear}-12-31`;

  // Reihenfolge wie gewünscht: top_tab zuerst, dann rp_creation_time
  const filtersPayload = {
    'top_tab_recent_posts:0': JSON.stringify({
      name: 'top_tab_recent_posts',
      args: ''
    }),
    'rp_creation_time:0': JSON.stringify({
      name: 'creation_time',
      args: JSON.stringify({
        start_year: String(startYear), // als String
        start_month: startMonthLabel,
        end_year: String(endYear), // als String
        end_month: endMonthLabel,
        start_day: startDayLabel,
        end_day: endDayLabel
      })
    })
  };

  const serialized = JSON.stringify(filtersPayload);

  // Rohes Base64 zurückgeben (kein encodeURIComponent!)
  if (typeof window !== 'undefined' && typeof window.btoa === 'function') {
    return window.btoa(serialized);
  } else if (typeof btoa === 'function') {
    return btoa(serialized);
  } else if (typeof Buffer !== 'undefined') {
    return Buffer.from(serialized, 'utf8').toString('base64');
  } else {
    console.warn('Base64 nicht verfügbar, gebe JSON zurück (wird von URLSearchParams encodet).');
    return serialized;
  }
}
/*
function buildBookmarkFiltersParam() {
 const y = String(new Date().getFullYear()); // "2025"

  // WICHTIG: Schlüssel-Reihenfolge wie im 'soll' → top_tab zuerst
  const filtersPayload = {
    'top_tab_recent_posts:0': JSON.stringify({
      name: 'top_tab_recent_posts',
      args: ''
    }),
    'rp_creation_time:0': JSON.stringify({
      name: 'creation_time',
      args: JSON.stringify({
        start_year: y,           // als String
        start_month: `${y}-1`,   // ohne Padding
        end_year: y,             // als String
        end_month: `${y}-12`,
        start_day: `${y}-1-1`,
        end_day: `${y}-12-31`
      })
    })
  };

  const serialized = JSON.stringify(filtersPayload);

  // Base64 OHNE URL-Encode zurückgeben
  if (typeof window !== 'undefined' && typeof window.btoa === 'function') {
    return window.btoa(serialized);
  } else if (typeof btoa === 'function') {
    return btoa(serialized);
  } else if (typeof Buffer !== 'undefined') {
    return Buffer.from(serialized, 'utf8').toString('base64');
  } else {
    console.warn('Base64 nicht verfügbar, gebe JSON zurück (wird von URLSearchParams encodet).');
    return serialized; // kein encodeURIComponent!
  }
}
*/
function buildBookmarkSearchUrl(query) {
  const trimmed = typeof query === 'string' ? query.trim() : '';
  if (!trimmed) {
    return null;
  }

  const searchUrl = new URL(BOOKMARKS_BASE_URL);
  searchUrl.searchParams.set('q', trimmed);
  searchUrl.searchParams.set('filters', buildBookmarkFiltersParam());
  return searchUrl.toString();
}

function splitBookmarkGroupOptions(rawGroup) {
  if (typeof rawGroup !== 'string') {
    return [];
  }

  return rawGroup
    .split(',')
    .map((entry) => entry.trim())
    .filter(Boolean);
}

function expandBookmarkQueryVariants(baseQuery) {
  const trimmed = typeof baseQuery === 'string' ? baseQuery.trim() : '';
  if (!trimmed) {
    return [];
  }

  const variants = [''];
  let cursor = 0;

  while (cursor < trimmed.length) {
    const openIndex = trimmed.indexOf('(', cursor);
    if (openIndex === -1) {
      const tail = trimmed.slice(cursor);
      for (let i = 0; i < variants.length; i += 1) {
        variants[i] = `${variants[i]}${tail}`;
      }
      break;
    }

    const closeIndex = trimmed.indexOf(')', openIndex + 1);
    if (closeIndex === -1) {
      const tail = trimmed.slice(cursor);
      for (let i = 0; i < variants.length; i += 1) {
        variants[i] = `${variants[i]}${tail}`;
      }
      break;
    }

    const before = trimmed.slice(cursor, openIndex);
    if (before) {
      for (let i = 0; i < variants.length; i += 1) {
        variants[i] = `${variants[i]}${before}`;
      }
    }

    const groupRaw = trimmed.slice(openIndex + 1, closeIndex);
    const options = splitBookmarkGroupOptions(groupRaw);

    if (options.length) {
      const expanded = [];
      variants.forEach((prefix) => {
        options.forEach((option) => {
          expanded.push(`${prefix}${option}`);
        });
      });
      variants.splice(0, variants.length, ...expanded);
    } else {
      const literalGroup = trimmed.slice(openIndex, closeIndex + 1);
      for (let i = 0; i < variants.length; i += 1) {
        variants[i] = `${variants[i]}${literalGroup}`;
      }
    }

    cursor = closeIndex + 1;
  }

  return variants
    .map((variant) => variant.trim())
    .filter(Boolean);
}

function buildBookmarkSearchQueries(baseQuery) {
  const trimmed = typeof baseQuery === 'string' ? baseQuery.trim() : '';
  if (!trimmed) {
    return [...BOOKMARK_SUFFIXES];
  }

  const baseVariants = expandBookmarkQueryVariants(trimmed);
  if (!baseVariants.length) {
    return BOOKMARK_SUFFIXES.map((suffix) => `${trimmed} ${suffix}`.trim());
  }

  const queries = [];
  baseVariants.forEach((variant) => {
    BOOKMARK_SUFFIXES.forEach((suffix) => {
      queries.push(`${variant} ${suffix}`.trim());
    });
  });

  return [...new Set(queries)];
}

function openBookmarkQueries(baseQuery) {
  const queries = Array.isArray(baseQuery) ? baseQuery : buildBookmarkSearchQueries(baseQuery);
  let opened = 0;

  queries.forEach((searchTerm) => {
    const url = buildBookmarkSearchUrl(searchTerm);
    if (url) {
      window.open(url, '_blank', 'noopener');
      opened += 1;
    }
  });

  return opened;
}

function openBookmark(bookmark) {
  if (!bookmark) {
    return;
  }

  const queries = buildBookmarkSearchQueries(bookmark.query);
  if (!queries.length) {
    queries.push('');
  }

  const stateBookmark = bookmarkState.items.find((item) => item.id === bookmark.id) || bookmark;
  const nowIso = new Date().toISOString();

  if (stateBookmark && stateBookmark.id) {
    upsertBookmarkInState({
      id: stateBookmark.id,
      label: stateBookmark.label,
      query: stateBookmark.query,
      last_clicked_at: nowIso,
      created_at: stateBookmark.created_at || nowIso,
      updated_at: nowIso,
      deletable: stateBookmark.deletable !== false,
      is_default: stateBookmark.isDefault === true
    });
    renderBookmarks();
    markBookmarkClick(stateBookmark.id);
  }

  openBookmarkQueries(queries);
}

async function markBookmarkClick(bookmarkId) {
  if (!bookmarkId) {
    return;
  }

  try {
    const response = await apiFetch(`${API_URL}/bookmarks/${encodeURIComponent(bookmarkId)}/click`, {
      method: 'POST'
    });

    if (!response.ok) {
      return;
    }

    const updated = await response.json();
    upsertBookmarkInState(updated);
    renderBookmarks();
  } catch (error) {
    console.warn('Konnte Bookmark-Klick nicht speichern:', error);
  }
}

async function removeBookmark(bookmarkId) {
  if (!bookmarkId) {
    return;
  }

  try {
    const response = await apiFetch(`${API_URL}/bookmarks/${encodeURIComponent(bookmarkId)}`, {
      method: 'DELETE'
    });

    if (response.ok || response.status === 204 || response.status === 404) {
      removeBookmarkFromState(bookmarkId);
      bookmarkState.error = null;
      renderBookmarks();
      return;
    }

    throw new Error(`HTTP ${response.status}`);
  } catch (error) {
    console.warn('Konnte Bookmark nicht löschen:', error);
    bookmarkState.error = 'Bookmark konnte nicht gelöscht werden.';
    renderBookmarks();
  }
}

function createBookmarkRow(bookmark) {
  const row = document.createElement('div');
  row.className = 'bookmark-row';
  row.dataset.query = bookmark.query || '';

  if (!bookmark.last_clicked_at) {
    row.dataset.state = 'never-used';
  }
  if (bookmark.isDefault) {
    row.dataset.default = '1';
  }

  const openButton = document.createElement('button');
  openButton.type = 'button';
  openButton.className = 'bookmark-row__open';

  const searchVariants = buildBookmarkSearchQueries(bookmark.query);
  if (searchVariants.length) {
    openButton.title = searchVariants.map((variant) => `• ${variant}`).join('\n');
  }

  openButton.addEventListener('click', () => openBookmark(bookmark));

  const label = document.createElement('span');
  label.className = 'bookmark-row__label';
  label.textContent = bookmark.label || bookmark.query || 'Bookmark';
  openButton.appendChild(label);

  const query = document.createElement('span');
  query.className = 'bookmark-row__query';
  query.textContent = bookmark.query ? `„${bookmark.query}“` : 'Standard-Keywords';
  openButton.appendChild(query);

  row.appendChild(openButton);

  const meta = document.createElement('span');
  meta.className = 'bookmark-row__meta';
  meta.textContent = formatRelativeTimeFromNow(bookmark.last_clicked_at);
  if (bookmark.last_clicked_at) {
    const date = new Date(bookmark.last_clicked_at);
    if (!Number.isNaN(date.getTime())) {
      meta.title = date.toLocaleString();
    }
  }
  row.appendChild(meta);

  if (bookmark.deletable !== false) {
    const removeBtn = document.createElement('button');
    removeBtn.type = 'button';
    removeBtn.className = 'bookmark-row__remove';
    removeBtn.setAttribute('aria-label', `${bookmark.label || bookmark.query} entfernen`);
    removeBtn.textContent = '×';
    removeBtn.addEventListener('click', (event) => {
      event.preventDefault();
      event.stopPropagation();
      removeBookmark(bookmark.id);
    });
    row.appendChild(removeBtn);
  }

  return row;
}

function renderBookmarks() {
  if (!bookmarksList) {
    return;
  }

  bookmarksList.innerHTML = '';

  if (bookmarkState.loading && !bookmarkState.loaded) {
    const loading = document.createElement('div');
    loading.className = 'bookmark-status bookmark-status--loading';
    loading.textContent = 'Lade Bookmarks...';
    bookmarksList.appendChild(loading);
    return;
  }

  if (bookmarkState.error && !bookmarkState.loaded) {
    const errorNode = document.createElement('div');
    errorNode.className = 'bookmark-status bookmark-status--error';
    errorNode.textContent = bookmarkState.error;
    bookmarksList.appendChild(errorNode);
    return;
  }

  if (bookmarkState.error && bookmarkState.loaded) {
    const warnNode = document.createElement('div');
    warnNode.className = 'bookmark-status bookmark-status--error';
    warnNode.textContent = bookmarkState.error;
    bookmarksList.appendChild(warnNode);
  }

  const dynamicBookmarks = bookmarkState.items;
  const filteredBookmarks = filterBookmarksBySearch(dynamicBookmarks);
  const sortedForAll = sortBookmarksForDisplay(filteredBookmarks);
  const defaultBookmark = sortedForAll.find((bookmark) => bookmark.isDefault);
  const nonDefaultBookmarks = sortedForAll.filter((bookmark) => !bookmark.isDefault);
  const displayList = bookmarkSearchTerm
    ? sortedForAll
    : (defaultBookmark ? [defaultBookmark, ...nonDefaultBookmarks] : nonDefaultBookmarks);
  const titleText = bookmarkSearchTerm
    ? (filteredBookmarks.length ? `Suchergebnisse (${filteredBookmarks.length})` : 'Keine Treffer')
    : 'Alle Bookmarks';

  if (!displayList.length) {
    const empty = document.createElement('div');
    empty.className = 'bookmark-empty';
    if (bookmarkSearchTerm) {
      empty.textContent = `Keine Bookmarks gefunden für „${bookmarkSearchTerm}“.`;
    } else {
      empty.textContent = 'Noch keine Bookmarks gespeichert.';
    }
    bookmarksList.appendChild(empty);
    return;
  }

  const sectionElement = document.createElement('section');
  sectionElement.className = 'bookmark-section';
  sectionElement.dataset.section = bookmarkSearchTerm ? 'search' : 'all';

  const headerEl = document.createElement('header');
  headerEl.className = 'bookmark-section__header';
  const titleEl = document.createElement('h3');
  titleEl.className = 'bookmark-section__title';
  titleEl.textContent = titleText;
  headerEl.appendChild(titleEl);
  sectionElement.appendChild(headerEl);

  const list = document.createElement('div');
  list.className = 'bookmark-section__list';
  displayList.forEach((bookmark) => {
    list.appendChild(createBookmarkRow(bookmark));
  });

  sectionElement.appendChild(list);
  bookmarksList.appendChild(sectionElement);
}

function resetBookmarkForm() {
  if (!bookmarkForm) {
    return;
  }

  bookmarkForm.reset();
  if (bookmarkNameInput) {
    bookmarkNameInput.value = '';
  }
  if (bookmarkQueryInput) {
    bookmarkQueryInput.value = '';
  }
}

function ensureBookmarkOutsideHandler() {
  if (bookmarkOutsideHandler) {
    return bookmarkOutsideHandler;
  }

  bookmarkOutsideHandler = (event) => {
    if (!bookmarkPanelVisible) {
      return;
    }

    const target = event.target;
    const insidePanel = bookmarkPanel && bookmarkPanel.contains(target);
    const onToggle = bookmarkPanelToggle && bookmarkPanelToggle.contains(target);
    if (!insidePanel && !onToggle) {
      toggleBookmarkPanel(false);
    }
  };

  return bookmarkOutsideHandler;
}

function removeBookmarkOutsideHandler() {
  if (!bookmarkOutsideHandler) {
    return;
  }
  document.removeEventListener('mousedown', bookmarkOutsideHandler, true);
  document.removeEventListener('focusin', bookmarkOutsideHandler);
}

function toggleBookmarkPanel(forceVisible) {
  if (!bookmarkPanel || !bookmarkPanelToggle) {
    return;
  }

  const shouldShow = typeof forceVisible === 'boolean' ? forceVisible : !bookmarkPanelVisible;
  if (shouldShow === bookmarkPanelVisible) {
    return;
  }

  bookmarkPanelVisible = shouldShow;
  bookmarkPanel.hidden = !bookmarkPanelVisible;
  bookmarkPanel.style.display = bookmarkPanelVisible ? 'flex' : 'none';
  bookmarkPanel.setAttribute('aria-hidden', bookmarkPanelVisible ? 'false' : 'true');
  bookmarkPanelToggle.setAttribute('aria-expanded', bookmarkPanelVisible ? 'true' : 'false');

  if (bookmarkPanelVisible) {
    if (!bookmarkState.loaded && !bookmarkState.loading) {
      bookmarkState.loading = true;
      renderBookmarks();
      refreshBookmarks().catch(() => {});
    } else {
      renderBookmarks();
    }
    resetBookmarkForm();
    if (bookmarkQueryInput) {
      window.requestAnimationFrame(() => {
        bookmarkQueryInput.focus();
      });
    }
    const handler = ensureBookmarkOutsideHandler();
    window.requestAnimationFrame(() => {
      document.addEventListener('mousedown', handler, true);
      document.addEventListener('focusin', handler);
    });
  } else {
    resetBookmarkForm();
    removeBookmarkOutsideHandler();
    bookmarkState.error = null;
    if (bookmarkPanelToggle) {
      bookmarkPanelToggle.focus();
    }
  }
}

async function handleBookmarkSubmit(event) {
  event.preventDefault();

  if (!bookmarkForm) {
    return;
  }

  const query = bookmarkQueryInput ? bookmarkQueryInput.value.trim() : '';
  const name = bookmarkNameInput ? bookmarkNameInput.value.trim() : '';

  if (!query) {
    resetBookmarkForm();
    if (bookmarkQueryInput) {
      bookmarkQueryInput.focus();
    }
    return;
  }

  bookmarkState.error = null;

  try {
    const response = await apiFetch(`${API_URL}/bookmarks`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        label: name,
        query
      })
    });

    if (response.status === 409) {
      bookmarkState.error = 'Bookmark existiert bereits.';
      renderBookmarks();
      resetBookmarkForm();
      if (bookmarkQueryInput) {
        bookmarkQueryInput.focus();
      }
      return;
    }

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }

    const created = await response.json();
    upsertBookmarkInState(created);
    bookmarkState.error = null;
    renderBookmarks();
    resetBookmarkForm();
  } catch (error) {
    console.warn('Konnte Bookmark nicht speichern:', error);
    bookmarkState.error = 'Bookmark konnte nicht gespeichert werden.';
    renderBookmarks();
  }
}

function setBookmarkQuickStatus(message, isError = false) {
  if (!bookmarkQuickStatus) {
    return;
  }

  const hasMessage = typeof message === 'string' && message.trim();
  bookmarkQuickStatus.hidden = !hasMessage;
  bookmarkQuickStatus.textContent = hasMessage ? message : '';
  bookmarkQuickStatus.classList.toggle('bookmark-status--error', !!isError);
}

function handleBookmarkQuickSubmit(event) {
  event.preventDefault();

  if (!bookmarkQuickForm) {
    return;
  }

  const query = bookmarkQuickQueryInput ? bookmarkQuickQueryInput.value.trim() : '';
  if (!query) {
    setBookmarkQuickStatus('Bitte gib einen Suchbegriff ein.', true);
    if (bookmarkQuickQueryInput) {
      bookmarkQuickQueryInput.focus();
    }
    return;
  }

  const opened = openBookmarkQueries(query);
  if (opened > 0) {
    setBookmarkQuickStatus(`Suche „${query}“ geöffnet (ohne Speicherung).`);
    if (bookmarkQuickQueryInput) {
      bookmarkQuickQueryInput.value = '';
      bookmarkQuickQueryInput.focus();
    }
  } else {
    setBookmarkQuickStatus('Konnte die Suche nicht öffnen.', true);
  }
}

function initializeBookmarks() {
  if (!bookmarksList) {
    return;
  }

  refreshBookmarks().catch(() => {});

  if (bookmarkPanel) {
    bookmarkPanel.setAttribute('aria-hidden', 'true');
    bookmarkPanel.hidden = true;
    bookmarkPanel.style.display = 'none';
  }

  if (bookmarkPanelToggle) {
    bookmarkPanelToggle.addEventListener('click', () => {
      toggleBookmarkPanel();
    });
  }

  if (bookmarkPanelClose) {
    bookmarkPanelClose.addEventListener('click', () => {
      toggleBookmarkPanel(false);
    });
  }

  if (bookmarkCancelBtn) {
    bookmarkCancelBtn.addEventListener('click', () => {
      resetBookmarkForm();
      if (bookmarkQueryInput) {
        bookmarkQueryInput.focus();
      }
    });
  }

  if (bookmarkForm) {
    bookmarkForm.addEventListener('submit', handleBookmarkSubmit);
  }

  if (bookmarkQuickForm) {
    bookmarkQuickForm.addEventListener('submit', handleBookmarkQuickSubmit);
  }

  if (bookmarkQuickQueryInput) {
    bookmarkQuickQueryInput.addEventListener('input', () => {
      setBookmarkQuickStatus('');
    });
  }

  if (bookmarkSearchInput) {
    bookmarkSearchInput.value = bookmarkSearchTerm;
    bookmarkSearchInput.addEventListener('input', () => {
      bookmarkSearchTerm = typeof bookmarkSearchInput.value === 'string'
        ? bookmarkSearchInput.value.trim()
        : '';
      persistBookmarkPreferences();
      renderBookmarks();
    });
  }

  if (bookmarkSortSelect) {
    bookmarkSortSelect.value = bookmarkSortMode;
    bookmarkSortSelect.addEventListener('change', () => {
      const value = bookmarkSortSelect.value;
      if (value === 'label' || value === 'recent') {
        bookmarkSortMode = value;
        persistBookmarkPreferences();
        renderBookmarks();
      }
    });
  }

  if (bookmarkSortDirectionToggle) {
    bookmarkSortDirectionToggle.addEventListener('click', () => {
      bookmarkSortDirection = bookmarkSortDirection === 'desc' ? 'asc' : 'desc';
      updateBookmarkSortDirectionUI();
      persistBookmarkPreferences();
      renderBookmarks();
    });
    updateBookmarkSortDirectionUI();
  }
}

function getSortSettingsPageKey() {
  try {
    const path = window.location.pathname;
    if (typeof path === 'string' && path) {
      return path;
    }
  } catch (error) {
    // ignore and fall back
  }
  return 'default';
}

function updateSortDirectionToggleUI() {
  if (!sortDirectionToggle) {
    return;
  }

  const isAsc = sortDirection === 'asc';
  sortDirectionToggle.setAttribute('aria-pressed', isAsc ? 'true' : 'false');
  sortDirectionToggle.setAttribute('aria-label', isAsc ? 'Aufsteigend' : 'Absteigend');
  sortDirectionToggle.title = isAsc ? 'Aufsteigend' : 'Absteigend';
  sortDirectionToggle.dataset.direction = sortDirection;

  const icon = sortDirectionToggle.querySelector('.sort-direction-toggle__icon');
  if (icon) {
    icon.textContent = isAsc ? '▲' : '▼';
  } else {
    sortDirectionToggle.textContent = isAsc ? '▲' : '▼';
  }
}

function getDefaultSortDirectionForMode(mode) {
  if (mode === 'deadline') {
    return 'asc';
  }
  if (mode === 'smart') {
    return 'desc';
  }
  return null;
}

function normalizeRequiredProfiles(post) {
  if (Array.isArray(post.required_profiles) && post.required_profiles.length) {
    return post.required_profiles
      .map((value) => {
        const parsed = parseInt(value, 10);
        if (Number.isNaN(parsed)) {
          return null;
        }
        return Math.min(MAX_PROFILES, Math.max(1, parsed));
      })
      .filter(Boolean);
  }

  const parsedTarget = parseInt(post.target_count, 10);
  const count = Number.isNaN(parsedTarget)
    ? 1
    : Math.min(MAX_PROFILES, Math.max(1, parsedTarget));

  return Array.from({ length: count }, (_, index) => index + 1);
}

function getTabButtons() {
  return Array.from(document.querySelectorAll('.tab-btn[data-tab]'));
}

function updateTabCountLabels(counts = {}) {
  getTabButtons().forEach((button) => {
    const tabKey = button.dataset.tab === 'all' ? 'all' : 'pending';
    const fallbackLabel = TAB_LABELS[tabKey] || button.textContent.trim();
    if (!button.dataset.baseLabel) {
      button.dataset.baseLabel = fallbackLabel;
    }

    const incomingCount = counts[tabKey];
    if (Number.isFinite(incomingCount)) {
      tabDisplayCounts[tabKey] = Math.max(0, incomingCount);
    }

    const count = tabDisplayCounts[tabKey];
    const label = button.dataset.baseLabel || fallbackLabel;
    button.textContent = `${label} (${count})`;
    button.setAttribute('aria-label', `${label} (${count})`);
  });
}

function updateTabButtons() {
  updateTabCountLabels();
  getTabButtons().forEach((button) => {
    const isActive = button.dataset.tab === currentTab;
    button.classList.toggle('active', isActive);
    button.setAttribute('aria-selected', isActive ? 'true' : 'false');
    button.setAttribute('tabindex', isActive ? '0' : '-1');
  });
}

function handleTabKeydown(event) {
  if (!event || (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight' && event.key !== 'Home' && event.key !== 'End')) {
    return;
  }

  const tabs = getTabButtons();
  if (!tabs.length) {
    return;
  }

  const currentIndex = tabs.findIndex((tab) => tab === event.currentTarget);
  if (currentIndex === -1) {
    return;
  }

  event.preventDefault();
  let nextIndex = currentIndex;

  if (event.key === 'Home') {
    nextIndex = 0;
  } else if (event.key === 'End') {
    nextIndex = tabs.length - 1;
  } else if (event.key === 'ArrowRight') {
    nextIndex = (currentIndex + 1) % tabs.length;
  } else if (event.key === 'ArrowLeft') {
    nextIndex = (currentIndex - 1 + tabs.length) % tabs.length;
  }

  const nextTab = tabs[nextIndex];
  if (!nextTab || !nextTab.dataset.tab) {
    return;
  }

  setTab(nextTab.dataset.tab, { updateUrl: true, announceChange: true });
  nextTab.focus();
}

function updateSearchClearButtonVisibility() {
  if (!searchInput || !searchClearBtn) {
    return;
  }
  const hasValue = typeof searchInput.value === 'string' && searchInput.value.trim().length > 0;
  searchClearBtn.hidden = !hasValue;
  searchClearBtn.disabled = !hasValue;
  searchClearBtn.setAttribute('aria-hidden', hasValue ? 'false' : 'true');
}

function getSortModeLabel(mode = sortMode) {
  return SORT_MODE_LABELS[mode] || SORT_MODE_LABELS.created;
}

function getSortDirectionLabel(direction = sortDirection) {
  return direction === 'asc' ? 'aufsteigend' : 'absteigend';
}

function isPostsViewActive() {
  const postsView = document.querySelector('[data-view="posts"]');
  return Boolean(postsView && postsView.classList.contains('app-view--active'));
}

function prefersReducedMotion() {
  try {
    return Boolean(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
  } catch (_error) {
    return false;
  }
}

function getScrollBehavior() {
  return prefersReducedMotion() ? 'auto' : 'smooth';
}

function updatePostsScrollTopButtonVisibility() {
  if (!postsScrollTopBtn) {
    return;
  }
  const shouldShow = isPostsViewActive() && window.scrollY > 520;
  postsScrollTopBtn.hidden = !shouldShow;
  postsScrollTopBtn.setAttribute('aria-hidden', shouldShow ? 'false' : 'true');
}

function updateTabInUrl() {
  if (!isPostsViewActive()) {
    return;
  }

  const url = new URL(window.location.href);
  if (currentTab === 'pending') {
    url.searchParams.set('tab', 'pending');
  } else {
    url.searchParams.set('tab', 'all');
  }
  const nextState = { ...(window.history.state || {}), view: 'posts' };
  window.history.replaceState(nextState, document.title, `${url.pathname}?${url.searchParams.toString()}${url.hash}`);
}

function getTabKey(tab = currentTab) {
  if (tab === 'all') {
    return 'all';
  }
  return 'pending';
}

function getVisibleCount(tab = currentTab) {
  const key = getTabKey(tab);
  const value = tabVisibleCounts[key];
  const normalized = Number.isFinite(value) ? Math.max(0, Math.floor(value)) : INITIAL_POST_LIMIT;
  return Math.max(INITIAL_POST_LIMIT, normalized);
}

function setVisibleCount(tab, count) {
  const key = getTabKey(tab);
  const normalized = Number.isFinite(count) ? Math.max(0, Math.floor(count)) : INITIAL_POST_LIMIT;
  tabVisibleCounts[key] = Math.max(INITIAL_POST_LIMIT, normalized);
}

function resetVisibleCount(tab = currentTab) {
  setVisibleCount(tab, INITIAL_POST_LIMIT);
}

function updateFilteredCount(tab, count) {
  const key = getTabKey(tab);
  tabFilteredCounts[key] = Math.max(0, count || 0);
}

function getPostListState() {
  const postItems = posts.map((post) => ({
    post,
    status: computePostStatus(post)
  }));

  const sortedItems = [...postItems].sort(comparePostItems);
  let filteredItems = sortedItems;

  if (currentTab === 'pending') {
    filteredItems = sortedItems.filter((item) => !item.status.isExpired && item.status.canCurrentProfileCheck && !item.status.isComplete);
  } else {
    filteredItems = includeExpiredPosts
      ? sortedItems
      : sortedItems.filter((item) => !item.status.isExpired && !item.status.isComplete);
  }

  const tabTotalCount = filteredItems.length;

  const searchValueRaw = searchInput && typeof searchInput.value === 'string'
    ? searchInput.value
    : postsSearchTerm;
  const searchValue = typeof searchValueRaw === 'string' ? searchValueRaw.trim() : '';
  const searchActive = Boolean(searchValue);

  if (searchActive) {
    const searchTerm = searchValue.toLowerCase();
    filteredItems = filteredItems.filter((item) => {
      const post = item.post;
      return (
        (post.title && post.title.toLowerCase().includes(searchTerm)) ||
        (post.url && post.url.toLowerCase().includes(searchTerm)) ||
        (post.created_by_name && post.created_by_name.toLowerCase().includes(searchTerm)) ||
        (post.id && post.id.toLowerCase().includes(searchTerm))
      );
    });
  }

  return {
    sortedItems,
    filteredItems,
    tabTotalCount,
    searchActive,
    searchValue
  };
}

function clearPendingAutoOpenCountdown() {
  if (pendingAutoOpenCountdownIntervalId) {
    clearInterval(pendingAutoOpenCountdownIntervalId);
    pendingAutoOpenCountdownIntervalId = null;
  }
}

function updatePendingAutoOpenCountdownLabel(remainingMs) {
  if (!pendingAutoOpenCountdown) {
    return;
  }
  const safeMs = Math.max(0, remainingMs);
  const seconds = safeMs / 1000;
  const formatted = seconds >= 10 ? seconds.toFixed(0) : seconds.toFixed(1);
  pendingAutoOpenCountdown.textContent = formatted;
}

function hidePendingAutoOpenOverlay() {
  clearPendingAutoOpenCountdown();
  if (pendingAutoOpenOverlay) {
    pendingAutoOpenOverlay.classList.remove('visible');
    pendingAutoOpenOverlay.hidden = true;
  }
}

function showPendingAutoOpenOverlay(delayMs) {
  if (!pendingAutoOpenOverlay) {
    return;
  }
  const duration = Math.max(0, delayMs);
  hidePendingAutoOpenOverlay();
  pendingAutoOpenOverlay.hidden = false;
  requestAnimationFrame(() => pendingAutoOpenOverlay.classList.add('visible'));
  updatePendingAutoOpenCountdownLabel(duration);
  const start = Date.now();
  pendingAutoOpenCountdownIntervalId = setInterval(() => {
    const remaining = Math.max(0, duration - (Date.now() - start));
    updatePendingAutoOpenCountdownLabel(remaining);
    if (remaining <= 0) {
      clearPendingAutoOpenCountdown();
    }
  }, 100);
}

function cancelPendingAutoOpen(showMessage = false) {
  if (pendingAutoOpenTimerId) {
    clearTimeout(pendingAutoOpenTimerId);
    pendingAutoOpenTimerId = null;
  }
  pendingAutoOpenTriggered = false;
  hidePendingAutoOpenOverlay();
  if (showMessage) {
    setPendingBulkStatus('Automatisches Öffnen abgebrochen.', false);
  }
}

function getPendingCandidates({ includeHidden = false } = {}) {
  if (currentTab !== 'pending') {
    return { items: [], totalConsidered: 0, cooldownBlocked: 0 };
  }
  const { filteredItems } = getPostListState();
  const visibleCount = Math.min(filteredItems.length, getVisibleCount(currentTab));
  const consideredItems = (includeHidden ? filteredItems : filteredItems.slice(0, visibleCount))
    .filter(({ post }) => post && post.url);
  const items = consideredItems.filter(({ post }) => !isPendingOpenCooldownActive(post.id));
  const cooldownBlocked = Math.max(0, consideredItems.length - items.length);
  return { items, totalConsidered: consideredItems.length, cooldownBlocked };
}

function openPendingBatch({ auto = false } = {}) {
  if (pendingProcessingBatch) {
    return;
  }
  if (!auto) {
    cancelPendingAutoOpen(false);
  }
  const includeHidden = true;
  const { items: candidates, totalConsidered, cooldownBlocked } = getPendingCandidates({ includeHidden });
  if (!candidates.length) {
    if (!auto) {
      if (totalConsidered === 0) {
        setPendingBulkStatus('Keine offenen Beiträge zum Öffnen.', true);
      } else if (cooldownBlocked > 0) {
        setPendingBulkStatus('Alle Beiträge sind noch im Cooldown (40 min).', true);
      } else {
        setPendingBulkStatus('Keine offenen Beiträge zum Öffnen.', true);
      }
    }
    pendingAutoOpenTriggered = false;
    return;
  }

  const count = pendingBulkCount || DEFAULT_PENDING_BULK_COUNT;
  const selection = candidates.slice(0, count);

  pendingProcessingBatch = true;
  if (pendingBulkOpenBtn) {
    pendingBulkOpenBtn.disabled = true;
  }
  if (!auto) {
    setPendingBulkStatus('');
  } else {
    setPendingBulkStatus(`Öffne automatisch ${selection.length} Links...`, false);
  }

  selection.forEach(({ post }) => {
    if (post && post.url) {
      window.open(post.url, '_blank', 'noopener');
      recordPendingOpen(post.id);
    }
  });

  pendingProcessingBatch = false;
  if (pendingBulkOpenBtn) {
    pendingBulkOpenBtn.disabled = false;
  }
  if (auto) {
    setPendingBulkStatus('');
    pendingAutoOpenTriggered = false;
  }
}

function maybeAutoOpenPending(reason = '', delayMs = PENDING_AUTO_OPEN_DELAY_MS) {
  if (!isPostsViewActive()) {
    hidePendingAutoOpenOverlay();
    return;
  }
  if (currentTab !== 'pending') {
    hidePendingAutoOpenOverlay();
    return;
  }
  if (!pendingAutoOpenEnabled) {
    hidePendingAutoOpenOverlay();
    return;
  }
  if (pendingProcessingBatch) {
    return;
  }
  if (pendingAutoOpenTriggered) {
    return;
  }
  const { items: candidates } = getPendingCandidates({ includeHidden: true });
  if (!candidates.length) {
    hidePendingAutoOpenOverlay();
    return;
  }
  if (pendingAutoOpenTimerId) {
    clearTimeout(pendingAutoOpenTimerId);
    pendingAutoOpenTimerId = null;
  }
  hidePendingAutoOpenOverlay();
  pendingAutoOpenTriggered = true;
  const delay = typeof delayMs === 'number' ? Math.max(0, delayMs) : PENDING_AUTO_OPEN_DELAY_MS;
  if (delay === 0) {
    if (pendingAutoOpenEnabled) {
      openPendingBatch({ auto: true });
    } else {
      pendingAutoOpenTriggered = false;
    }
    return;
  }
  showPendingAutoOpenOverlay(delay);
  pendingAutoOpenTimerId = setTimeout(() => {
    pendingAutoOpenTimerId = null;
    hidePendingAutoOpenOverlay();
    if (pendingAutoOpenEnabled) {
      openPendingBatch({ auto: true });
    } else {
      pendingAutoOpenTriggered = false;
    }
  }, delay);
}

function cleanupLoadMoreObserver() {
  if (loadMoreObserver && observedLoadMoreElement) {
    loadMoreObserver.unobserve(observedLoadMoreElement);
    observedLoadMoreElement = null;
  }
}

function getTabDisplayLabel(tab = currentTab) {
  if (tab === 'all') {
    return TAB_LABELS.all;
  }
  return TAB_LABELS.pending;
}

function buildPostsSummary({
  tab,
  visibleCount,
  filteredCount,
  tabTotalCount,
  totalCountAll,
  searchActive
}) {
  const label = getTabDisplayLabel(tab);
  const segments = [];
  segments.push(`<span class="posts-summary__item">Angezeigt: <strong>${visibleCount}</strong> von ${filteredCount}</span>`);

  if (searchActive) {
    segments.push(`<span class="posts-summary__item">Treffer: <strong>${filteredCount}</strong> von ${tabTotalCount}</span>`);
  }

  segments.push(`<span class="posts-summary__item">Tab gesamt: <strong>${tabTotalCount}</strong></span>`);
  segments.push(`<span class="posts-summary__item">Alle Beiträge: <strong>${totalCountAll}</strong></span>`);
  segments.push(`<span class="posts-summary__item">Sortierung: <strong>${getSortModeLabel()} · ${getSortDirectionLabel()}</strong></span>`);

  return `
    <div class="posts-summary" role="status" aria-live="polite">
      <span class="posts-summary__label">${label}</span>
      ${segments.join('<span class="posts-summary__separator">·</span>')}
    </div>
  `;
}

function ensureLoadMoreObserver() {
  if (loadMoreObserver) {
    return loadMoreObserver;
  }

  loadMoreObserver = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
      if (!entry.isIntersecting) {
        return;
      }

      const element = entry.target;
      const tab = element && element.dataset ? element.dataset.tab : currentTab;

      if (loadMoreObserver) {
        loadMoreObserver.unobserve(element);
      }

      loadMorePosts(tab, { triggeredByScroll: true });
    });
  }, {
    root: null,
    rootMargin: '200px 0px',
    threshold: 0.1
  });

  return loadMoreObserver;
}

function observeLoadMoreElement(element, tab) {
  if (!element) {
    return;
  }
  const observer = ensureLoadMoreObserver();
  observedLoadMoreElement = element;
  element.dataset.tab = getTabKey(tab);
  observer.observe(element);
}

function loadMorePosts(tab = currentTab, { triggeredByScroll = false } = {}) {
  const key = getTabKey(tab);
  const total = tabFilteredCounts[key] || 0;
  const currentLimit = getVisibleCount(tab);

  if (currentLimit >= total) {
    return;
  }

  const newLimit = Math.min(total, currentLimit + POST_LOAD_INCREMENT);
  setVisibleCount(tab, newLimit);

  if (tab === currentTab) {
    renderPosts();
  }
}

function setTab(tab, { updateUrl = true, announceChange = false } = {}) {
  const previousTab = currentTab;
  if (tab === 'all') {
    currentTab = 'all';
  } else {
    currentTab = 'pending';
  }
  if (currentTab !== 'all' && mergeMode) {
    mergeMode = false;
    resetMergeSelection();
  }
  updateTabButtons();
  loadSortMode({ fromTabChange: true });
  updateMergeControlsUI();
  if (updateUrl) {
    updateTabInUrl();
  }
  renderPosts();
  maybeAutoOpenPending('tab');
  if (announceChange && previousTab !== currentTab) {
    showToast(`Tab: ${getTabDisplayLabel(currentTab)}`, 'info');
  }
}

function initializeTabFromUrl() {
  let tabResolved = false;
  try {
    const params = new URLSearchParams(window.location.search);
    const tabParam = params.get('tab');
    if (tabParam === 'all' || tabParam === 'pending') {
      currentTab = tabParam;
      tabResolved = true;
    } else if (initialTabOverride) {
      currentTab = initialTabOverride;
      tabResolved = true;
    } else if (initialViewParam === 'dashboard') {
      currentTab = 'pending';
      tabResolved = true;
    }
  } catch (error) {
    console.warn('Konnte Tab-Parameter nicht auslesen:', error);
  }

  updateTabButtons();
  if (tabResolved) {
    updateTabInUrl();
  }
}

function normalizeDeadlineInput(value) {
  if (!value) {
    return null;
  }

  try {
    const date = new Date(value);
    if (Number.isNaN(date.getTime())) {
      return null;
    }
    return date.toISOString();
  } catch (error) {
    console.warn('Ungültige Deadline-Eingabe:', error);
    return null;
  }
}

function toDateTimeLocalValue(value) {
  if (!value) {
    return '';
  }

  try {
    const date = new Date(value);
    if (Number.isNaN(date.getTime())) {
      return '';
    }
    const local = new Date(date.getTime() - date.getTimezoneOffset() * 60000);
    return local.toISOString().slice(0, 16);
  } catch (error) {
    console.warn('Kann Deadline nicht für Eingabe formatieren:', error);
    return '';
  }
}

function normalizeFacebookPostUrl(rawValue) {
  if (typeof rawValue !== 'string') {
    return null;
  }

  let value = rawValue.trim();
  if (!value) {
    return null;
  }

  const trackingIndex = value.indexOf('__cft__');
  if (trackingIndex !== -1) {
    value = value.slice(0, trackingIndex);
  }

  value = value.replace(/[?&]$/, '');

  let parsed;
  try {
    parsed = new URL(value);
  } catch (error) {
    try {
      parsed = new URL(value, window.location.origin);
    } catch (innerError) {
      try {
        parsed = new URL(value, 'https://www.facebook.com');
      } catch (fallbackError) {
        return null;
      }
    }
  }

  if (!parsed.hostname.toLowerCase().endsWith('facebook.com')) {
    return null;
  }

  const normalizedPathBeforeTrim = parsed.pathname.replace(/\/+$/, '') || '/';
  const lowerPathBeforeTrim = normalizedPathBeforeTrim.toLowerCase();
  const watchId = parsed.searchParams.get('v') || parsed.searchParams.get('video_id');
  if ((lowerPathBeforeTrim === '/watch' || lowerPathBeforeTrim === '/video.php') && watchId) {
    parsed.pathname = `/reel/${watchId}/`;
    parsed.search = '';
  } else {
    const reelMatch = lowerPathBeforeTrim.match(/^\/reel\/([^/]+)$/);
    if (reelMatch) {
      parsed.pathname = `/reel/${reelMatch[1]}/`;
      parsed.search = '';
    }
  }

  const cleanedParams = new URLSearchParams();
  parsed.searchParams.forEach((paramValue, paramKey) => {
    const lowerKey = paramKey.toLowerCase();
    const isTrackingParam = FACEBOOK_TRACKING_PARAMS.some((trackingKey) => lowerKey.startsWith(trackingKey));
    const isSingleUnitParam = lowerKey === 's' && paramValue === 'single_unit';
    if (isTrackingParam || isSingleUnitParam) {
      return;
    }
    cleanedParams.append(paramKey, paramValue);
  });

  const normalizedPath = parsed.pathname.replace(/\/+$/, '').toLowerCase();
  if (normalizedPath.startsWith('/hashtag/') || normalizedPath.startsWith('/watch/hashtag/')) {
    return '';
  }

  const search = cleanedParams.toString();
  const formatted = `${parsed.origin}${parsed.pathname}${search ? `?${search}` : ''}`;
  return formatted.replace(/[?&]$/, '');
}

function getDeadlinePartsFromValue(value) {
  const localValue = toDateTimeLocalValue(value);

  if (!localValue) {
    return {
      date: '',
      time: ''
    };
  }

  const [datePart, timePart = ''] = localValue.split('T');
  return {
    date: datePart,
    time: timePart
  };
}

function getDefaultDeadlineParts() {
  const tomorrow = new Date();
  tomorrow.setHours(0, 0, 0, 0);
  tomorrow.setDate(tomorrow.getDate() + 1);

  const localIso = new Date(tomorrow.getTime() - tomorrow.getTimezoneOffset() * 60000)
    .toISOString()
    .slice(0, 16);

  const [datePart, timePart = '00:00'] = localIso.split('T');

  return {
    date: datePart,
    time: timePart || '00:00'
  };
}

function getDefaultDeadlineInputValue() {
  const { date, time } = getDefaultDeadlineParts();
  if (!date) {
    return '';
  }
  return `${date}T${time || '00:00'}`;
}

function positionDeadlinePicker(picker, triggerElement) {
  if (!picker || !triggerElement) {
    return;
  }

  const rect = triggerElement.getBoundingClientRect();
  const pickerRect = picker.getBoundingClientRect();

  const viewportWidth = window.innerWidth;
  const viewportHeight = window.innerHeight;
  const safeMargin = 16;
  const offset = 8;

  let left = rect.left;
  let top = rect.bottom + offset;

  if (left + pickerRect.width + safeMargin > viewportWidth) {
    left = viewportWidth - pickerRect.width - safeMargin;
  }

  if (left < safeMargin) {
    left = safeMargin;
  }

  if (top + pickerRect.height + safeMargin > viewportHeight) {
    top = rect.top - pickerRect.height - offset;
    if (top < safeMargin) {
      top = viewportHeight - pickerRect.height - safeMargin;
    }
  }

  if (top < safeMargin) {
    top = safeMargin;
  }

  picker.style.left = `${Math.round(left)}px`;
  picker.style.top = `${Math.round(top)}px`;
}

function closeActiveDeadlinePicker() {
  if (!activeDeadlinePicker) {
    return;
  }

  const current = activeDeadlinePicker;
  activeDeadlinePicker = null;

  try {
    if (typeof current.destroy === 'function') {
      current.destroy();
      return;
    }

    const { input } = current;
    if (input) {
      input.removeEventListener('change', current.onChange);
      input.removeEventListener('blur', current.onBlur);
      input.remove();
    }
  } catch (error) {
    console.warn('Konnte Deadline-Picker nicht schließen:', error);
  }
}

function openNativeDeadlinePicker(post, triggerElement) {
  if (!post || !triggerElement) {
    return;
  }

  closeActiveDeadlinePicker();

  const picker = document.createElement('div');
  picker.className = 'deadline-picker';
  picker.setAttribute('role', 'dialog');
  picker.setAttribute('aria-modal', 'true');
  picker.setAttribute('tabindex', '-1');

  picker.innerHTML = `
    <div class="deadline-picker__header">
      <span class="deadline-picker__title">Deadline anpassen</span>
      <button type="button" class="deadline-picker__close" aria-label="Schließen">×</button>
    </div>
    <form class="deadline-picker__form" novalidate>
      <div class="deadline-picker__field">
        <label>
          <span>Datum</span>
          <input type="date" class="deadline-picker__date" required>
        </label>
      </div>
      <div class="deadline-picker__field">
        <label>
          <span>Uhrzeit</span>
          <input type="time" class="deadline-picker__time" step="60" placeholder="hh:mm">
        </label>
      </div>
      <div class="deadline-picker__hint">Uhrzeit optional – Standard ist 00:00 Uhr.</div>
      <div class="deadline-picker__error" aria-live="polite"></div>
      <div class="deadline-picker__actions">
        <button type="submit" class="btn btn-primary deadline-picker__save">Speichern</button>
        <button type="button" class="btn btn-secondary deadline-picker__cancel">Abbrechen</button>
      </div>
    </form>
  `;

  const form = picker.querySelector('.deadline-picker__form');
  const dateInput = picker.querySelector('.deadline-picker__date');
  const timeInput = picker.querySelector('.deadline-picker__time');
  const errorEl = picker.querySelector('.deadline-picker__error');
  const cancelButton = picker.querySelector('.deadline-picker__cancel');
  const closeButton = picker.querySelector('.deadline-picker__close');
  const saveButton = picker.querySelector('.deadline-picker__save');

  const originalSaveLabel = saveButton ? saveButton.textContent : '';
  const initialValues = { date: '', time: '' };

  const applyInitialValues = () => {
    const existing = getDeadlinePartsFromValue(post.deadline_at);
    const defaults = existing.date ? existing : getDefaultDeadlineParts();

    initialValues.date = defaults.date;
    initialValues.time = defaults.time || '';

    if (dateInput) {
      dateInput.value = defaults.date;
    }
    if (timeInput) {
      timeInput.value = defaults.time;
    }
  };

  applyInitialValues();

  document.body.appendChild(picker);
  positionDeadlinePicker(picker, triggerElement);

  const showError = (message = '') => {
    if (!errorEl) {
      return;
    }
    errorEl.textContent = message;
    errorEl.classList.toggle('is-visible', Boolean(message));
  };

  let isSaving = false;

  const normalizeParts = (datePart, timePart) => {
    if (!datePart) {
      return '';
    }
    return `${datePart}T${(timePart || '00:00')}`;
  };

  const attemptAutoSave = () => {
    if (isSaving || !form || !dateInput) {
      return false;
    }

    const currentDate = dateInput.value;
    if (!currentDate) {
      return false;
    }

    const currentTime = timeInput ? timeInput.value : '';
    const currentNormalized = normalizeParts(currentDate, currentTime);
    const initialNormalized = normalizeParts(initialValues.date, initialValues.time);

    if (currentNormalized === initialNormalized) {
      return false;
    }

    if (typeof form.requestSubmit === 'function') {
      form.requestSubmit();
    } else {
      form.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
    }

    return true;
  };

  const handleSubmit = async (event) => {
    event.preventDefault();
    if (isSaving || !dateInput || !saveButton) {
      return;
    }

    showError('');

    const dateValue = dateInput.value;
    if (!dateValue) {
      showError('Bitte ein Datum auswählen.');
      dateInput.focus();
      return;
    }

    const timeValue = (timeInput && timeInput.value) ? timeInput.value : '00:00';
    const combined = `${dateValue}T${timeValue || '00:00'}`;
    const normalized = normalizeDeadlineInput(combined);

    if (!normalized) {
      showError('Ungültige Eingabe.');
      return;
    }

    try {
      isSaving = true;
      saveButton.disabled = true;
      saveButton.textContent = 'Speichert…';
      const success = await saveDeadline(post.id, normalized);
      if (success) {
        closeActiveDeadlinePicker();
        return;
      }
      showError('Deadline wurde nicht gespeichert.');
    } catch (error) {
      console.warn('Deadline konnte nicht gespeichert werden:', error);
      showError('Konnte Deadline nicht speichern.');
    } finally {
      isSaving = false;
      saveButton.disabled = false;
      saveButton.textContent = originalSaveLabel;
    }
  };

  form?.addEventListener('submit', handleSubmit);

  const cleanupListeners = [];

  const pickerState = {
    destroy: () => {
      while (cleanupListeners.length) {
        const clean = cleanupListeners.pop();
        try {
          clean();
        } catch (error) {
          console.warn('Konnte Listener nicht entfernen:', error);
        }
      }
      picker.remove();
    }
  };

  const registerListener = (target, type, listener, options) => {
    if (!target) {
      return;
    }
    target.addEventListener(type, listener, options);
    cleanupListeners.push(() => target.removeEventListener(type, listener, options));
  };

  const closePicker = () => {
    if (activeDeadlinePicker) {
      closeActiveDeadlinePicker();
      return;
    }

    pickerState.destroy();
  };

  registerListener(cancelButton, 'click', (event) => {
    event.preventDefault();
    closePicker();
  });

  registerListener(closeButton, 'click', (event) => {
    event.preventDefault();
    closePicker();
  });

  const handleOutsidePointer = (event) => {
    const targetElement = event.target instanceof Element ? event.target : null;
    const isClearButton = targetElement?.closest('.post-deadline__clear');

    if (!picker.contains(event.target) && !triggerElement.contains(event.target)) {
      if (!isClearButton && attemptAutoSave()) {
        event.preventDefault();
        event.stopPropagation();
        return;
      }

      closePicker();
    }
  };

  const handleKeydown = (event) => {
    if (event.key === 'Escape') {
      event.preventDefault();
      closePicker();
    }
  };

  const reposition = () => {
    positionDeadlinePicker(picker, triggerElement);
  };

  registerListener(document, 'pointerdown', handleOutsidePointer, true);
  registerListener(document, 'keydown', handleKeydown);
  registerListener(window, 'resize', reposition);
  registerListener(window, 'scroll', reposition, true);

  activeDeadlinePicker = pickerState;

  requestAnimationFrame(() => {
    positionDeadlinePicker(picker, triggerElement);
    requestAnimationFrame(() => {
      dateInput?.focus({ preventScroll: true });
    });
  });
}

async function clearDeadline(postId) {
  closeActiveDeadlinePicker();
  await saveDeadline(postId, null);
}


function normalizeChecks(checks) {
  if (!Array.isArray(checks)) {
    return [];
  }

  return checks
    .map((check) => {
      if (!check) {
        return null;
      }

      const parsed = parseInt(check.profile_number, 10);
      if (Number.isNaN(parsed)) {
        return null;
      }

      const profileNumber = Math.min(MAX_PROFILES, Math.max(1, parsed));

      return {
        ...check,
        profile_number: profileNumber,
        profile_name: check.profile_name || getProfileName(profileNumber),
        checked_at: check.checked_at || null
      };
    })
    .filter(Boolean)
    .sort((a, b) => {
      const aTime = a.checked_at ? new Date(a.checked_at).getTime() : 0;
      const bTime = b.checked_at ? new Date(b.checked_at).getTime() : 0;
      if (aTime === bTime) {
        return a.profile_number - b.profile_number;
      }
      return aTime - bTime;
    });
}

function calculateUrgencyScore(postItem) {
  const post = postItem.post;
  const status = postItem.status;

  // Remaining participations needed
  const remaining = status.targetCount - status.checkedCount;
  if (remaining <= 0) return 999999; // Completed posts go to bottom

  const now = Date.now();
  const createdAt = post.created_at ? new Date(post.created_at).getTime() : now;

  // Time until deadline (in hours)
  let hoursUntilDeadline = Infinity;
  let totalDuration = Infinity;
  if (post.deadline_at) {
    const deadline = new Date(post.deadline_at).getTime();
    hoursUntilDeadline = Math.max(0, (deadline - now) / (1000 * 60 * 60));
    totalDuration = Math.max(1, (deadline - createdAt) / (1000 * 60 * 60)); // Total hours from creation to deadline
  }

  // Time since last participation (in hours)
  let hoursSinceLastCheck = Infinity;
  if (status.lastCheckedAt) {
    const lastCheck = new Date(status.lastCheckedAt).getTime();
    hoursSinceLastCheck = (now - lastCheck) / (1000 * 60 * 60);
  }

  // Calculate ideal pace: how often should participations happen?
  let idealIntervalHours = Infinity;
  let behindSchedule = false;

  if (totalDuration < Infinity && status.targetCount > 0) {
    // Ideal interval between participations
    idealIntervalHours = totalDuration / status.targetCount;

    // Expected participations by now (based on time elapsed)
    const hoursElapsed = (now - createdAt) / (1000 * 60 * 60);
    const expectedChecks = Math.floor(hoursElapsed / idealIntervalHours);

    // Are we behind schedule?
    behindSchedule = status.checkedCount < expectedChecks;
  }

  // Calculate urgency score (lower = more urgent)
  let score = 0;

  // For posts with deadline: calculate when the next participation should ideally happen
  if (hoursUntilDeadline < Infinity && remaining > 0) {
    // How many hours do we have per remaining participation?
    const hoursPerParticipation = hoursUntilDeadline / remaining;

    // When should the next participation ideally happen?
    let hoursUntilNextIdeal = hoursPerParticipation;

    // If we have a last check, calculate from there
    if (hoursSinceLastCheck < Infinity) {
      hoursUntilNextIdeal = Math.max(0, hoursPerParticipation - hoursSinceLastCheck);
    }

    // Score based on how soon the next participation is due
    // Posts that are overdue or due soon get higher priority
    if (hoursUntilNextIdeal <= 0) {
      // Overdue! High priority
      score = Math.abs(hoursUntilNextIdeal) * -10; // Negative score = very high priority
    } else if (hoursUntilNextIdeal <= 24) {
      // Due within 24h
      score = hoursUntilNextIdeal * 10; // 0-240
    } else if (hoursUntilNextIdeal <= 72) {
      // Due within 3 days
      score = 240 + (hoursUntilNextIdeal - 24) * 20; // 240-1200
    } else {
      // Due later
      score = 1200 + (hoursUntilNextIdeal - 72) * 50; // 1200+
    }

    // Emergency boost: if deadline is very close, override everything
    if (hoursUntilDeadline <= 24 && remaining > 0) {
      score = Math.min(score, hoursUntilDeadline * 5); // Max 120 points
    } else if (hoursUntilDeadline <= 48 && remaining > 1) {
      score = Math.min(score, 120 + (hoursUntilDeadline - 24) * 10);
    }
  } else if (hoursUntilDeadline < Infinity && remaining === 0) {
    // Completed with deadline
    score = 100000;
  } else {
    // No deadline - use simpler heuristic
    score = 50000;

    // Prioritize posts that haven't been checked in a while
    if (hoursSinceLastCheck < Infinity) {
      if (hoursSinceLastCheck < 24) {
        score += 50; // Recently checked = lower priority
      } else if (hoursSinceLastCheck > 72) {
        score -= 100; // Long time since check = higher priority
      }
    }
  }

  // Recent participation (<24h) should lower urgency regardless of deadline pressure
  if (hoursSinceLastCheck < 24) {
    const freshnessRatio = (24 - hoursSinceLastCheck) / 24; // 0 (at 24h) to 1 (just now)
    const recencyPenalty = 60 + Math.round(freshnessRatio * 120); // 60-180 point penalty
    score += recencyPenalty;
  }

  // Give extra priority to posts with deadlines approaching soon
  if (hoursUntilDeadline < Infinity && remaining > 0) {
    const urgencyWindowHours = 72;
    const cappedHours = Math.min(hoursUntilDeadline, urgencyWindowHours);
    const closenessRatio = 1 - (cappedHours / urgencyWindowHours); // 0-1

    if (closenessRatio > 0) {
      const baseBoost = Math.round(closenessRatio * 120); // Up to 120 points
      const remainingFactor = Math.min(1.6, 0.7 + remaining * 0.3); // 1.0 (1 remaining) .. 1.6 (>=3 remaining)
      const deadlineBoost = Math.round(baseBoost * remainingFactor);
      score -= deadlineBoost;
    }
  }

  return score;
}

function comparePostItems(a, b) {
  const postA = a.post;
  const postB = b.post;
  const createdA = toTimestamp(postA.created_at, 0);
  const createdB = toTimestamp(postB.created_at, 0);

  let comparison = 0;

  if (sortMode === 'deadline') {
    const deadlineA = toTimestamp(postA.deadline_at, Infinity);
    const deadlineB = toTimestamp(postB.deadline_at, Infinity);

    if (deadlineA !== deadlineB) {
      comparison = deadlineA - deadlineB;
    }
  } else if (sortMode === 'smart') {
    // Smart sorting based on urgency
    const scoreA = calculateUrgencyScore(a);
    const scoreB = calculateUrgencyScore(b);

    if (scoreA !== scoreB) {
      comparison = scoreB - scoreA; // Higher score = lower priority (inverted for intuitive direction)
    }
  } else if (sortMode === 'lastChange') {
    const changeA = Number.isFinite(a.status.lastChangeTimestamp)
      ? a.status.lastChangeTimestamp
      : toTimestamp(postA.created_at, 0);
    const changeB = Number.isFinite(b.status.lastChangeTimestamp)
      ? b.status.lastChangeTimestamp
      : toTimestamp(postB.created_at, 0);

    if (changeA !== changeB) {
      comparison = changeA - changeB;
    }
  } else if (sortMode === 'lastCheck') {
    const lastA = Number.isFinite(a.status.lastCheckedTimestamp)
      ? a.status.lastCheckedTimestamp
      : Number.NEGATIVE_INFINITY;
    const lastB = Number.isFinite(b.status.lastCheckedTimestamp)
      ? b.status.lastCheckedTimestamp
      : Number.NEGATIVE_INFINITY;

    if (lastA !== lastB) {
      comparison = lastA - lastB;
    }
  } else if (createdA !== createdB) {
    comparison = createdA - createdB;
  }

  if (comparison === 0 && createdA !== createdB) {
    comparison = createdA - createdB;
  }

  if (comparison === 0) {
    const idA = String(postA.id || '');
    const idB = String(postB.id || '');
    if (idA !== idB) {
      comparison = idA < idB ? -1 : 1;
    }
  }

  if (comparison === 0) {
    return 0;
  }

  const multiplier = sortDirection === 'asc' ? 1 : -1;
  return comparison * multiplier;
}

function displayManualPostMessage(message, type = 'success') {
  if (!manualPostMessage) {
    return;
  }

  manualPostMessage.textContent = message;
  manualPostMessage.classList.remove('error', 'success');
  manualPostMessage.classList.add(type);
}

function clearManualPostMessage() {
  if (!manualPostMessage) {
    return;
  }
  manualPostMessage.textContent = '';
  manualPostMessage.classList.remove('error', 'success');
}

function openManualPostModal({ mode = 'create', post = null, focus = null } = {}) {
  if (!manualPostModal || !manualPostForm) {
    return;
  }

  manualPostModalLastFocus = document.activeElement && typeof document.activeElement.blur === 'function'
    ? document.activeElement
    : null;

  resetManualPostForm({ keepMessages: false });

  manualPostMode = mode === 'edit' ? 'edit' : 'create';
  manualPostEditingId = manualPostMode === 'edit' && post ? post.id : null;

  const desiredFocus = typeof focus === 'string' ? focus : (manualPostMode === 'edit' ? 'title' : 'url');

  if (manualPostModalTitle) {
    manualPostModalTitle.textContent = manualPostMode === 'edit'
      ? 'Beitrag bearbeiten'
      : 'Beitrag hinzufügen';
  }

  if (manualPostSubmitButton) {
    manualPostSubmitButton.textContent = manualPostMode === 'edit'
      ? 'Aktualisieren'
      : 'Speichern';
  }

  if (manualPostMode === 'edit' && post) {
    populateManualPostForm(post);
  }

  manualPostModal.removeAttribute('hidden');
  manualPostModal.classList.add('open');

  manualPostModalPreviousOverflow = document.body.style.overflow;
  document.body.style.overflow = 'hidden';

  requestAnimationFrame(() => {
    if (manualPostModalContent) {
      manualPostModalContent.focus();
    }

    if (desiredFocus === 'deadline' && manualPostDeadlineInput) {
      manualPostDeadlineInput.focus();
      manualPostDeadlineInput.select?.();
      return;
    }

    if (desiredFocus === 'title' && manualPostTitleInput) {
      manualPostTitleInput.focus();
      manualPostTitleInput.select();
      return;
    }

    if (desiredFocus === 'url' && manualPostUrlInput) {
      manualPostUrlInput.focus();
      manualPostUrlInput.select?.();
      return;
    }

    if (manualPostMode === 'edit' && manualPostTitleInput) {
      manualPostTitleInput.focus();
      manualPostTitleInput.select();
    } else if (manualPostUrlInput) {
      manualPostUrlInput.focus();
    }
  });
}

function closeManualPostModal() {
  if (!manualPostModal) {
    return;
  }

  manualPostModal.classList.remove('open');
  manualPostModal.setAttribute('hidden', '');
  resetManualPostForm();

  document.body.style.overflow = manualPostModalPreviousOverflow;

  if (manualPostModalLastFocus && typeof manualPostModalLastFocus.focus === 'function') {
    manualPostModalLastFocus.focus();
  }
}

function loadAutoRefreshSettings() {
  try {
    const stored = localStorage.getItem(REFRESH_SETTINGS_KEY);
    if (stored) {
      const parsed = JSON.parse(stored);
      if (typeof parsed === 'object' && parsed) {
        if (typeof parsed.enabled === 'boolean') {
          autoRefreshSettings.enabled = parsed.enabled;
        }
        if (typeof parsed.interval === 'number' && parsed.interval >= 5000) {
          autoRefreshSettings.interval = parsed.interval;
        }
      }
    }
  } catch (error) {
    console.warn('Konnte Refresh-Einstellungen nicht laden:', error);
  }

  if (autoRefreshToggle) {
    autoRefreshToggle.checked = autoRefreshSettings.enabled;
  }
  if (autoRefreshIntervalSelect) {
    autoRefreshIntervalSelect.value = String(autoRefreshSettings.interval);
    autoRefreshIntervalSelect.disabled = !autoRefreshSettings.enabled;
  }
}

function saveAutoRefreshSettings() {
  try {
    localStorage.setItem(REFRESH_SETTINGS_KEY, JSON.stringify(autoRefreshSettings));
  } catch (error) {
    console.warn('Konnte Refresh-Einstellungen nicht speichern:', error);
  }
}

function applyAutoRefreshSettings() {
  if (autoRefreshTimer) {
    clearInterval(autoRefreshTimer);
    autoRefreshTimer = null;
  }

  if (!isValidProfileNumber(currentProfile)) {
    return;
  }

  if (autoRefreshIntervalSelect) {
    const disabled = !autoRefreshSettings.enabled || updatesStreamHealthy;
    autoRefreshIntervalSelect.disabled = disabled;
    autoRefreshIntervalSelect.title = updatesStreamHealthy
      ? 'Live-Updates sind aktiv; das Intervall wird aktuell nicht verwendet.'
      : '';
  }

  if (!autoRefreshSettings.enabled) {
    return;
  }

  if (updatesStreamHealthy) {
    return;
  }

  autoRefreshTimer = setInterval(() => {
    if (document.hidden) {
      return;
    }
    if (!isValidProfileNumber(currentProfile)) {
      return;
    }
    fetchPosts({ showLoader: false });
  }, autoRefreshSettings.interval);
}

function loadSortMode({ fromTabChange = false } = {}) {
  const pageKey = getSortSettingsPageKey();
  const tabKey = getSortTabKey();
  const storage = getSortStorage();
  const pageSettings = storage[pageKey] && typeof storage[pageKey] === 'object' ? storage[pageKey] : {};

  const legacyCandidate = (pageSettings.mode || pageSettings.direction)
    ? { mode: pageSettings.mode, direction: pageSettings.direction }
    : null;

  const candidates = [pageSettings[tabKey], legacyCandidate, pageSettings.default, storage.default];
  let applied = null;

  for (const candidate of candidates) {
    if (candidate && typeof candidate === 'object') {
      applied = candidate;
      break;
    }
  }

  if (applied) {
    sortMode = normalizeSortMode(applied.mode);
    sortDirection = normalizeSortDirection(applied.direction);
  } else {
    sortMode = normalizeSortMode(sortMode);
    sortDirection = normalizeSortDirection(sortDirection);
  }

  if (sortModeSelect) {
    sortModeSelect.value = sortMode;
  }
  updateSortDirectionToggleUI();

  if (!fromTabChange) {
    // Ensure structure exists for initial load
    saveSortMode();
  }
}

function saveSortMode() {
  const pageKey = getSortSettingsPageKey();
  const tabKey = getSortTabKey();
  const storage = getSortStorage();

  if (!storage[pageKey] || typeof storage[pageKey] !== 'object') {
    storage[pageKey] = {};
  }

  delete storage[pageKey].mode;
  delete storage[pageKey].direction;

  storage[pageKey][tabKey] = {
    mode: normalizeSortMode(sortMode),
    direction: normalizeSortDirection(sortDirection)
  };

  if (!storage[pageKey].default) {
    storage[pageKey].default = { ...DEFAULT_SORT_SETTINGS };
  }

  if (!storage.default) {
    storage.default = { ...DEFAULT_SORT_SETTINGS };
  }

  persistSortStorage(storage);
}

function computePostStatus(post, profileNumber = currentProfile) {
  const requiredProfiles = normalizeRequiredProfiles(post);
  const checks = normalizeChecks(post.checks);

  let lastCheckedAt = null;
  let lastCheckedTimestamp = null;

  for (const check of checks) {
    if (!check || !check.checked_at) {
      continue;
    }
    const timestamp = new Date(check.checked_at).getTime();
    if (Number.isNaN(timestamp)) {
      continue;
    }
    if (lastCheckedTimestamp === null || timestamp > lastCheckedTimestamp) {
      lastCheckedTimestamp = timestamp;
      lastCheckedAt = check.checked_at;
    }
  }

  const lastChangeTimestamp = toTimestamp(post.last_change, toTimestamp(post.created_at, 0));
  const lastChangeAt = post.last_change || post.created_at || null;

  // Check if post is expired (deadline passed)
  const isExpired = post.deadline_at ? new Date(post.deadline_at) < new Date() : false;

  const backendStatuses = Array.isArray(post.profile_statuses) ? post.profile_statuses : [];
  let profileStatuses = backendStatuses
    .map((status) => {
      if (!status) {
        return null;
      }
      const parsed = parseInt(status.profile_number, 10);
      if (Number.isNaN(parsed)) {
        return null;
      }
      const profileNumberValue = Math.min(MAX_PROFILES, Math.max(1, parsed));
      let normalizedStatus = status.status;
      if (normalizedStatus !== 'done' && normalizedStatus !== 'available') {
        normalizedStatus = 'locked';
      }
      const check = checks.find((item) => item.profile_number === profileNumberValue) || null;
      return {
        profile_number: profileNumberValue,
        profile_name: status.profile_name || getProfileName(profileNumberValue),
        status: normalizedStatus,
        checked_at: status.checked_at || (check ? check.checked_at : null) || null
      };
    })
    .filter(Boolean);

  if (profileStatuses.length !== requiredProfiles.length) {
    const checksByProfile = new Map(checks.map((check) => [check.profile_number, check]));
    const completedSet = new Set(checks.map((check) => check.profile_number));

    profileStatuses = requiredProfiles.map((value, index) => {
      const prerequisites = requiredProfiles.slice(0, index);
      const prerequisitesMet = prerequisites.every((profile) => completedSet.has(profile));
      const check = checksByProfile.get(value) || null;

      return {
        profile_number: value,
        profile_name: getProfileName(value),
        status: check ? 'done' : (prerequisitesMet ? 'available' : 'locked'),
        checked_at: check ? check.checked_at : null
      };
    });
  } else {
    const checksByProfile = new Map(checks.map((check) => [check.profile_number, check]));
    profileStatuses = requiredProfiles.map((value) => {
      const status = profileStatuses.find((item) => item.profile_number === value);
      if (!status) {
        const check = checksByProfile.get(value) || null;
        return {
          profile_number: value,
          profile_name: getProfileName(value),
          status: check ? 'done' : 'locked',
          checked_at: check ? check.checked_at : null
        };
      }

      if (status.status === 'done') {
        const check = checksByProfile.get(value) || null;
        return {
          ...status,
          checked_at: status.checked_at || (check ? check.checked_at : null) || null
        };
      }

      if (status.status === 'available') {
        return {
          ...status,
          checked_at: status.checked_at || null
        };
      }

      return {
        ...status,
        status: 'locked',
        checked_at: status.checked_at || null
      };
    });
  }

  const completedProfilesSet = new Set(
    profileStatuses
      .filter((status) => status.status === 'done')
      .map((status) => status.profile_number)
  );

  const checkedCount = profileStatuses.filter((status) => status.status === 'done').length;
  const targetCount = profileStatuses.length;
  const isComplete = profileStatuses.every((status) => status.status === 'done');
  const nextRequiredProfile = profileStatuses.find((status) => status.status === 'available') || null;
  const isCurrentProfileRequired = requiredProfiles.includes(profileNumber);
  const isCurrentProfileDone = profileStatuses.some((status) => status.profile_number === profileNumber && status.status === 'done');
  const canCurrentProfileCheck = profileStatuses.some((status) => status.profile_number === profileNumber && status.status === 'available');

  const waitingForStatuses = profileStatuses.filter((status) => status.profile_number < profileNumber && status.status !== 'done');
  const waitingForProfiles = waitingForStatuses.map((status) => status.profile_number);
  const waitingForNames = waitingForStatuses.map((status) => status.profile_name);

  return {
    requiredProfiles,
    profileStatuses,
    checks,
    lastCheckedAt,
    lastCheckedTimestamp,
    lastChangeAt,
    lastChangeTimestamp,
    completedProfilesSet,
    checkedCount,
    targetCount,
    isComplete,
    isCurrentProfileRequired,
    isCurrentProfileDone,
    canCurrentProfileCheck,
    waitingForProfiles,
    waitingForNames,
    nextRequiredProfile,
    profileNumber,
    isExpired
  };
}

function applyScreenshotModalSize() {
  if (!screenshotModalContent || !screenshotModalImage) {
    return;
  }

  if (screenshotModalZoomed) {
    return;
  }

  if (!screenshotModalImage.src) {
    return;
  }

  requestAnimationFrame(() => {
    const padding = 48;
    const viewportWidth = Math.max(320, window.innerWidth * 0.95);
    const viewportHeight = Math.max(280, window.innerHeight * 0.92);
    const naturalWidth = screenshotModalImage.naturalWidth || screenshotModalImage.width || 0;
    const naturalHeight = screenshotModalImage.naturalHeight || screenshotModalImage.height || 0;

    const targetWidth = Math.min(Math.max(320, naturalWidth + padding), viewportWidth);
    const targetHeight = Math.min(Math.max(260, naturalHeight + padding), viewportHeight);

    screenshotModalContent.style.width = `${targetWidth}px`;
    screenshotModalContent.style.height = `${targetHeight}px`;
  });
}

async function fetchProfileState() {
  try {
    const response = await apiFetch(`${API_URL}/profile-state`);
    if (!response.ok) {
      return null;
    }
    const data = await response.json();
    if (data && typeof data.profile_number !== 'undefined') {
      const parsed = parseInt(data.profile_number, 10);
      if (!Number.isNaN(parsed)) {
        return parsed;
      }
    }
    return null;
  } catch (error) {
    console.warn('Profilstatus konnte nicht geladen werden:', error);
    return null;
  }
}

async function pushProfileState(profileNumber) {
  try {
    await apiFetch(`${API_URL}/profile-state`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ profile_number: profileNumber })
    });
  } catch (error) {
    console.error('Profilstatus konnte nicht gespeichert werden:', error);
  }
}

function ensureProfileSelected() {
  if (isValidProfileNumber(currentProfile)) {
    hideError();
    return true;
  }
  showError('Bitte zuerst ein Profil auswählen.');
  return false;
}

function applyProfileNumber(profileNumber, { fromBackend = false } = {}) {
  if (!isValidProfileNumber(profileNumber)) {
    return;
  }

  if (profileSelectElement) {
    profileSelectElement.value = String(profileNumber);
  }

  if (currentProfile === profileNumber) {
    if (!fromBackend) {
      pushProfileState(profileNumber);
    }
    return;
  }

  currentProfile = profileNumber;
  localStorage.setItem('profileNumber', currentProfile);
  hideError();

  if (!fromBackend) {
    pushProfileState(currentProfile);
  }

  resetVisibleCount();
  pendingOpenCooldownMap = loadPendingOpenCooldownMap(currentProfile);
  renderPosts();
  maybeAutoOpenPending('profile');

  if (!posts.length && !isFetchingPosts) {
    fetchPosts({ showLoader: false });
  }

  checkAutoCheck();
  applyAutoRefreshSettings();
}

// Load profile from localStorage
function loadProfile() {
  fetchProfileState().then((backendProfile) => {
    if (isValidProfileNumber(backendProfile)) {
      applyProfileNumber(backendProfile, { fromBackend: true });
    } else {
      const saved = localStorage.getItem('profileNumber');
      const parsed = saved ? parseInt(saved, 10) : NaN;
      if (isValidProfileNumber(parsed)) {
        applyProfileNumber(parsed, { fromBackend: true });
        return;
      }
      if (profileSelectElement) {
        profileSelectElement.value = '';
      }
      currentProfile = null;
      showError('Bitte zuerst ein Profil auswählen.');
    }
  });
}

// Save profile to localStorage
function saveProfile(profileNumber) {
  applyProfileNumber(profileNumber);
}

function startProfilePolling() {
  if (profilePollTimer) {
    clearInterval(profilePollTimer);
  }

  profilePollTimer = setInterval(async () => {
    const backendProfile = await fetchProfileState();
    if (backendProfile && backendProfile !== currentProfile) {
      applyProfileNumber(backendProfile, { fromBackend: true });
    }
  }, 5000);
}

// Profile selector change handler
if (profileSelectElement) {
  profileSelectElement.addEventListener('change', (e) => {
    const parsed = parseInt(e.target.value, 10);
    if (!isValidProfileNumber(parsed)) {
      showError('Bitte zuerst ein Profil auswählen.');
      return;
    }
    saveProfile(parsed);
  });
}

if (includeExpiredToggle) {
  updateIncludeExpiredToggleUI();
  updateIncludeExpiredToggleVisibility();
  includeExpiredToggle.addEventListener('change', () => {
    includeExpiredPosts = includeExpiredToggle.checked;
    persistIncludeExpiredPreference(includeExpiredPosts);
    resetVisibleCount('all');
    renderPosts();
  });
}

if (pendingBulkCountSelect) {
  pendingBulkCountSelect.value = String(pendingBulkCount);
  pendingBulkCountSelect.addEventListener('change', () => {
    const value = parseInt(pendingBulkCountSelect.value, 10);
    if (!Number.isNaN(value)) {
      pendingBulkCount = value;
      persistPendingBulkCount(value);
    }
  });
}

if (pendingBulkOpenBtn) {
  pendingBulkOpenBtn.addEventListener('click', () => openPendingBatch());
}

if (pendingAutoOpenOverlayPanel) {
  pendingAutoOpenOverlayPanel.addEventListener('click', () => cancelPendingAutoOpen(true));
}

if (pendingAutoOpenToggle) {
  pendingAutoOpenToggle.checked = !!pendingAutoOpenEnabled;
  pendingAutoOpenToggle.addEventListener('change', () => {
    pendingAutoOpenEnabled = pendingAutoOpenToggle.checked;
    persistPendingAutoOpenEnabled(pendingAutoOpenEnabled);
    pendingAutoOpenTriggered = false;
    if (!pendingAutoOpenEnabled && pendingAutoOpenTimerId) {
      cancelPendingAutoOpen(false);
    }
    if (pendingAutoOpenEnabled) {
      maybeAutoOpenPending('toggle');
    } else {
      hidePendingAutoOpenOverlay();
    }
  });
}

// Tab switching
getTabButtons().forEach((btn) => {
  btn.addEventListener('click', () => {
    setTab(btn.dataset.tab, { updateUrl: true, announceChange: true });
  });
  btn.addEventListener('keydown', handleTabKeydown);
});

window.addEventListener('app:view-change', (event) => {
  if (event && event.detail && event.detail.view === 'posts') {
    updateTabInUrl();
  }
  updatePostsScrollTopButtonVisibility();
});

if (manualPostForm) {
  manualPostForm.addEventListener('submit', handleManualPostSubmit);
}

if (manualPostResetButton) {
  manualPostResetButton.addEventListener('click', () => {
    if (manualPostMode === 'edit' && manualPostEditingId) {
      const post = posts.find((item) => item.id === manualPostEditingId);
      if (post) {
        populateManualPostForm(post);
      }
    } else {
      resetManualPostForm();
      if (manualPostUrlInput) {
        manualPostUrlInput.focus();
      }
    }
  });
}

if (autoRefreshToggle) {
  autoRefreshToggle.addEventListener('change', () => {
    autoRefreshSettings.enabled = !!autoRefreshToggle.checked;
    saveAutoRefreshSettings();
    applyAutoRefreshSettings();
    if (autoRefreshSettings.enabled && updatesStreamHealthy) {
      console.info('Live-Updates sind aktiv; automatisches Refresh bleibt pausiert.');
    }
    if (autoRefreshSettings.enabled && !updatesStreamHealthy) {
      fetchPosts({ showLoader: false });
    }
  });
}

if (autoRefreshIntervalSelect) {
  autoRefreshIntervalSelect.addEventListener('change', () => {
    const value = parseInt(autoRefreshIntervalSelect.value, 10);
    if (!Number.isNaN(value) && value >= 5000) {
      autoRefreshSettings.interval = value;
      saveAutoRefreshSettings();
      applyAutoRefreshSettings();
    }
  });
}

const manualRefreshBtn = document.getElementById('manualRefreshBtn');
if (manualRefreshBtn) {
  manualRefreshBtn.addEventListener('click', () => {
    fetchPosts();
  });
}

if (searchInput) {
  if (postsSearchTerm) {
    searchInput.value = postsSearchTerm;
  }

  searchInput.addEventListener('input', () => {
    postsSearchTerm = searchInput.value || '';
    persistPostsSearchTerm(postsSearchTerm);
    updateSearchClearButtonVisibility();
    resetVisibleCount();
    renderPosts();
  });

  searchInput.addEventListener('keydown', (event) => {
    if (!event || event.key !== 'Escape') {
      return;
    }
    if (typeof searchInput.value === 'string' && searchInput.value.length > 0) {
      event.preventDefault();
      searchInput.value = '';
      postsSearchTerm = '';
      persistPostsSearchTerm(postsSearchTerm);
      updateSearchClearButtonVisibility();
      resetVisibleCount();
      renderPosts();
      return;
    }
    searchInput.blur();
  });

  document.addEventListener('keydown', (event) => {
    if (!event || event.key !== '/' || event.metaKey || event.ctrlKey || event.altKey) {
      return;
    }
    const target = event.target;
    const tagName = target && target.tagName ? target.tagName.toLowerCase() : '';
    const isEditable = !!(target && (target.isContentEditable || tagName === 'input' || tagName === 'textarea' || tagName === 'select'));
    if (isEditable || !isPostsViewActive()) {
      return;
    }
    event.preventDefault();
    searchInput.focus();
    searchInput.select();
  });
}

if (searchClearBtn) {
  searchClearBtn.addEventListener('click', () => {
    if (!searchInput) {
      return;
    }
    searchInput.value = '';
    postsSearchTerm = '';
    persistPostsSearchTerm(postsSearchTerm);
    updateSearchClearButtonVisibility();
    resetVisibleCount();
    renderPosts();
    searchInput.focus();
  });
}

updateSearchClearButtonVisibility();

if (postsScrollTopBtn) {
  postsScrollTopBtn.addEventListener('click', () => {
    window.scrollTo({
      top: 0,
      behavior: getScrollBehavior()
    });
    updatePostsScrollTopButtonVisibility();
  });
}

if (mergeModeToggle) {
  mergeModeToggle.addEventListener('click', () => {
    if (currentTab !== 'all') {
      alert('Merge-Modus ist nur in „Alle Beiträge“ verfügbar.');
      return;
    }
    mergeMode = !mergeMode;
    if (!mergeMode) {
      resetMergeSelection();
    }
    updateMergeControlsUI();
    renderPosts();
  });
}

if (mergeSubmitBtn) {
  mergeSubmitBtn.addEventListener('click', () => {
    mergeSelectedPosts();
  });
}

if (sortModeSelect) {
  sortModeSelect.addEventListener('change', () => {
    const value = sortModeSelect.value;
    sortMode = VALID_SORT_MODES.has(value) ? value : DEFAULT_SORT_SETTINGS.mode;
    const defaultDirection = getDefaultSortDirectionForMode(sortMode);
    if (defaultDirection) {
      sortDirection = defaultDirection;
      updateSortDirectionToggleUI();
    }
    saveSortMode();
    resetVisibleCount();
    renderPosts();
  });
}

if (sortDirectionToggle) {
  sortDirectionToggle.addEventListener('click', () => {
    sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
    updateSortDirectionToggleUI();
    saveSortMode();
    resetVisibleCount();
    renderPosts();
  });
}

if (openManualPostModalBtn) {
  openManualPostModalBtn.addEventListener('click', () => {
    openManualPostModal({ mode: 'create' });
  });
}

if (manualPostModalClose) {
  manualPostModalClose.addEventListener('click', closeManualPostModal);
}

if (manualPostModalBackdrop) {
  manualPostModalBackdrop.addEventListener('click', closeManualPostModal);
}

// Fetch all posts
async function fetchPosts({ showLoader = true } = {}) {
  if (isFetchingPosts) {
    return;
  }

  if (!ensureProfileSelected()) {
    return;
  }

  isFetchingPosts = true;
  cancelPendingAutoOpen(false);

  try {
    if (showLoader) {
      showLoading();
    }

    const response = await apiFetch(`${API_URL}/posts`);

    if (!response.ok) {
      throw new Error('Failed to fetch posts');
    }

    const data = await response.json();
    posts = Array.isArray(data) ? data : [];
    await normalizeLoadedPostUrls();
    sortPostsByCreatedAt();
    renderPosts();
    maybeAutoOpenPending('load');
  } catch (error) {
    if (showLoader) {
      showError('Fehler beim Laden der Beiträge. Stelle sicher, dass das Backend läuft.');
    }
    console.error('Error fetching posts:', error);
  } finally {
    if (showLoader) {
      hideLoading();
    }
    isFetchingPosts = false;
  }
}

async function normalizeLoadedPostUrls() {
  if (!Array.isArray(posts) || !posts.length) {
    return false;
  }

  const candidates = posts
    .map((post) => {
      if (!post || !post.id || !post.url) {
        return null;
      }
      const cleaned = normalizeFacebookPostUrl(post.url);
      if (!cleaned || cleaned === post.url) {
        return null;
      }
      return { id: post.id, cleaned };
    })
    .filter(Boolean);

  if (!candidates.length) {
    return false;
  }

  let changed = false;

  for (const candidate of candidates) {
    try {
      const response = await apiFetch(`${API_URL}/posts/${candidate.id}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ url: candidate.cleaned })
      });

      if (!response.ok) {
        console.warn(`Konnte URL für Beitrag ${candidate.id} nicht normalisieren.`);
        continue;
      }

      const updatedPost = await response.json();
      posts = posts.map((item) => (item.id === candidate.id ? updatedPost : item));
      changed = true;
    } catch (error) {
      console.warn(`Fehler beim Normalisieren der URL für Beitrag ${candidate.id}:`, error);
    }
  }

  return changed;
}

function doesPostMatchFocus(post) {
  if (!post) {
    return false;
  }
  if (focusPostIdParam && String(post.id) === focusPostIdParam) {
    return true;
  }
  if (focusNormalizedUrl && post.url) {
    const candidateNormalized = normalizeFacebookPostUrl(post.url) || post.url;
    return candidateNormalized === focusNormalizedUrl;
  }
  return false;
}

function resolveFocusTargetInfo(items) {
  if (!Array.isArray(items) || (!focusPostIdParam && !focusNormalizedUrl)) {
    return { index: -1, post: null };
  }
  const index = items.findIndex(({ post }) => doesPostMatchFocus(post));
  return {
    index,
    post: index !== -1 && items[index] ? items[index].post : null
  };
}

function clearFocusParamsFromUrl() {
  try {
    const url = new URL(window.location.href);
    let changed = false;
    if (url.searchParams.has('postId')) {
      url.searchParams.delete('postId');
      changed = true;
    }
    if (url.searchParams.has('postUrl')) {
      url.searchParams.delete('postUrl');
      changed = true;
    }
    if (changed) {
      const newQuery = url.searchParams.toString();
      const newUrl = `${url.pathname}${newQuery ? `?${newQuery}` : ''}${url.hash}`;
      window.history.replaceState({}, document.title, newUrl);
    }
  } catch (error) {
    console.warn('Konnte Fokus-Parameter nicht aus URL entfernen:', error);
  }
}

function highlightPostCard(post) {
  if (!post || focusHandled) {
    return;
  }

  const card = document.getElementById(`post-${post.id}`);
  if (!card) {
    return;
  }

  card.classList.add('post-card--highlight');

  const hadTabIndex = card.hasAttribute('tabindex');
  if (!hadTabIndex) {
    card.setAttribute('tabindex', '-1');
    card.dataset.fbTrackerTempTabindex = '1';
  }

  requestAnimationFrame(() => {
    try {
      card.scrollIntoView({ behavior: getScrollBehavior(), block: 'center' });
    } catch (error) {
      console.warn('Konnte Karte nicht scrollen:', error);
    }
    try {
      card.focus({ preventScroll: true });
    } catch (error) {
      // ignore focus errors
    }
  });

  window.setTimeout(() => {
    card.classList.remove('post-card--highlight');
    if (card.dataset.fbTrackerTempTabindex === '1') {
      card.removeAttribute('tabindex');
      delete card.dataset.fbTrackerTempTabindex;
    }
  }, 4000);

  focusPostIdParam = null;
  focusPostUrlParam = null;
  focusNormalizedUrl = '';
  focusHandled = true;
  focusTabAdjusted = null;
  clearFocusParamsFromUrl();
}

function toggleMergeSelection(postId, checkboxEl = null) {
  if (!mergeMode || currentTab !== 'all') {
    resetMergeSelection();
    if (checkboxEl) {
      checkboxEl.checked = false;
    }
    updateMergeControlsUI();
    return;
  }

  if (mergeSelection.has(postId)) {
    mergeSelection.delete(postId);
  } else {
    if (mergeSelection.size >= MERGE_MAX_SELECTION) {
      alert(`Es können maximal ${MERGE_MAX_SELECTION} Beiträge ausgewählt werden.`);
      if (checkboxEl) {
        checkboxEl.checked = false;
      }
      updateMergeControlsUI();
      return;
    }
    mergeSelection.add(postId);
  }

  updateMergeControlsUI();
}

async function mergeSelectedPosts() {
  if (!mergeMode || currentTab !== 'all') {
    alert('Mergen ist nur in „Alle Beiträge“ möglich.');
    return;
  }

  if (mergeSelection.size !== MERGE_MAX_SELECTION) {
    alert('Bitte genau zwei Beiträge auswählen.');
    return;
  }

  const [primaryId, secondaryId] = Array.from(mergeSelection);

  const confirmed = window.confirm(
    `Beitrag ${primaryId} als Haupt-URL behalten und Beitrag ${secondaryId} anhängen?`
  );
  if (!confirmed) {
    return;
  }

  if (mergeSubmitBtn) {
    mergeSubmitBtn.disabled = true;
    mergeSubmitBtn.textContent = 'Mergen...';
  }

  try {
    const response = await apiFetch(`${API_URL}/posts/merge`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        primary_post_id: primaryId,
        secondary_post_id: secondaryId
      })
    });

    if (!response.ok) {
      const data = await response.json().catch(() => null);
      const message = data && data.error ? data.error : 'Beiträge konnten nicht gemerged werden.';
      throw new Error(message);
    }

    const mergedPost = await response.json();
    posts = posts
      .filter((post) => {
        if (post.id === secondaryId) {
          return false;
        }
        if (post.id === primaryId) {
          return false;
        }
        return true;
      });
    posts.push(mergedPost);
    sortPostsByCreatedAt();

    mergeMode = false;
    resetMergeSelection();
    updateMergeControlsUI();
    renderPosts();
    alert('Beiträge wurden gemerged.');
  } catch (error) {
    console.error('Merge error:', error);
    alert(error.message || 'Beiträge konnten nicht gemerged werden.');
  } finally {
    if (mergeSubmitBtn) {
      mergeSubmitBtn.disabled = false;
    }
    updateMergeControlsUI();
  }
}

// Render posts
function renderPosts() {
  hideLoading();
  if (!ensureProfileSelected()) {
    const container = document.getElementById('postsContainer');
    if (container) {
      container.innerHTML = '';
    }
    updatePostsScrollTopButtonVisibility();
    return;
  }
  hideError();

  const container = document.getElementById('postsContainer');
  if (!container) {
    updatePostsScrollTopButtonVisibility();
    return;
  }

  updateIncludeExpiredToggleVisibility();
  updateMergeControlsUI();
  closeActiveDeadlinePicker();
  updateTabButtons();
  cleanupLoadMoreObserver();

  const {
    sortedItems,
    filteredItems: filteredItemsResult,
    tabTotalCount,
    searchActive
  } = getPostListState();

  const tabCounts = sortedItems.reduce((acc, item) => {
    if (!item || !item.status) {
      return acc;
    }
    const status = item.status;
    if (!status.isExpired && status.canCurrentProfileCheck && !status.isComplete) {
      acc.pending += 1;
    }
    if (includeExpiredPosts || (!status.isExpired && !status.isComplete)) {
      acc.all += 1;
    }
    return acc;
  }, { pending: 0, all: 0 });
  updateTabCountLabels(tabCounts);
  updateTabButtons();

  let filteredItems = filteredItemsResult;
  const focusCandidateEntry = (!focusHandled && (focusPostIdParam || focusNormalizedUrl))
    ? sortedItems.find((item) => doesPostMatchFocus(item.post))
    : null;

  if (!focusHandled && focusCandidateEntry && !searchActive) {
    const candidateVisibleInCurrentTab = filteredItems.some(({ post }) => doesPostMatchFocus(post));
    if (!candidateVisibleInCurrentTab) {
      if (currentTab !== 'all' && focusTabAdjusted !== 'all') {
        focusTabAdjusted = 'all';
        setTab('all');
        return;
      }

      if (!includeExpiredPosts && (focusCandidateEntry.status.isExpired || focusCandidateEntry.status.isComplete)) {
        includeExpiredPosts = true;
        persistIncludeExpiredPreference(includeExpiredPosts);
        updateIncludeExpiredToggleUI();
        renderPosts();
        return;
      }
    }
  }

  const focusTargetInfo = resolveFocusTargetInfo(filteredItems);
  if (focusTargetInfo.index !== -1) {
    const requiredVisible = focusTargetInfo.index + 1;
    if (requiredVisible > getVisibleCount(currentTab)) {
      setVisibleCount(currentTab, requiredVisible);
    }
  }

  updateFilteredCount(currentTab, filteredItems.length);
  let pendingBulkStats = null;
  if (currentTab === 'pending') {
    pendingBulkStats = filteredItems.reduce((stats, item) => {
      const post = item && item.post ? item.post : null;
      if (!post || !post.url) {
        return stats;
      }
      if (isPendingOpenCooldownActive(post.id)) {
        stats.cooldownBlocked += 1;
      } else {
        stats.openableCount += 1;
      }
      return stats;
    }, { openableCount: 0, cooldownBlocked: 0 });
  }
  updatePendingBulkControls(filteredItems.length, pendingBulkStats);

  const visibleCount = Math.min(filteredItems.length, getVisibleCount(currentTab));
  const visibleItems = filteredItems.slice(0, visibleCount);

  const summaryHtml = buildPostsSummary({
    tab: currentTab,
    visibleCount,
    filteredCount: filteredItems.length,
    tabTotalCount,
    totalCountAll: posts.length,
    searchActive
  });

  if (filteredItems.length === 0) {
    let emptyMessage = 'Noch keine Beiträge erfasst.';
    let emptyIcon = '🎉';
    if (currentTab === 'pending') {
      emptyMessage = 'Keine offenen Beiträge!';
    } else {
      emptyMessage = 'Keine Beiträge vorhanden.';
    }

    if (searchActive) {
      emptyMessage = 'Keine Beiträge gefunden.';
      emptyIcon = '🔍';
    }

    container.innerHTML = `${summaryHtml}
      <div class="empty-state">
        <div class="empty-state-icon">${emptyIcon}</div>
        <div class="empty-state-text">
          ${emptyMessage}
        </div>
      </div>
    `;
    updatePostsScrollTopButtonVisibility();
    return;
  }

  container.innerHTML = `${summaryHtml}${visibleItems
    .map(({ post, status }, index) => createPostCard(post, status, {
      index,
      globalIndex: index + 1,
      totalFiltered: filteredItems.length,
      totalOverall: posts.length,
      tabTotalCount,
      searchActive
    }))
    .join('')}`;

  visibleItems.forEach(({ post, status }) => attachPostEventHandlers(post, status));

  if (visibleCount < filteredItems.length) {
    const loadMoreContainer = document.createElement('div');
    loadMoreContainer.className = 'posts-load-more';

    const loadMoreButton = document.createElement('button');
    loadMoreButton.type = 'button';
    loadMoreButton.className = 'btn btn-secondary posts-load-more__btn';
    loadMoreButton.textContent = 'Weitere Beiträge laden';
    loadMoreButton.addEventListener('click', () => {
      loadMoreButton.disabled = true;
      loadMoreButton.textContent = 'Lade...';
      loadMorePosts(currentTab, { triggeredByScroll: false });
    });

    loadMoreContainer.appendChild(loadMoreButton);
    container.appendChild(loadMoreContainer);

    observeLoadMoreElement(loadMoreContainer, currentTab);
  }

  if (!focusHandled && focusTargetInfo.index !== -1 && focusTargetInfo.post) {
    requestAnimationFrame(() => highlightPostCard(focusTargetInfo.post));
  }
  updatePostsScrollTopButtonVisibility();
}

function attachPostEventHandlers(post, status) {
  const card = document.getElementById(`post-${post.id}`);
  if (!card) {
    return;
  }

  const mergeCheckbox = card.querySelector('.merge-checkbox');
  if (mergeCheckbox) {
    mergeCheckbox.addEventListener('change', () => {
      toggleMergeSelection(post.id, mergeCheckbox);
    });
  }

  const openBtn = card.querySelector('.btn-open');
  if (openBtn) {
    openBtn.addEventListener('click', () => openPost(post.id));
  }

  const deleteBtn = card.querySelector('.btn-delete');
  if (deleteBtn) {
    deleteBtn.addEventListener('click', () => deletePost(post.id));
  }

  const screenshotEl = card.querySelector('.post-screenshot');
  if (screenshotEl && screenshotEl.dataset.screenshot) {
    const url = screenshotEl.dataset.screenshot;
    const openHandler = () => openScreenshotModal(url);
    screenshotEl.addEventListener('click', openHandler);
    screenshotEl.addEventListener('keydown', (event) => {
      if (event.key === 'Enter' || event.key === ' ') {
        event.preventDefault();
        openScreenshotModal(url);
      }
    });
  }

  const toggleButtons = card.querySelectorAll('.profile-line__toggle');
  toggleButtons.forEach((button) => {
    button.addEventListener('click', () => {
      const profileNumber = parseInt(button.dataset.profile, 10);
      const currentStatus = button.dataset.status || 'pending';
      toggleProfileStatus(post.id, profileNumber, currentStatus);
    });
  });

  const editPostBtn = card.querySelector('.btn-edit-post');
  if (editPostBtn) {
    editPostBtn.addEventListener('click', () => {
      openManualPostModal({ mode: 'edit', post });
    });
  }

  const deadlineButton = card.querySelector('.post-deadline__calendar');
  if (deadlineButton) {
    deadlineButton.addEventListener('click', (event) => {
      event.preventDefault();
      event.stopPropagation();
      openNativeDeadlinePicker(post, deadlineButton);
    });
  }

  const clearDeadlineButton = card.querySelector('.post-deadline__clear');
  if (clearDeadlineButton) {
    clearDeadlineButton.addEventListener('click', (event) => {
      event.preventDefault();
      event.stopPropagation();
      clearDeadline(post.id);
    });
  }

  const successCheckbox = card.querySelector('.success-checkbox-input');
  if (successCheckbox) {
    successCheckbox.addEventListener('change', async () => {
      await toggleSuccessStatus(post.id, successCheckbox.checked);
    });
  }

  const targetSelect = card.querySelector('.post-target__select');
  if (targetSelect) {
    targetSelect.dataset.originalValue = String(status.targetCount);
    targetSelect.addEventListener('change', () => {
      const value = parseInt(targetSelect.value, 10);
      if (targetSelect.dataset.originalValue && String(value) === targetSelect.dataset.originalValue) {
        targetSelect.blur();
        return;
      }
      updateTargetInline(post.id, value, targetSelect);
      targetSelect.blur();
    });
  }
}

function openScreenshotModal(url) {
  if (!screenshotModal || !url) {
    return;
  }

  screenshotModalLastFocus = document.activeElement;
  screenshotModalImage.src = url;
  resetScreenshotZoom();
  screenshotModalPreviousOverflow = document.body.style.overflow;
  document.body.style.overflow = 'hidden';

  screenshotModal.removeAttribute('hidden');
  screenshotModal.classList.add('open');

  if (screenshotModalClose) {
    screenshotModalClose.focus();
  }

  if (screenshotModalImage.complete) {
    applyScreenshotModalSize();
  } else {
    const handleLoad = () => {
      applyScreenshotModalSize();
    };
    screenshotModalImage.addEventListener('load', handleLoad, { once: true });
  }
}

function closeScreenshotModal() {
  if (!screenshotModal) {
    return;
  }

  if (!screenshotModal.classList.contains('open')) {
    return;
  }

  resetScreenshotZoom();
  screenshotModal.classList.remove('open');
  screenshotModal.setAttribute('hidden', '');
  screenshotModalImage.src = '';
  document.body.style.overflow = screenshotModalPreviousOverflow;

  if (screenshotModalLastFocus && typeof screenshotModalLastFocus.focus === 'function') {
    screenshotModalLastFocus.focus();
  }
}

function resetScreenshotZoom() {
  screenshotModalZoomed = false;
  if (screenshotModalContent) {
    screenshotModalContent.classList.remove('zoomed');
    screenshotModalContent.style.width = '';
    screenshotModalContent.style.height = '';
    screenshotModalContent.scrollTo({ top: 0, left: 0, behavior: 'auto' });
  }
  if (screenshotModalImage) {
    screenshotModalImage.classList.remove('zoomed');
  }
  applyScreenshotModalSize();
}

function toggleScreenshotZoom() {
  if (!screenshotModalContent || !screenshotModalImage) {
    return;
  }

  screenshotModalZoomed = !screenshotModalZoomed;
  screenshotModalContent.classList.toggle('zoomed', screenshotModalZoomed);
  screenshotModalImage.classList.toggle('zoomed', screenshotModalZoomed);

  if (screenshotModalZoomed) {
    screenshotModalContent.style.width = 'min(95vw, 1300px)';
    screenshotModalContent.style.height = '92vh';
    screenshotModalContent.scrollTo({ top: 0, left: 0, behavior: 'auto' });
  } else {
    screenshotModalContent.style.width = '';
    screenshotModalContent.style.height = '';
    applyScreenshotModalSize();
  }
}

// Create post card HTML
function createPostCard(post, status, meta = {}) {
  const createdDate = formatDateTime(post.created_at) || '—';
  const lastChangeDate = formatDateTime(post.last_change || post.created_at) || '—';

  const baseScreenshotPath = post.screenshot_path
    ? (post.screenshot_path.startsWith('http')
      ? post.screenshot_path
      : `${API_URL.replace(/\/api$/, '')}${post.screenshot_path.startsWith('/') ? '' : '/'}${post.screenshot_path}`)
    : `${API_URL}/posts/${post.id}/screenshot`;

  const screenshotVersion = post.last_change || post.updated_at || post.created_at || '';
  const versionedScreenshotPath = screenshotVersion
    ? `${baseScreenshotPath}${baseScreenshotPath.includes('?') ? '&' : '?'}v=${encodeURIComponent(screenshotVersion)}`
    : baseScreenshotPath;
  const resolvedScreenshotPath = versionedScreenshotPath;
  const screenshotHtml = `
    <div class="post-screenshot" data-screenshot="${escapeHtml(versionedScreenshotPath)}" role="button" tabindex="0" aria-label="Screenshot anzeigen">
      <img src="${escapeHtml(versionedScreenshotPath)}" alt="Screenshot zum Beitrag" loading="lazy" />
    </div>
  `;

  const displayIndex = typeof meta.globalIndex === 'number' ? meta.globalIndex : typeof meta.index === 'number' ? meta.index + 1 : null;
  const totalFiltered = typeof meta.totalFiltered === 'number' ? meta.totalFiltered : posts.length;
  const totalOverall = typeof meta.totalOverall === 'number' ? meta.totalOverall : posts.length;
  const tabTotalCount = typeof meta.tabTotalCount === 'number' ? meta.tabTotalCount : posts.length;
  const searchActive = !!meta.searchActive;
  const indexBadge = displayIndex !== null ? `<span class="post-index">#${String(displayIndex).padStart(2, '0')}</span>` : '';
  const mergeSelectable = mergeMode && currentTab === 'all';
  const mergeCheckboxHtml = mergeSelectable
    ? `
      <label class="merge-select">
        <input type="checkbox" class="merge-checkbox" data-post-id="${post.id}" ${mergeSelection.has(post.id) ? 'checked' : ''}>
        <span>Merge</span>
      </label>
    `
    : '';

  const profileRowsHtml = status.profileStatuses.map((profileStatus) => {
    const classes = ['profile-line', `profile-line--${profileStatus.status}`];
    const isCurrentProfile = parseInt(profileStatus.profile_number, 10) === status.profileNumber;
    if (isCurrentProfile) {
      classes.push('profile-line--current');
    }
    let label = 'Wartet';
    if (profileStatus.status === 'done') {
      const doneDate = formatDateTime(profileStatus.checked_at);
      label = doneDate ? `Erledigt (${doneDate})` : 'Erledigt';
    } else if (profileStatus.status === 'available') {
      label = 'Bereit';
    }

    const toggleLabel = profileStatus.status === 'done'
      ? 'Als offen markieren'
      : 'Als erledigt markieren';

    // Disable toggle button if post is expired
    const toggleDisabled = status.isExpired ? 'disabled' : '';

    const badgeHtml = isCurrentProfile ? '<span class="profile-line__badge">Dein Profil</span>' : '';

    return `
      <div class="${classes.join(' ')}">
        <span class="profile-line__name">${escapeHtml(profileStatus.profile_name)}${badgeHtml}</span>
        <span class="profile-line__status">${escapeHtml(label)}</span>
        <div class="profile-line__actions">
          <button type="button" class="profile-line__toggle" data-post-id="${post.id}" data-profile="${profileStatus.profile_number}" data-status="${profileStatus.status}" ${toggleDisabled}>
            ${escapeHtml(toggleLabel)}
          </button>
        </div>
      </div>
    `;
  }).join('');

  const infoMessages = [];
  if (status.isExpired) {
    infoMessages.push('Deadline ist abgelaufen.');
  }
  if (!status.isCurrentProfileRequired) {
    infoMessages.push('Dieses Profil muss den Beitrag nicht bestätigen.');
  } else if (status.isCurrentProfileDone) {
    infoMessages.push('Für dein Profil erledigt.');
  } else if (status.waitingForNames.length) {
    infoMessages.push(`Wartet auf: ${status.waitingForNames.join(', ')}`);
  }

  const infoHtml = infoMessages.length
    ? `
      <div class="post-hints">
        ${infoMessages.map((message) => `
          <div class="post-hint${message.includes('erledigt') ? ' post-hint--success' : ''}">
            ${escapeHtml(message)}
          </div>
        `).join('')}
      </div>
    `
    : '';

  const directLinkHtml = post.url
    ? `
      <div class="post-link">
        <span class="post-link__label">Direktlink:</span>
        <a href="${escapeHtml(post.url)}" target="_blank" rel="noopener noreferrer" class="post-link__anchor">
          ${escapeHtml(formatUrlForDisplay(post.url))}
        </a>
      </div>
    `
    : '';

  const openButtonHtml = (status.canCurrentProfileCheck && !status.isExpired)
    ? `
      <button class="btn btn-success btn-open">Beitrag öffnen & abhaken</button>
    `
    : '';

  const bodyClasses = ['post-body'];
  if (resolvedScreenshotPath) {
    bodyClasses.push('post-body--with-screenshot');
  }

  let creatorName = typeof post.created_by_name === 'string' && post.created_by_name.trim()
    ? post.created_by_name.trim()
    : null;

  // Remove ", Story ansehen" suffix if present
  if (creatorName && creatorName.endsWith(', Story ansehen')) {
    creatorName = creatorName.slice(0, -16).trim();
  }
  const creatorDisplay = creatorName || 'Unbekannt';

  const titleText = (post.title && post.title.trim()) ? post.title.trim() : creatorDisplay;
  const profileLink = getProfileLinkForPost(post);
  const creatorContent = profileLink
    ? `<a href="${escapeHtml(profileLink)}" target="_blank" rel="noopener noreferrer" class="post-creator__link">${escapeHtml(creatorDisplay)}</a>`
    : escapeHtml(creatorDisplay);

  const deadlineText = formatDeadline(post.deadline_at);
  const hasDeadline = Boolean(post.deadline_at);
  const isOverdue = hasDeadline && (new Date(post.deadline_at).getTime() < Date.now());
  const deadlineClasses = ['post-deadline'];

  let deadlineStyle = '';
  if (hasDeadline) {
    deadlineClasses.push('has-deadline');

    // Calculate color based on time until deadline (smooth gradient)
    const now = Date.now();
    const deadlineTime = new Date(post.deadline_at).getTime();
    const hoursUntilDeadline = (deadlineTime - now) / (1000 * 60 * 60);

    let color;
    if (hoursUntilDeadline < 0) {
      // Overdue - dark red
      color = '#dc2626';
    } else {
      // Smooth gradient from red (0h) to default gray (168h/7 days)
      const maxHours = 168; // 7 days
      const ratio = Math.min(hoursUntilDeadline / maxHours, 1);

      // Color stops: red -> default gray (#4b5563)
      // Red: rgb(220, 38, 38)
      // Gray: rgb(75, 85, 99)

      const r = Math.round(220 - (220 - 75) * ratio);
      const g = Math.round(38 + (85 - 38) * ratio);
      const b = Math.round(38 + (99 - 38) * ratio);
      color = `rgb(${r}, ${g}, ${b})`;
    }

    deadlineStyle = `style="color: ${color};"`;
  }
  if (isOverdue) {
    deadlineClasses.push('overdue');
  }

  return `
    <div class="post-card ${status.isComplete ? 'complete' : ''}" id="post-${post.id}">
      <div class="post-header">
        <div class="post-title-with-checkbox">
          ${mergeCheckboxHtml}
          ${indexBadge}
          <div class="post-title">${escapeHtml(titleText)}</div>
          <label class="success-checkbox success-checkbox--header">
            <input type="checkbox" class="success-checkbox-input" data-post-id="${post.id}" ${post.is_successful ? 'checked' : ''}>
            <span>Erfolgreich</span>
          </label>
        </div>
        <div class="post-header-right">
          <div class="post-status ${status.isComplete ? 'complete' : ''}">
            ${status.checkedCount}/${status.targetCount} ${status.isComplete ? '✓' : ''}
          </div>
          <div class="post-target">
            <span>Benötigte Profile:</span>
            <select class="control-select post-target__select" data-post-id="${post.id}">
              ${Array.from({ length: MAX_PROFILES }, (_, index) => index + 1)
                .map((value) => `
                  <option value="${value}" ${value === status.targetCount ? 'selected' : ''}>${value}</option>
                `).join('')}
            </select>
          </div>
        </div>
      </div>

      <div class="${bodyClasses.join(' ')}">
        <div class="post-screenshot-wrapper">
          ${screenshotHtml}
        </div>

        <div class="post-content">
          <div class="post-meta">
            <div class="post-info">
              <div>Erstellt: ${escapeHtml(createdDate)}</div>
              <div>Letzte Änderung: ${escapeHtml(lastChangeDate)}</div>
            </div>
            <div class="post-creator">Erstellt von: ${creatorContent}</div>
          </div>

          <div class="post-deadline-row" data-post-id="${post.id}">
            <span class="${deadlineClasses.join(' ')}" ${deadlineStyle}>Deadline: ${escapeHtml(deadlineText)}</span>
            <button type="button" class="post-deadline__calendar" aria-label="Deadline bearbeiten" data-post-id="${post.id}">
              📅
            </button>
            ${hasDeadline ? `
              <button type="button" class="post-deadline__clear" aria-label="Deadline entfernen" data-post-id="${post.id}">
                ✕
              </button>
            ` : ''}
          </div>
          ${directLinkHtml}

          <div class="post-profiles">
            ${profileRowsHtml}
          </div>

          ${infoHtml}

      <div class="post-actions">
        ${openButtonHtml}
        <button type="button" class="btn btn-secondary btn-edit-post">Bearbeiten</button>
        ${post.url ? `
          <a href="${escapeHtml(post.url)}" target="_blank" rel="noopener noreferrer" class="btn btn-secondary btn-direct-link">
            Direkt öffnen
          </a>
        ` : ''}
        <button class="btn btn-danger btn-delete">Löschen</button>
          </div>
        </div>
      </div>
    </div>
  `;
}

// Open post and auto-check
async function openPost(postId) {
  if (!ensureProfileSelected()) {
    return;
  }

  const post = posts.find((item) => item.id === postId);
  if (!post) {
    alert('Beitrag konnte nicht gefunden werden.');
    return;
  }

  if (!post.url) {
    alert('Für diesen Beitrag ist kein Direktlink vorhanden.');
    return;
  }

  const status = computePostStatus(post);

  if (!status.isCurrentProfileRequired) {
    alert('Dieses Profil muss den Beitrag nicht bestätigen.');
    return;
  }

  if (status.isCurrentProfileDone) {
    window.open(post.url, '_blank');
    return;
  }

  try {
    const ignoreOrder = true;
    const response = await apiFetch(`${API_URL}/check-by-url`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        url: post.url,
        profile_number: currentProfile,
        ignore_order: ignoreOrder
      })
    });

    if (!response.ok) {
      if (response.status === 409) {
        const data = await response.json().catch(() => null);
        if (data && data.error) {
          alert(data.error);
          return;
        }
      }
      throw new Error('Failed to check post');
    }

    window.open(post.url, '_blank');

    await fetchPosts({ showLoader: false });
  } catch (error) {
    alert('Fehler beim Abhaken des Beitrags');
    console.error('Error checking post:', error);
  }
}

async function toggleSuccessStatus(postId, isSuccessful) {
  try {
    const response = await apiFetch(`${API_URL}/posts/${postId}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ is_successful: isSuccessful })
    });

    if (!response.ok) {
      alert('Status konnte nicht geändert werden.');
      return;
    }

    const updatedPost = await response.json();
    posts = posts.map((item) => (item.id === postId ? updatedPost : item));
    renderPosts();
  } catch (error) {
    console.error('Error updating success status:', error);
    alert('Status konnte nicht geändert werden.');
  }
}

async function toggleProfileStatus(postId, profileNumber, currentStatus) {
  if (!ensureProfileSelected()) {
    return;
  }
  if (!profileNumber) {
    return;
  }

  const desiredStatus = currentStatus === 'done' ? 'pending' : 'done';

  try {
    const response = await apiFetch(`${API_URL}/posts/${postId}/profile-status`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        profile_number: profileNumber,
        status: desiredStatus,
        ignore_order: true
      })
    });

    if (!response.ok) {
      const data = await response.json().catch(() => null);
      const message = data && data.error ? data.error : 'Status konnte nicht geändert werden.';
      alert(message);
      return;
    }

    const updatedPost = await response.json();
    posts = posts.map((item) => (item.id === postId ? updatedPost : item));
    renderPosts();
  } catch (error) {
    console.error('Error updating profile status:', error);
    alert('Profilstatus konnte nicht geändert werden.');
  }
}

function populateManualPostForm(post) {
  if (!manualPostForm || !post) {
    return;
  }

  if (manualPostUrlInput) {
    const normalizedUrl = typeof post.url === 'string' ? normalizeFacebookPostUrl(post.url) : null;
    manualPostUrlInput.value = normalizedUrl || post.url || '';
    manualPostUrlInput.disabled = true;
    manualPostUrlInput.readOnly = true;
  }

  if (manualPostTitleInput) {
    manualPostTitleInput.value = post.title || '';
  }

  if (manualPostTargetSelect) {
    const targetValue = parseInt(post.target_count, 10);
    manualPostTargetSelect.value = Number.isNaN(targetValue) ? '1' : String(targetValue);
  }

  if (manualPostCreatorInput) {
    manualPostCreatorInput.value = post.created_by_name || '';
  }

  if (manualPostDeadlineInput) {
    const existingValue = toDateTimeLocalValue(post.deadline_at);
    manualPostDeadlineInput.value = existingValue || getDefaultDeadlineInputValue();
  }

  clearManualPostMessage();
}

function resetManualPostForm({ keepMessages = false } = {}) {
  if (!manualPostForm) {
    return;
  }

  manualPostMode = 'create';
  manualPostEditingId = null;

  manualPostForm.reset();

  if (manualPostTargetSelect) {
    manualPostTargetSelect.value = '1';
  }

  if (manualPostUrlInput) {
    manualPostUrlInput.disabled = false;
    manualPostUrlInput.readOnly = false;
    manualPostUrlInput.value = '';
  }

  if (manualPostTitleInput) {
    manualPostTitleInput.value = '';
  }

  if (manualPostCreatorInput) {
    manualPostCreatorInput.value = '';
  }

  if (manualPostDeadlineInput) {
    manualPostDeadlineInput.value = getDefaultDeadlineInputValue();
  }

  if (manualPostModalTitle) {
    manualPostModalTitle.textContent = 'Beitrag hinzufügen';
  }

  if (manualPostSubmitButton) {
    manualPostSubmitButton.textContent = 'Speichern';
  }

  if (!keepMessages) {
    clearManualPostMessage();
  }
}

async function saveDeadline(postId, deadlineIso) {
  try {
    const response = await apiFetch(`${API_URL}/posts/${postId}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ deadline_at: deadlineIso })
    });

    if (!response.ok) {
      const data = await response.json().catch(() => null);
      const message = data && data.error ? data.error : 'Deadline konnte nicht gespeichert werden.';
      alert(message);
      return false;
    }

    const updatedPost = await response.json();
    posts = posts.map((item) => (item.id === postId ? updatedPost : item));
    renderPosts();
    return true;
  } catch (error) {
    console.error('Error updating deadline:', error);
    alert('Deadline konnte nicht gespeichert werden.');
    return false;
  }
}

async function updateTargetInline(postId, value, selectElement) {
  if (!selectElement) {
    return;
  }

  if (Number.isNaN(value) || value < 1 || value > MAX_PROFILES) {
    renderPosts();
    return;
  }

  try {
    const response = await apiFetch(`${API_URL}/posts/${postId}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ target_count: value })
    });

    if (!response.ok) {
      const data = await response.json().catch(() => null);
      const message = data && data.error ? data.error : 'Anzahl konnte nicht gespeichert werden.';
      alert(message);
      if (selectElement.dataset.originalValue) {
        selectElement.value = selectElement.dataset.originalValue;
      }
      renderPosts();
      return;
    }

    const updatedPost = await response.json();
    posts = posts.map((item) => (item.id === postId ? updatedPost : item));
    renderPosts();
  } catch (error) {
    console.error('Error updating target count:', error);
    alert('Anzahl konnte nicht gespeichert werden.');
    if (selectElement.dataset.originalValue) {
      selectElement.value = selectElement.dataset.originalValue;
    }
    renderPosts();
  }
}

async function handleManualPostSubmit(event) {
  event.preventDefault();

  if (!manualPostForm) {
    return;
  }

  clearManualPostMessage();

  const urlValue = manualPostUrlInput ? manualPostUrlInput.value.trim() : '';
  if (!urlValue) {
    displayManualPostMessage('Bitte gib einen Direktlink an.', 'error');
    if (manualPostUrlInput) {
      manualPostUrlInput.focus();
    }
    return;
  }

  const targetValue = manualPostTargetSelect ? manualPostTargetSelect.value : '1';
  const parsedTarget = parseInt(targetValue, 10);
  if (Number.isNaN(parsedTarget) || parsedTarget < 1 || parsedTarget > MAX_PROFILES) {
    displayManualPostMessage('Die Anzahl der benötigten Profile muss zwischen 1 und 5 liegen.', 'error');
    return;
  }

  const creatorValue = manualPostCreatorInput ? manualPostCreatorInput.value.trim() : '';
  const deadlineValue = manualPostDeadlineInput ? manualPostDeadlineInput.value : '';
  const titleValue = manualPostTitleInput ? manualPostTitleInput.value.trim() : '';

  const cleanedUrl = normalizeFacebookPostUrl(urlValue);
  if (!cleanedUrl) {
    displayManualPostMessage('Bitte gib einen gültigen Facebook-Link an.', 'error');
    if (manualPostUrlInput) {
      manualPostUrlInput.focus();
      manualPostUrlInput.select?.();
    }
    return;
  }

  const payload = {
    url: cleanedUrl,
    target_count: parsedTarget
  };

  if (titleValue) {
    payload.title = titleValue;
  }

  if (creatorValue) {
    payload.created_by_name = creatorValue;
  }

  const normalizedDeadline = normalizeDeadlineInput(deadlineValue);
  if (normalizedDeadline) {
    payload.deadline_at = normalizedDeadline;
  }

  const submitButtons = manualPostForm.querySelectorAll('button, input[type="submit"]');
  submitButtons.forEach((btn) => {
    btn.disabled = true;
  });

  try {
    if (manualPostMode === 'edit' && manualPostEditingId) {
      const updatePayload = {
        target_count: parsedTarget,
        title: titleValue || ''
      };

      if (creatorValue || creatorValue === '') {
        updatePayload.created_by_name = creatorValue || null;
      }

      updatePayload.deadline_at = normalizedDeadline;

      const response = await apiFetch(`${API_URL}/posts/${manualPostEditingId}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(updatePayload)
      });

      if (!response.ok) {
        const data = await response.json().catch(() => null);
        const message = data && data.error ? data.error : 'Beitrag konnte nicht aktualisiert werden.';
        displayManualPostMessage(message, 'error');
        return;
      }

      const updatedPost = await response.json();
      posts = posts.map((item) => (item.id === manualPostEditingId ? updatedPost : item));
      renderPosts();
      closeManualPostModal();
    } else {
      const response = await apiFetch(`${API_URL}/posts`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload)
      });

      if (!response.ok) {
        const data = await response.json().catch(() => null);
        if (response.status === 409 && data && data.error) {
          displayManualPostMessage(data.error, 'error');
        } else {
          const message = data && data.error ? data.error : 'Beitrag konnte nicht erstellt werden.';
          displayManualPostMessage(message, 'error');
        }
        return;
      }

      const createdPost = await response.json();
      posts = [createdPost, ...posts.filter((item) => item.id !== createdPost.id)];
      renderPosts();
      displayManualPostMessage('Beitrag wurde erstellt.', 'success');
      resetManualPostForm({ keepMessages: true });
      if (manualPostUrlInput) {
        manualPostUrlInput.focus();
      }
    }
  } catch (error) {
    console.error('Error creating manual post:', error);
    displayManualPostMessage('Beitrag konnte nicht erstellt werden.', 'error');
  } finally {
    submitButtons.forEach((btn) => {
      btn.disabled = false;
    });
  }
}

// Delete post
async function deletePost(postId) {
  if (!confirm('Beitrag wirklich löschen?')) {
    return;
  }

  try {
    const response = await apiFetch(`${API_URL}/posts/${postId}`, {
      method: 'DELETE'
    });

    if (!response.ok) {
      throw new Error('Failed to delete post');
    }

    await fetchPosts({ showLoader: false });
  } catch (error) {
    alert('Fehler beim Löschen des Beitrags');
    console.error('Error deleting post:', error);
  }
}

// Utility functions
function showLoading() {
  document.getElementById('loading').style.display = 'block';
  document.getElementById('postsContainer').style.display = 'none';
}

function hideLoading() {
  document.getElementById('loading').style.display = 'none';
  document.getElementById('postsContainer').style.display = 'block';
}

function showError(message) {
  const errorEl = document.getElementById('error');
  errorEl.textContent = message;
  errorEl.style.display = 'block';
}

function hideError() {
  document.getElementById('error').style.display = 'none';
}

function escapeHtml(unsafe) {
  if (unsafe === null || unsafe === undefined) {
    unsafe = '';
  }

  if (typeof unsafe !== 'string') {
    unsafe = String(unsafe);
  }

  return unsafe
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');
}

// Auto-check on page load if URL parameter is present
function checkAutoCheck() {
  const urlParams = new URLSearchParams(window.location.search);
  const autoCheckUrl = urlParams.get('check');

  if (autoCheckUrl) {
    if (!ensureProfileSelected()) {
      return;
    }
    // Try to check this URL automatically
    apiFetch(`${API_URL}/check-by-url`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        url: decodeURIComponent(autoCheckUrl),
        profile_number: currentProfile,
        ignore_order: true
      })
    }).then(() => {
      // Remove nur den check-Parameter aus der URL
      try {
        const url = new URL(window.location.href);
        url.searchParams.delete('check');
        const paramsString = url.searchParams.toString();
        const newUrl = paramsString ? `${url.pathname}?${paramsString}${url.hash}` : `${url.pathname}${url.hash}`;
        window.history.replaceState({}, document.title, newUrl);
      } catch (error) {
        console.warn('Konnte check-Parameter nicht entfernen:', error);
      }
      fetchPosts({ showLoader: false });
    }).catch(console.error);
  }
}

if (screenshotModalClose) {
  screenshotModalClose.addEventListener('click', closeScreenshotModal);
}

if (screenshotModalBackdrop) {
  screenshotModalBackdrop.addEventListener('click', closeScreenshotModal);
}

if (screenshotModalImage) {
  screenshotModalImage.addEventListener('click', (event) => {
    event.stopPropagation();
    toggleScreenshotZoom();
  });

  screenshotModalImage.addEventListener('keydown', (event) => {
    if (event.key === 'Enter' || event.key === ' ') {
      event.preventDefault();
      toggleScreenshotZoom();
    }
  });

  screenshotModalImage.setAttribute('tabindex', '0');
  screenshotModalImage.setAttribute('role', 'button');
  screenshotModalImage.setAttribute('aria-label', 'Screenshot vergrößern oder verkleinern');
}

document.addEventListener('keydown', (event) => {
  if (event.key !== 'Escape') {
    return;
  }

  if (manualPostModal && manualPostModal.classList.contains('open')) {
    event.preventDefault();
    closeManualPostModal();
    return;
  }

  if (bookmarkPanelVisible) {
    event.preventDefault();
    toggleBookmarkPanel(false);
    return;
  }

  if (screenshotModal && screenshotModal.classList.contains('open')) {
    if (screenshotModalZoomed) {
      resetScreenshotZoom();
      return;
    }
    closeScreenshotModal();
  }
});

window.addEventListener('resize', () => {
  if (screenshotModal && screenshotModal.classList.contains('open') && !screenshotModalZoomed) {
    applyScreenshotModalSize();
  }
  updatePostsScrollTopButtonVisibility();
});

window.addEventListener('scroll', () => {
  updatePostsScrollTopButtonVisibility();
}, { passive: true });

window.addEventListener('app:view-change', (event) => {
  const view = event && event.detail ? event.detail.view : null;
  if (view === 'posts') {
    maybeAutoOpenPending('view');
  } else {
    cancelPendingAutoOpen(false);
  }
  updatePostsScrollTopButtonVisibility();
});

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'visible' && pendingAutoOpenEnabled) {
    if (pendingAutoOpenTimerId) {
      clearTimeout(pendingAutoOpenTimerId);
      pendingAutoOpenTimerId = null;
    }
    pendingAutoOpenTriggered = false;
    maybeAutoOpenPending('visibility', PENDING_AUTO_OPEN_DELAY_MS);
  }
});

// Initialize
async function bootstrapApp() {
  const authenticated = await ensureAuthenticated();
  if (!authenticated) {
    return;
  }

  markAppReady();
  bindLogoutButton();
  initializeBookmarks();
  loadAutoRefreshSettings();
  initializeFocusParams();
  initializeTabFromUrl();
  updateMergeControlsUI();
  loadSortMode();
  resetManualPostForm();
  loadProfile();
  startProfilePolling();
  fetchPosts();
  checkAutoCheck();
  startUpdatesStream();
  applyAutoRefreshSettings();
  updatePostsScrollTopButtonVisibility();
}

bootstrapApp();
