(function(){
  document.body.classList.add('photo-contest-admin-ready');
  const state = {
    photos: [],
    filtered: [],
    stats: {},
    walks: [],
    groups: [],
    settings: null,
    selectedGroupIds: new Set(),
    view: localStorage.getItem('pyrkPhotoView') || 'gallery',
    page: 1,
    perPage: 24,
    selectedWalkId: new URLSearchParams(window.location.search).get('walk_id') || '',
    settingsLoading: false,
    settingsError: ''
  };

  const $ = id => document.getElementById(id);
  const fmtDate = value => value ? new Date(String(value).replace(' ', 'T')).toLocaleString() : '—';
  const esc = value => Pyrk.esc(value);

  function isDeletedWalk(walk) {
    const status = String(walk?.status || '').toLowerCase().trim();
    const title = String(walk?.title || '').toLowerCase().trim();
    const token = String(walk?.checkin_token || '').toLowerCase().trim();
    const id = Number(walk?.id || 0);
    const date = String(walk?.walk_date || '').slice(0, 10);

    const today = new Date();
    const todayIso = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
    const isPast = date && date < todayIso;

    const legacySeed = (id === 1 || id === 2)
      || title.includes('sunrise waterfront')
      || title.includes('community park')
      || token.startsWith('pyrk-walk-sunrise-')
      || token.startsWith('pyrk-walk-park-');

    return ['deleted', 'archived', 'removed', 'remove', 'trash', 'trashed', 'cancelled', 'canceled', 'inactive', 'hidden'].includes(status)
      || isPast
      || legacySeed
      || !!walk?.deleted_at
      || !!walk?.removed_at
      || !!walk?.archived_at
      || !!walk?.cancelled_at
      || !!walk?.canceled_at
      || Number(walk?.is_deleted || walk?.deleted || walk?.is_archived || walk?.archived || walk?.is_removed || 0) === 1
      || !!walk?.deleted_by
      || !!walk?.removed_by
      || !!walk?.archived_by;
  }

  function visibleWalks(walks) {
    return (walks || []).filter(w => !isDeletedWalk(w));
  }


  function statusPill(status) {
    const cls = status === 'approved' ? 'ok' : status === 'rejected' ? 'bad' : status === 'winner' ? 'gold' : status === 'closed' ? 'bad' : status === 'open' ? 'ok' : 'muted';
    const label = status === 'winner' ? 'Winner' : status === 'open' ? 'Open' : status === 'closed' ? 'Closed' : (status || 'pending');
    return `<span class="pill ${cls}">${esc(label)}</span>`;
  }

  function permissionLabel(value) {
    return ({
      any_member: 'Anyone logged in',
      walk_registered: 'Walk registrants',
      walk_checked_in: 'Checked-in attendees',
      selected_groups: 'Selected voting groups',
      closed: 'Closed'
    })[value || 'walk_registered'] || 'Walk registrants';
  }

  function voteModeLabel(value) {
    return 'One vote per walk contest';
  }

  function photoLimitLabel(value) {
    const n = Math.max(1, Math.min(50, Number(value || 1)));
    return `${n} photo${n === 1 ? '' : 's'} per participant`;
  }

  function avatar(photo) {
    if (photo.uploaded_avatar) {
      return `<img class="avatar-img" src="../${esc(photo.uploaded_avatar)}" alt="${esc(photo.uploaded_by)}">`;
    }
    return `<span class="avatar">${esc((photo.uploaded_by || 'M').slice(0,1).toUpperCase())}</span>`;
  }

  function photoSrc(photo) {
    return `../${esc(photo.thumbnail_path || photo.file_path || '')}`;
  }

  function fullPhotoSrc(photo) {
    return `../${esc(photo.file_path || photo.thumbnail_path || '')}`;
  }

  function hasPhotoLocation(photo) {
    return photo && photo.photo_latitude !== null && photo.photo_latitude !== undefined && photo.photo_latitude !== '' && photo.photo_longitude !== null && photo.photo_longitude !== undefined && photo.photo_longitude !== '';
  }

  function photoMapUrl(photo) {
    if (!hasPhotoLocation(photo)) return '';
    return `https://www.google.com/maps?q=${encodeURIComponent(String(photo.photo_latitude) + ',' + String(photo.photo_longitude))}`;
  }

  function photoLocationChip(photo) {
    if (!hasPhotoLocation(photo)) return '<span class="photo-location-badge muted">No location</span>';
    const accuracy = photo.photo_location_accuracy ? ` · ±${Math.round(Number(photo.photo_location_accuracy))}m` : '';
    return `<a class="photo-location-link" href="${photoMapUrl(photo)}" target="_blank" rel="noopener">📍 View location${esc(accuracy)}</a>`;
  }

  function getSelectedWalk() {
    const id = Number(state.selectedWalkId || $('walkSelect')?.value || 0);
    return state.walks.find(w => Number(w.id) === id) || null;
  }

  function renderCurrentConfig() {
    const box = $('contestCurrentConfig');
    if (!box) return;

    const walk = getSelectedWalk();
    if (!walk) {
      box.innerHTML = `<div class="current-config-empty"><strong>Choose a walk to see its current voting configuration.</strong><span class="muted">Each walk has independent photo voting rules.</span></div>`;
      return;
    }

    if (state.settingsLoading) {
      box.innerHTML = `<div class="current-config-empty is-loading"><strong>Loading saved rules for ${esc(walk.title || 'selected walk')}...</strong><span class="muted">Reading the current per-walk configuration.</span></div>`;
      return;
    }

    if (state.settingsError) {
      box.innerHTML = `<div class="current-config-empty bad"><strong>Could not load saved rules.</strong><span>${esc(state.settingsError)}</span></div>`;
      return;
    }

    const settings = state.settings || {};
    const status = settings.voting_status || walk.contest_voting_status || walk.legacy_voting_status || 'open';
    const permission = settings.voting_permission || walk.voting_permission || 'walk_registered';
    const deadline = settings.voting_deadline || walk.contest_deadline || walk.legacy_voting_deadline || '';
    const groups = settings.groups || [];
    const groupLabel = groups.length ? groups.map(g => g.group_name).join(', ') : 'No voting groups selected';
    const selfVote = Number(settings.allow_self_vote ?? walk.allow_self_vote ?? 1) === 1 ? 'Allowed' : 'Not allowed';
    const ownerNames = Number(settings.show_photo_owner_name ?? walk.show_photo_owner_name ?? 0) === 1 ? 'Visible to members' : 'Masked / anonymous';
    const photoLimit = settings.max_photos_per_participant ?? walk.max_photos_per_participant ?? 1;
    const isClosed = status === 'closed' || permission === 'closed';

    box.innerHTML = `<div class="current-config-card ${isClosed ? 'closed' : 'open'}">
      <div class="current-config-main">
        <span class="pill ${isClosed ? 'bad' : 'ok'}">${isClosed ? 'Voting closed' : 'Voting open'}</span>
        <h3>${esc(walk.title || 'Selected walk')}</h3>
        <p class="muted">${Pyrk.formatDate(walk.walk_date)} ${esc(walk.start_time || '')} · ${esc(walk.route_name || '')}</p>
      </div>
      <div class="current-config-metrics">
        <div><span>Who can vote</span><strong>${esc(permissionLabel(permission))}</strong></div>
        <div><span>Deadline</span><strong>${esc(deadline ? fmtDate(deadline) : 'No deadline')}</strong></div>
        <div><span>Self voting</span><strong>${esc(selfVote)}</strong></div>
        <div><span>Picture owner names</span><strong>${esc(ownerNames)}</strong></div>
        <div><span>Upload limit</span><strong>${esc(photoLimitLabel(photoLimit))}</strong></div>
        <div><span>Voting groups</span><strong>${esc(permission === 'selected_groups' ? groupLabel : 'Not required')}</strong></div>
      </div>
    </div>`;
  }

  function localDateTimeForInput(value) {
    if (!value) return '';
    const normalized = String(value).replace(' ', 'T').slice(0, 16);
    return normalized;
  }

  function renderStats() {
    const stats = state.stats || {};
    const box = $('photoStats');
    if (!box) return;
    const data = [
      ['Total photos', stats.total || 0, 'All entries'],
      ['Pending', stats.pending || 0, 'Needs approval'],
      ['Approved', stats.approved || 0, 'Visible'],
      ['Total votes', stats.total_votes || 0, 'Activity'],
      ['Participants', stats.participant_count || 0, 'Members']
    ];
    box.innerHTML = data.map(([label, value, help]) => `<button class="stat photo-stat-card" type="button"><span>${esc(label)}</span><strong>${Number(value).toLocaleString()}</strong><small class="muted">${esc(help)}</small></button>`).join('');
  }

  function renderWalkOptions() {
    const select = $('walkSelect');
    if (!select) return;
    const preferred = state.selectedWalkId || select.value || '';
    select.innerHTML = '<option value="">Choose a walk to manage voting rules</option>' + state.walks.map(w => {
      const status = w.contest_voting_status || w.legacy_voting_status || 'open';
      const permission = w.voting_permission || 'walk_registered';
      return `<option value="${Number(w.id)}">${esc(w.title)} · ${Pyrk.formatDate(w.walk_date)} · ${esc(status)} · ${esc(permissionLabel(permission))} · ${esc(photoLimitLabel(w.max_photos_per_participant || 1))}</option>`;
    }).join('');
    if (preferred && Array.from(select.options).some(o => o.value === String(preferred))) {
      select.value = String(preferred);
      state.selectedWalkId = String(preferred);
    } else {
      state.selectedWalkId = select.value || '';
    }
  }

  function renderGroupFilter() {
    const select = $('groupFilter');
    if (!select) return;
    const current = select.value;
    select.innerHTML = '<option value="">All groups</option>' + state.groups.map(g => `<option value="${Number(g.id)}">${esc(g.group_name)} (${Number(g.member_count || 0)})</option>`).join('');
    if (current && state.walks.some(w => Number(w.id) === Number(current))) {
      select.value = current;
    } else if (current) {
      select.value = '';
      state.selectedWalkId = '';
    }
  }


  function isGroupPermission(permission = '') {
    return String(permission || '') === 'selected_groups';
  }

  function currentVotingPermission() {
    return $('votingPermission')?.value || state.settings?.voting_permission || getSelectedWalk()?.voting_permission || 'walk_registered';
  }

  function syncGroupSelectorVisibility(permission = currentVotingPermission(), options = {}) {
    const needsGroups = isGroupPermission(permission);
    const selectedWalkId = state.selectedWalkId || $('walkSelect')?.value || '';
    const disabled = !selectedWalkId || !needsGroups;
    const selector = $('contestGroupSelector');
    const search = $('contestGroupSearch');
    const results = $('contestGroupResults');
    const count = $('selectedGroupsCount');

    if (selector) {
      selector.classList.toggle('is-needed', needsGroups);
      selector.classList.toggle('is-disabled', disabled);
      selector.classList.toggle('is-not-required', !!selectedWalkId && !needsGroups);
      selector.setAttribute('aria-disabled', disabled ? 'true' : 'false');
    }

    if (search) {
      search.disabled = disabled;
      search.placeholder = needsGroups ? 'Search groups...' : 'Groups are not required for this voting rule';
      if (!needsGroups) search.value = '';
    }

    if (!needsGroups && !options.keepSelection) {
      state.selectedGroupIds = new Set();
    }

    if (count) {
      count.textContent = needsGroups ? `${state.selectedGroupIds.size} selected` : 'Not required';
      count.className = `pill ${needsGroups ? '' : 'muted'}`;
    }

    if (results && !needsGroups) {
      results.innerHTML = `<div class="groups-not-required-card">
        <strong>Groups are turned off for this rule.</strong>
        <p class="muted">For “Anyone logged in”, “Registered members”, or “Checked-in members”, Pyrk automatically decides who can vote. You do not need to select groups or users.</p>
      </div>`;
      return false;
    }

    return needsGroups;
  }

  function renderContestSettings() {
    const settings = state.settings || null;
    const selectedWalkId = state.selectedWalkId || $('walkSelect')?.value || '';
    const disabled = !selectedWalkId;
    renderCurrentConfig();

    ['votingStatus','votingPermission','voteMode','votingDeadline','allowSelfVote','showPhotoOwnerName','maxPhotosPerParticipant','rulesNote','contestGroupSearch'].forEach(id => {
      const el = $(id);
      if (el) el.disabled = disabled;
    });
    ['closeContestBtn','reopenContestBtn'].forEach(id => {
      const el = $(id);
      if (el) el.disabled = disabled;
    });
    const form = $('contestSettingsForm');
    if (form) form.classList.toggle('is-disabled', disabled);
    syncGroupSelectorVisibility(currentVotingPermission(), {keepSelection: true});
    const settingsWalkId = $('settingsWalkId');
    if (settingsWalkId) settingsWalkId.value = selectedWalkId;

    if (!settings) {
      const walk = getSelectedWalk();
      const fallbackStatus = walk?.contest_voting_status || walk?.legacy_voting_status || 'open';
      const fallbackPermission = walk?.voting_permission || 'walk_registered';
      if ($('votingStatus')) $('votingStatus').value = fallbackStatus;
      if ($('votingPermission')) $('votingPermission').value = fallbackPermission;
      if ($('voteMode')) $('voteMode').value = 'one_per_contest';
      if ($('votingDeadline')) $('votingDeadline').value = localDateTimeForInput(walk?.contest_deadline || walk?.legacy_voting_deadline || '');
      if ($('allowSelfVote')) $('allowSelfVote').checked = Number(walk?.allow_self_vote ?? 1) === 1;
      if ($('showPhotoOwnerName')) $('showPhotoOwnerName').checked = Number(walk?.show_photo_owner_name ?? 0) === 1;
      if ($('maxPhotosPerParticipant')) $('maxPhotosPerParticipant').value = Math.max(1, Math.min(50, Number(walk?.max_photos_per_participant || 1)));
      if ($('rulesNote')) $('rulesNote').value = '';
      const pill = $('contestStatusPill');
      if (pill) {
        const isClosed = !selectedWalkId || fallbackStatus === 'closed' || fallbackPermission === 'closed';
        pill.textContent = selectedWalkId ? (isClosed ? 'Closed' : 'Open') : 'Choose a walk';
        pill.className = `pill ${selectedWalkId ? (isClosed ? 'bad' : 'ok') : 'muted'}`;
      }
      if (!isGroupPermission(fallbackPermission)) {
        state.selectedGroupIds = new Set();
      }
      renderContestGroups();
      syncGroupSelectorVisibility(fallbackPermission);
      return;
    }

    if ($('votingStatus')) $('votingStatus').value = settings.voting_status || 'open';
    if ($('votingPermission')) $('votingPermission').value = settings.voting_permission || 'walk_registered';
    if ($('voteMode')) $('voteMode').value = settings.vote_mode || 'one_per_contest';
    if ($('votingDeadline')) $('votingDeadline').value = localDateTimeForInput(settings.voting_deadline);
    if ($('allowSelfVote')) $('allowSelfVote').checked = Number(settings.allow_self_vote) === 1;
    if ($('showPhotoOwnerName')) $('showPhotoOwnerName').checked = Number(settings.show_photo_owner_name || 0) === 1;
    if ($('maxPhotosPerParticipant')) $('maxPhotosPerParticipant').value = Math.max(1, Math.min(50, Number(settings.max_photos_per_participant || 1)));
    if ($('rulesNote')) $('rulesNote').value = settings.rules_note || '';

    state.selectedGroupIds = new Set((settings.groups || []).map(g => Number(g.id)));
    const pill = $('contestStatusPill');
    if (pill) {
      const isClosed = (settings.voting_status || '') === 'closed' || (settings.voting_permission || '') === 'closed';
      pill.textContent = isClosed ? 'Closed' : 'Open';
      pill.className = `pill ${isClosed ? 'bad' : 'ok'}`;
    }
    if (!isGroupPermission($('votingPermission')?.value || '')) {
      state.selectedGroupIds = new Set();
    }
    renderContestGroups();
    syncGroupSelectorVisibility($('votingPermission')?.value || 'walk_registered');
  }

  function renderContestGroups() {
    const box = $('contestGroupResults');
    const count = $('selectedGroupsCount');
    const permission = currentVotingPermission();
    if (!syncGroupSelectorVisibility(permission, {keepSelection: isGroupPermission(permission)})) return;
    if (count) count.textContent = `${state.selectedGroupIds.size} selected`;
    if (!box) return;
    const q = ($('contestGroupSearch')?.value || '').trim().toLowerCase();
    const visible = state.groups.filter(g => !q || [g.group_name, g.description, g.status].some(v => String(v || '').toLowerCase().includes(q)));
    box.innerHTML = visible.map(g => {
      const id = Number(g.id);
      const checked = state.selectedGroupIds.has(id);
      return `<label class="contest-group-option ${checked ? 'selected' : ''}">
        <input type="checkbox" value="${id}" ${checked ? 'checked' : ''}>
        <span class="group-avatar">${esc((g.group_name || 'G').slice(0,1).toUpperCase())}</span>
        <span class="group-copy"><strong>${esc(g.group_name)}</strong><small>${Number(g.member_count || 0)} members · ${esc(g.status || 'active')}</small></span>
      </label>`;
    }).join('') || '<div class="empty-state mini"><strong>No groups found</strong><p class="muted">Create groups from Admin > People > Member groups.</p></div>';
  }

  function applyLocalFilters() {
    const q = ($('photoSearch')?.value || '').trim().toLowerCase();
    let list = [...state.photos];
    if (q) {
      list = list.filter(p => [p.title, p.caption, p.uploaded_by, p.uploaded_email, p.uploaded_mobile, p.walk_title, p.route_name, p.status, p.voting_permission, p.contest_groups, p.photo_location_source].some(v => String(v || '').toLowerCase().includes(q)));
    }
    state.filtered = list;
    state.page = Math.min(state.page, Math.max(1, Math.ceil(list.length / state.perPage)));
  }

  function pagedPhotos() {
    const start = (state.page - 1) * state.perPage;
    return state.filtered.slice(start, start + state.perPage);
  }

  function renderCards(list) {
    const box = $('adminPhotosGallery');
    if (!box) return;
    box.hidden = state.view !== 'gallery';
    if (box.hidden) return;

    box.innerHTML = list.map(p => {
      const contestStatus = p.contest_voting_status || p.voting_status || 'open';
      return `<article class="enterprise-photo-card contest-photo-card ${esc(p.status)}">
        <div class="enterprise-photo-thumb" onclick="previewPhoto(${Number(p.id)})" role="button" tabindex="0">
          <img src="${photoSrc(p)}" alt="${esc(p.title)}">
          <span class="photo-status-chip">${statusPill(p.status)}</span>
          <span class="photo-vote-chip">⭐ ${Number(p.votes_count || 0)} votes</span>
        </div>
        <div class="enterprise-photo-body">
          <div class="photo-title-row">
            <h3>${esc(p.title || 'Untitled photo')}</h3>
            ${p.status === 'winner' ? '<span class="winner-crown">🏆</span>' : ''}
          </div>
          <p class="photo-caption">${esc(p.caption || 'No caption')}</p>
          <div class="photo-member-line">${avatar(p)}<div><strong>${esc(p.uploaded_by || 'Member')}</strong><small>${esc(p.walk_title || 'Walk')} · ${Pyrk.formatDate(p.walk_date)}</small></div></div>
          <div class="photo-card-location-row">${photoLocationChip(p)}</div>
          <div class="contest-rule-strip">
            ${statusPill(contestStatus)}
            <span>${esc(permissionLabel(p.voting_permission))}</span>
            <span>${esc(voteModeLabel(p.vote_mode))}</span>
            ${p.contest_groups ? `<span>${esc(p.contest_groups)}</span>` : ''}
          </div>
          <div class="photo-card-actions">
            <button class="btn btn-small" type="button" onclick="setPhoto(${Number(p.id)}, 'approve')" ${p.status === 'approved' || p.status === 'winner' ? 'disabled' : ''}>Approve</button>
            <button class="btn btn-danger btn-small" type="button" onclick="setPhoto(${Number(p.id)}, 'reject')" ${p.status === 'rejected' ? 'disabled' : ''}>Reject</button>
            <button class="btn btn-danger btn-small btn-delete-photo" type="button" onclick="setPhoto(${Number(p.id)}, 'delete')">Delete</button>
            <button class="btn btn-small btn-light" type="button" onclick="previewPhoto(${Number(p.id)})">Preview</button>
          </div>
        </div>
      </article>`;
    }).join('') || '<div class="empty-state"><strong>No photos found</strong><p class="muted">Try changing walk, group, status, permission, or search filters.</p></div>';
  }

  function renderTable(list) {
    const wrap = $('adminPhotosTableWrap');
    const tbody = $('adminPhotosTable');
    if (!wrap || !tbody) return;
    wrap.hidden = state.view !== 'table';
    if (wrap.hidden) return;
    tbody.innerHTML = list.map(p => {
      const contestStatus = p.contest_voting_status || p.voting_status || 'open';
      return `<tr>
        <td><div class="photo-table-photo" onclick="previewPhoto(${Number(p.id)})" role="button" tabindex="0"><img src="${photoSrc(p)}" alt="${esc(p.title)}"><div><strong>${esc(p.title || 'Untitled photo')}</strong><small>${esc(p.caption || 'No caption')}</small></div></div></td>
        <td><div class="identity-cell">${avatar(p)}<div><strong>${esc(p.uploaded_by || 'Member')}</strong><small>${esc(p.uploaded_email || '')}</small></div></div></td>
        <td><strong>${esc(p.walk_title || '')}</strong><small>${esc(p.route_name || '')} · ${Pyrk.formatDate(p.walk_date)}</small></td>
        <td><div class="contest-table-rules">${statusPill(contestStatus)}<small>${esc(permissionLabel(p.voting_permission))}</small><small>${esc(voteModeLabel(p.vote_mode))}</small>${p.contest_groups ? `<small>${esc(p.contest_groups)}</small>` : ''}</div></td>
        <td>${photoLocationChip(p)}</td>
        <td>${statusPill(p.status)}</td>
        <td><strong>${Number(p.votes_count || 0).toLocaleString()}</strong></td>
        <td class="table-actions"><button class="btn btn-small" type="button" onclick="setPhoto(${Number(p.id)}, 'approve')" ${p.status === 'approved' || p.status === 'winner' ? 'disabled' : ''}>Approve</button><button class="btn btn-danger btn-small" type="button" onclick="setPhoto(${Number(p.id)}, 'reject')" ${p.status === 'rejected' ? 'disabled' : ''}>Reject</button><button class="btn btn-danger btn-small btn-delete-photo" type="button" onclick="setPhoto(${Number(p.id)}, 'delete')">Delete</button><button class="btn btn-small btn-light" type="button" onclick="previewPhoto(${Number(p.id)})">View</button></td>
      </tr>`;
    }).join('') || '<tr><td colspan="8"><div class="empty-state"><strong>No photos found</strong><p class="muted">Try changing filters.</p></div></td></tr>';
  }

  function renderPagination() {
    const el = $('photoPagination');
    const info = $('photoPageInfo');
    if (!el || !info) return;
    const pages = Math.max(1, Math.ceil(state.filtered.length / state.perPage));
    el.hidden = state.filtered.length <= state.perPage;
    info.textContent = `Page ${state.page} of ${pages} · ${state.filtered.length.toLocaleString()} photos`;
    if ($('prevPhotoPage')) $('prevPhotoPage').disabled = state.page <= 1;
    if ($('nextPhotoPage')) $('nextPhotoPage').disabled = state.page >= pages;
  }

  function renderAll() {
    applyLocalFilters();
    const list = pagedPhotos();
    if ($('visiblePhotoCount')) $('visiblePhotoCount').textContent = state.filtered.length.toLocaleString();
    if ($('photoBulkBar')) $('photoBulkBar').hidden = false;
    document.querySelectorAll('[data-view]').forEach(btn => btn.classList.toggle('active', btn.dataset.view === state.view));
    renderStats();
    renderCards(list);
    renderTable(list);
    renderPagination();
  }

  function requestWithTimeout(promise, ms = 12000) {
    return Promise.race([
      promise,
      new Promise(resolve => window.setTimeout(() => resolve({ok:false, message:'Request timed out. Please refresh and check the API/PHP error log if it repeats.'}), ms))
    ]);
  }

  async function loadMeta() {
    const params = {};
    const walkId = $('walkSelect')?.value || '';
    if (walkId) params.walk_id = walkId;
    const res = await Pyrk.get('admin_photos.php', {action: 'meta', ...params});
    if (!res.ok) {
      Pyrk.toast('adminPhotoMsg', res);
      return;
    }
    state.walks = visibleWalks(res.data.walks || []); // pyrkPhotoSelectedWalkSanity
    if (state.selectedWalkId && !state.walks.some(w => Number(w.id) === Number(state.selectedWalkId))) {
      // selected walk was hidden by photos filter
      state.selectedWalkId = '';
      if ($('walkSelect')) $('walkSelect').value = '';
      try { history.replaceState(null, '', location.pathname); } catch(e) {}
    }
    state.groups = res.data.groups || [];
    state.settings = res.data.settings || null;
    renderWalkOptions();
    renderGroupFilter();
    renderContestSettings();
  }

  async function loadSettings() {
    const walkId = $('walkSelect')?.value || state.selectedWalkId || '';
    state.selectedWalkId = walkId;
    state.settingsError = '';
    if (!walkId) {
      state.settings = null;
      state.settingsLoading = false;
      renderContestSettings();
      return;
    }
    state.settingsLoading = true;
    renderContestSettings();
    try {
      const res = await requestWithTimeout(Pyrk.get('admin_photos.php', {action: 'settings', walk_id: walkId}));
      state.settingsLoading = false;
      if (res.ok) {
        state.settings = res.data.settings || null;
        state.settingsError = '';
      } else {
        state.settings = null;
        state.settingsError = res.message || 'Settings request failed.';
        Pyrk.toast('contestSettingsMsg', res);
      }
    } catch (error) {
      state.settings = null;
      state.settingsLoading = false;
      state.settingsError = error?.message || 'JavaScript error while loading saved rules.';
      Pyrk.toast('contestSettingsMsg', {ok:false, message: state.settingsError});
    }
    renderContestSettings();
  }

  async function loadAdminPhotos() {
    const params = {
      walk_id: state.selectedWalkId || $('walkSelect')?.value || '',
      group_id: $('groupFilter')?.value || '',
      status: $('statusFilter')?.value || '',
      contest_status: $('contestStatusFilter')?.value || '',
      permission: $('permissionFilter')?.value || '',
      sort: $('sortFilter')?.value || ''
    };
    const res = await Pyrk.get('admin_photos.php', params);
    if (!res.ok) {
      state.photos = [];
      state.filtered = [];
      Pyrk.toast('adminPhotoMsg', res);
      renderAll();
      return;
    }
    state.photos = res.data.photos || [];
    state.stats = res.data.stats || {};
    state.page = 1;
    const msg = $('adminPhotoMsg');
    if (msg) { msg.textContent = ''; msg.className = 'notice'; }
    renderAll();
  }

  async function setPhoto(photoId, action) {
    if (action === 'delete') {
      const photo = state.photos.find(p => Number(p.id) === Number(photoId));
      const label = photo ? `“${photo.title || 'Untitled photo'}” by ${photo.uploaded_by || 'member'}` : 'this photo';
      if (!confirm(`Delete ${label}?

This removes the photo, its thumbnail, and its votes. The member will be able to upload again if the walk limit allows it.`)) return;
    }
    const res = await Pyrk.post('admin_photos.php', {action, photo_id: photoId});
    Pyrk.toast('adminPhotoMsg', res);
    if (res.ok && action === 'delete') Pyrk.closeAllModals();
    await loadAdminPhotos();
    await loadMeta();
  }

  async function saveSettings(event) {
    if (event && typeof event.preventDefault === 'function') event.preventDefault();
    const form = $('contestSettingsForm');
    const walkId = state.selectedWalkId || $('walkSelect')?.value || '';
    if (!walkId) { Pyrk.toast('contestSettingsMsg', {ok:false, message:'Choose a walk first.'}); return; }
    if (!form) { Pyrk.toast('contestSettingsMsg', {ok:false, message:'Settings form was not found on this page.'}); return; }
    const button = $('saveContestSettingsBtn');
    if (button) button.disabled = true;
    try {
      const payload = Object.fromEntries(new FormData(form));
      payload.action = 'save_settings';
      payload.walk_id = walkId;
      payload.allow_self_vote = $('allowSelfVote')?.checked ? 1 : 0;
      payload.show_photo_owner_name = $('showPhotoOwnerName')?.checked ? 1 : 0;
      payload.max_photos_per_participant = Math.max(1, Math.min(50, Number($('maxPhotosPerParticipant')?.value || 1)));
      payload.vote_mode = 'one_per_contest';
      if (isGroupPermission(payload.voting_permission)) {
        payload.group_ids = [...state.selectedGroupIds];
        if (payload.group_ids.length === 0) {
          Pyrk.toast('contestSettingsMsg', {ok:false, message:'Choose at least one group, or change Who can vote to Registered / Checked-in / Anyone.'});
          return;
        }
      } else {
        payload.group_ids = [];
        state.selectedGroupIds = new Set();
      }
      const res = await requestWithTimeout(Pyrk.post('admin_photos.php', payload));
      Pyrk.toast('contestSettingsMsg', res);
      if (res.ok) {
        state.settings = res.data.settings;
        state.settingsError = '';
        state.settingsLoading = false;
        renderContestSettings();
        await loadAdminPhotos();
        await loadMeta();
      }
    } finally {
      if (button) button.disabled = false;
    }
  }

  async function closeContest() {
    const walkId = state.selectedWalkId || $('walkSelect')?.value || '';
    if (!walkId) { Pyrk.toast('contestSettingsMsg', {ok:false, message:'Choose a specific walk before closing a contest.'}); return; }
    if (!confirm('Close voting for this walk and select the highest-vote winner?')) return;
    const res = await Pyrk.post('admin_photos.php', {action:'close_contest', walk_id: walkId});
    Pyrk.toast('contestSettingsMsg', res);
    if (res.ok) {
      state.settings = res.data.settings;
      renderContestSettings();
    }
    await loadAdminPhotos();
    await loadMeta();
  }

  async function reopenContest() {
    const walkId = state.selectedWalkId || $('walkSelect')?.value || '';
    if (!walkId) { Pyrk.toast('contestSettingsMsg', {ok:false, message:'Choose a specific walk before reopening a contest.'}); return; }
    const res = await Pyrk.post('admin_photos.php', {action:'reopen_contest', walk_id: walkId});
    Pyrk.toast('contestSettingsMsg', res);
    if (res.ok) {
      state.settings = res.data.settings;
      renderContestSettings();
    }
    await loadAdminPhotos();
    await loadMeta();
  }

  function previewPhoto(photoId) {
    const photo = state.photos.find(p => Number(p.id) === Number(photoId));
    const modal = $('photoPreviewModal');
    const body = $('photoPreviewBody');
    const title = $('photoPreviewTitle');
    if (!photo || !modal || !body) return;
    const contestStatus = photo.contest_voting_status || photo.voting_status || 'open';
    if (title) title.textContent = photo.title || 'Photo';
    body.innerHTML = `<div class="photo-preview-layout">
      <img class="photo-preview-image" src="${fullPhotoSrc(photo)}" alt="${esc(photo.title)}">
      <aside class="photo-preview-side">
        <div class="photo-title-row"><h3>${esc(photo.title || 'Untitled photo')}</h3>${photo.status === 'winner' ? '<span class="winner-crown">🏆</span>' : ''}</div>
        <p>${esc(photo.caption || 'No caption')}</p>
        <div class="photo-preview-meta">
          <div><span>Member</span><strong>${esc(photo.uploaded_by || 'Member')}</strong></div>
          <div><span>Walk</span><strong>${esc(photo.walk_title || '')}</strong></div>
          <div><span>Route</span><strong>${esc(photo.route_name || '')}</strong></div>
          <div><span>Uploaded</span><strong>${esc(fmtDate(photo.uploaded_at))}</strong></div>
          <div><span>Location</span><strong>${hasPhotoLocation(photo) ? 'Available' : 'Not saved'}</strong></div>
          <div><span>Votes</span><strong>${Number(photo.votes_count || 0).toLocaleString()}</strong></div>
          <div><span>Photo status</span><strong>${esc(photo.status || '')}</strong></div>
        </div>
        ${hasPhotoLocation(photo) ? `<div class="photo-preview-location-card"><strong>Photo location</strong><p class="muted">Coordinates: ${esc(Number(photo.photo_latitude).toFixed(6))}, ${esc(Number(photo.photo_longitude).toFixed(6))}${photo.photo_location_accuracy ? ` · accuracy about ${esc(Math.round(Number(photo.photo_location_accuracy)))}m` : ''}</p><a class="btn btn-small btn-light" href="${photoMapUrl(photo)}" target="_blank" rel="noopener">Open in Google Maps</a></div>` : `<div class="photo-preview-location-card"><strong>Photo location</strong><p class="muted">No location was saved for this photo. The member may have denied location permission, or the browser/device did not provide GPS metadata.</p></div>`}
        <div class="contest-rule-card">
          <strong>Contest voting rules</strong>
          <p>${statusPill(contestStatus)} ${esc(permissionLabel(photo.voting_permission))}</p>
          <p>${esc(voteModeLabel(photo.vote_mode))}</p>
          <p>Upload limit: ${esc(photoLimitLabel(photo.max_photos_per_participant || 1))}</p>
          ${photo.contest_groups ? `<p>Groups: ${esc(photo.contest_groups)}</p>` : ''}
        </div>
        <div class="button-row"><button class="btn" type="button" onclick="setPhoto(${Number(photo.id)}, 'approve')">Approve</button><button class="btn btn-danger" type="button" onclick="setPhoto(${Number(photo.id)}, 'reject')">Reject</button><button class="btn btn-danger" type="button" onclick="setPhoto(${Number(photo.id)}, 'delete')">Delete photo</button></div>
      </aside>
    </div>`;
    Pyrk.openModal(modal);
  }

  function bindEvents() {
    $('refreshPhotosBtn')?.addEventListener('click', async () => { await loadMeta(); await loadAdminPhotos(); });
    $('walkSelect')?.addEventListener('change', async () => { state.selectedWalkId = $('walkSelect')?.value || ''; await loadSettings(); await loadAdminPhotos(); });
    $('groupFilter')?.addEventListener('change', loadAdminPhotos);
    $('statusFilter')?.addEventListener('change', loadAdminPhotos);
    $('contestStatusFilter')?.addEventListener('change', loadAdminPhotos);
    $('permissionFilter')?.addEventListener('change', loadAdminPhotos);
    $('sortFilter')?.addEventListener('change', loadAdminPhotos);
    $('photoSearch')?.addEventListener('input', () => { state.page = 1; renderAll(); });
    $('contestGroupSearch')?.addEventListener('input', renderContestGroups);
    $('contestSettingsForm')?.addEventListener('submit', saveSettings);
    $('saveContestSettingsBtn')?.addEventListener('click', saveSettings);
    $('contestSettingsForm')?.addEventListener('keydown', event => { if (event.key === 'Enter' && event.target.tagName !== 'TEXTAREA') event.preventDefault(); });
    $('closeContestBtn')?.addEventListener('click', closeContest);
    $('reopenContestBtn')?.addEventListener('click', reopenContest);
    $('votingPermission')?.addEventListener('change', () => {
      const permission = $('votingPermission')?.value || 'walk_registered';
      if (!isGroupPermission(permission)) {
        state.selectedGroupIds = new Set();
      }
      renderContestGroups();
      syncGroupSelectorVisibility(permission);
    });
    $('contestGroupResults')?.addEventListener('change', event => {
      if (!isGroupPermission(currentVotingPermission())) return;
      const input = event.target.closest('input[type="checkbox"]');
      if (!input) return;
      const id = Number(input.value);
      if (input.checked) state.selectedGroupIds.add(id);
      else state.selectedGroupIds.delete(id);
      renderContestGroups();
    });
    document.querySelectorAll('[data-view]').forEach(btn => btn.addEventListener('click', () => {
      state.view = btn.dataset.view;
      localStorage.setItem('pyrkPhotoView', state.view);
      renderAll();
    }));
    document.querySelectorAll('#photoStatusTabs [data-status]').forEach(btn => btn.addEventListener('click', () => {
      document.querySelectorAll('#photoStatusTabs [data-status]').forEach(b => b.classList.remove('active'));
      btn.classList.add('active');
      if ($('statusFilter')) $('statusFilter').value = btn.dataset.status || '';
      loadAdminPhotos();
    }));
    $('prevPhotoPage')?.addEventListener('click', () => { state.page = Math.max(1, state.page - 1); renderAll(); });
    $('nextPhotoPage')?.addEventListener('click', () => { state.page += 1; renderAll(); });
  }

  window.setPhoto = setPhoto;
  window.previewPhoto = previewPhoto;

  document.addEventListener('DOMContentLoaded', async () => {
    try {
      bindEvents();
      await loadMeta();
      if (state.selectedWalkId || $('walkSelect')?.value) {
        await loadSettings();
      }
      await loadAdminPhotos();
    } catch (error) {
      state.settingsLoading = false;
      state.settingsError = error?.message || 'JavaScript error while starting the photo contest page.';
      renderContestSettings();
      Pyrk.toast('adminPhotoMsg', {ok:false, message: state.settingsError});
    }
  });
})();
