let currentWalk = null;
let selfieStream = null;
let selfieBlob = null;
let nativeCapturedPhotoFile = null;
let nativeCapturedDirection = 'front';
let waiverSignatureHasInk = false;
let waiverSignaturePadReady = false;
let waiverSignatureLocked = false;
let photoLocationMeta = null;
let photoUploadPolicy = null;

const PYRK_CLIENT_PHOTO_MAX_BYTES = 18 * 1024 * 1024;
const PYRK_CLIENT_PHOTO_RESIZE_TRIGGER_BYTES = 3 * 1024 * 1024;
const PYRK_CLIENT_PHOTO_MAX_SIDE = 2200;
const PYRK_PHOTO_UPLOAD_TIMEOUT_MS = Number(window.PYRK_PHOTO_UPLOAD_TIMEOUT_MS || 90000);

function fileExtension(file) {
  const name = String(file?.name || '').toLowerCase();
  const dot = name.lastIndexOf('.');
  return dot >= 0 ? name.slice(dot + 1) : '';
}

function isAllowedPhotoFile(file) {
  if (!file) return false;
  const type = String(file.type || '').toLowerCase();
  const ext = fileExtension(file);
  return type.startsWith('image/') || ['jpg', 'jpeg', 'png', 'webp', 'heic', 'heif'].includes(ext);
}

function extensionFromMime(type, fallback = 'jpg') {
  type = String(type || '').toLowerCase();
  if (type.includes('png')) return 'png';
  if (type.includes('webp')) return 'webp';
  if (type.includes('heic')) return 'heic';
  if (type.includes('heif')) return 'heif';
  if (type.includes('jpeg') || type.includes('jpg')) return 'jpg';
  return fallback || 'jpg';
}

function safeUploadName(file, forcedExt = '') {
  const original = String(file?.name || '').trim();
  const base = original
    ? original.replace(/\.[^.]+$/, '').replace(/[^a-z0-9_-]+/gi, '-').replace(/^-+|-+$/g, '').slice(0, 50)
    : 'pyrk-photo';
  const ext = forcedExt || extensionFromMime(file?.type, fileExtension(file) || 'jpg');
  return `${base || 'pyrk-photo'}-${Date.now()}.${ext}`;
}

function loadImageForResize(file) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    const url = URL.createObjectURL(file);
    img.onload = () => {
      URL.revokeObjectURL(url);
      resolve(img);
    };
    img.onerror = () => {
      URL.revokeObjectURL(url);
      reject(new Error('This image format cannot be previewed by the browser.'));
    };
    img.src = url;
  });
}

async function resizePhotoFile(file) {
  const img = await loadImageForResize(file);
  const ratio = Math.min(PYRK_CLIENT_PHOTO_MAX_SIDE / img.width, PYRK_CLIENT_PHOTO_MAX_SIDE / img.height, 1);
  const width = Math.max(1, Math.round(img.width * ratio));
  const height = Math.max(1, Math.round(img.height * ratio));
  const canvas = document.createElement('canvas');
  canvas.width = width;
  canvas.height = height;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(img, 0, 0, width, height);

  return await new Promise((resolve, reject) => {
    canvas.toBlob(blob => {
      if (!blob) reject(new Error('The browser could not prepare the photo.'));
      else resolve(blob);
    }, 'image/jpeg', 0.86);
  });
}

async function preparePhotoForUpload(file) {
  if (!isAllowedPhotoFile(file)) {
    throw new Error('Please select a valid image file.');
  }

  const type = String(file.type || '').toLowerCase();
  const ext = fileExtension(file);
  const mayNeedServerConversion = type.includes('heic') || type.includes('heif') || ['heic', 'heif'].includes(ext);

  if (!mayNeedServerConversion && file.size <= PYRK_CLIENT_PHOTO_RESIZE_TRIGGER_BYTES) {
    return {blob: file, name: file.name || safeUploadName(file)};
  }

  try {
    const resized = await resizePhotoFile(file);
    return {blob: resized, name: safeUploadName(file, 'jpg')};
  } catch (error) {
    // Some mobile HEIC files cannot be decoded by the browser. Send them to PHP,
    // where Imagick can convert them when the server supports HEIC/HEIF.
    if (file.size > PYRK_CLIENT_PHOTO_MAX_BYTES) {
      throw new Error('Photo is too large for mobile upload. Please choose a smaller photo or use the camera buttons to capture a new JPG photo.');
    }
    return {blob: file, name: file.name || safeUploadName(file)};
  }
}

function selectedMainPhotoFile() {
  const fileInput = document.getElementById('photoInput');
  return fileInput && fileInput.files && fileInput.files.length ? fileInput.files[0] : null;
}

function cleanCoord(value, min, max) {
  const n = Number(value);
  if (!Number.isFinite(n) || n < min || n > max) return '';
  return String(n);
}

function setPhotoLocationMeta(meta = null, message = '') {
  const latInput = document.getElementById('photoLatitude');
  const lngInput = document.getElementById('photoLongitude');
  const accInput = document.getElementById('photoLocationAccuracy');
  const srcInput = document.getElementById('photoLocationSource');
  const atInput = document.getElementById('photoLocationCapturedAt');
  const panel = document.getElementById('photoLocationPanel');
  const title = document.getElementById('photoLocationTitle');
  const status = document.getElementById('photoLocationStatus');

  photoLocationMeta = meta && meta.latitude && meta.longitude ? meta : null;
  if (latInput) latInput.value = photoLocationMeta ? photoLocationMeta.latitude : '';
  if (lngInput) lngInput.value = photoLocationMeta ? photoLocationMeta.longitude : '';
  if (accInput) accInput.value = photoLocationMeta ? (photoLocationMeta.accuracy || '') : '';
  if (srcInput) srcInput.value = photoLocationMeta ? (photoLocationMeta.source || 'browser_geolocation') : '';
  if (atInput) atInput.value = photoLocationMeta ? (photoLocationMeta.captured_at || new Date().toISOString()) : '';

  if (panel) {
    panel.classList.toggle('is-ready', !!photoLocationMeta);
    panel.classList.toggle('is-unavailable', !photoLocationMeta && !!message);
  }
  if (title) title.textContent = photoLocationMeta ? 'Location attached' : 'Location not attached yet';
  if (status) {
    if (photoLocationMeta) {
      const accuracy = photoLocationMeta.accuracy ? ` · accuracy about ${Math.round(Number(photoLocationMeta.accuracy))}m` : '';
      status.textContent = `Saved coordinates ${Number(photoLocationMeta.latitude).toFixed(5)}, ${Number(photoLocationMeta.longitude).toFixed(5)}${accuracy}.`;
    } else if (message) {
      status.textContent = message;
    } else {
      status.textContent = 'When allowed by the member, Pyrk saves where the photo was captured/uploaded and shows it to admins.';
    }
  }
}

async function getBrowserLocation(timeoutMs = 7000) {
  if (window.PyrkNativeDevice?.isAvailable('geolocation')) {
    return await window.PyrkNativeDevice.getCurrentPosition({
      enableHighAccuracy: true,
      timeout: Math.max(timeoutMs, 12000),
      maximumAge: 15000
    });
  }

  return await new Promise((resolve, reject) => {
    if (!navigator.geolocation) {
      reject(new Error('Location is not supported by this browser.'));
      return;
    }
    navigator.geolocation.getCurrentPosition(resolve, reject, {
      enableHighAccuracy: true,
      timeout: timeoutMs,
      maximumAge: 60000
    });
  });
}

async function capturePhotoLocation(options = {}) {
  const silent = !!options.silent;
  const status = document.getElementById('photoLocationStatus');
  if (status && !silent) status.textContent = 'Requesting secure location permission...';
  try {
    const pos = await getBrowserLocation();
    const lat = cleanCoord(pos.coords.latitude, -90, 90);
    const lng = cleanCoord(pos.coords.longitude, -180, 180);
    if (!lat || !lng) throw new Error('Browser returned invalid coordinates.');
    const meta = {
      latitude: lat,
      longitude: lng,
      accuracy: Number.isFinite(Number(pos.coords.accuracy)) ? String(Math.round(Number(pos.coords.accuracy) * 100) / 100) : '',
      source: window.PyrkNativeDevice?.isAvailable('geolocation') ? 'native_geolocation' : 'browser_geolocation',
      captured_at: new Date().toISOString()
    };
    setPhotoLocationMeta(meta);
    return meta;
  } catch (error) {
    const msg = error && error.code === 1
      ? 'Location permission was not allowed. The photo can still be uploaded without location.'
      : 'Could not attach location now. The photo can still be uploaded without location.';
    setPhotoLocationMeta(null, msg);
    return null;
  }
}

async function ensurePhotoLocationBeforeUpload(fd) {
  if (!photoLocationMeta) {
    await capturePhotoLocation({silent: false});
  }
  if (photoLocationMeta) {
    fd.set('photo_latitude', photoLocationMeta.latitude);
    fd.set('photo_longitude', photoLocationMeta.longitude);
    fd.set('photo_location_accuracy', photoLocationMeta.accuracy || '');
    fd.set('photo_location_source', photoLocationMeta.source || 'browser_geolocation');
    fd.set('photo_location_captured_at', photoLocationMeta.captured_at || new Date().toISOString());
  }
}

function normalizePhotoPolicy(policy) {
  if (!policy || typeof policy !== 'object') return null;
  const limit = Math.max(1, Math.min(50, Number(policy.max_photos_per_participant || 1)));
  const count = Math.max(0, Number(policy.current_count || 0));
  const existing = Array.isArray(policy.existing_photos) ? policy.existing_photos : [];
  return {
    ...policy,
    max_photos_per_participant: limit,
    current_count: count,
    remaining: Math.max(0, limit - count),
    limit_reached: count >= limit,
    existing_photos: existing
  };
}

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

function renderPhotoUploadPolicy(policy) {
  const card = document.getElementById('photoUploadPolicyCard');
  if (!card) return;
  photoUploadPolicy = normalizePhotoPolicy(policy);
  if (!photoUploadPolicy) {
    card.hidden = true;
    card.innerHTML = '';
    return;
  }

  const limit = photoUploadPolicy.max_photos_per_participant;
  const count = photoUploadPolicy.current_count;
  const remaining = photoUploadPolicy.remaining;
  const reached = !!photoUploadPolicy.limit_reached;
  const existing = photoUploadPolicy.existing_photos || [];
  card.hidden = false;
  card.classList.toggle('limit-reached', reached);

  const existingList = existing.length
    ? `<div class="photo-policy-existing-list">${existing.map(p => `<div class="photo-policy-existing-item"><span>${Pyrk.esc(p.title || 'Walk photo')}</span><small>${Pyrk.esc(p.status || '')}${p.uploaded_at ? ' · ' + Pyrk.esc(Pyrk.formatDateTime(p.uploaded_at)) : ''}</small></div>`).join('')}</div>`
    : '';

  const replaceSelect = reached && existing.length
    ? `<label class="photo-replace-select">Replace existing photo
        <select id="replacePhotoSelect">
          ${existing.map(p => `<option value="${Number(p.id)}">${Pyrk.esc(p.title || 'Walk photo')} · ${Pyrk.esc(p.status || '')}</option>`).join('')}
        </select>
        <small>When you upload now, Pyrk will remove the selected old photo and its votes, then submit the new one for approval.</small>
      </label>`
    : '';

  card.innerHTML = `<div class="photo-policy-head">
    <div>
      <p class="eyebrow">Walk photo limit</p>
      <strong>${Pyrk.esc(photoLimitLabel(limit))}</strong>
      <p class="muted small">${reached ? 'You reached the upload limit for this walk.' : `You already uploaded ${count} and can upload ${remaining} more.`}</p>
    </div>
    <span class="pill ${reached ? 'bad' : 'ok'}">${reached ? 'Replacement required' : `${remaining} remaining`}</span>
  </div>
  ${existingList}
  ${replaceSelect}`;
}

function confirmPhotoReplacementIfNeeded(fd) {
  const policy = normalizePhotoPolicy(photoUploadPolicy);
  if (!policy || !policy.limit_reached) return true;

  const select = document.getElementById('replacePhotoSelect');
  const replacePhotoId = Number(select?.value || policy.existing_photos?.[0]?.id || 0);
  if (!replacePhotoId) {
    Pyrk.toast('photoMsg', {ok:false, message:'This walk photo limit is reached. Choose which existing photo to replace.'});
    return false;
  }

  const chosen = (policy.existing_photos || []).find(p => Number(p.id) === replacePhotoId);
  const title = chosen?.title || 'your existing photo';
  const ok = confirm(`This walk allows ${photoLimitLabel(policy.max_photos_per_participant)}.

Replace “${title}” with the new photo?

The old photo and its votes will be removed.`);
  if (!ok) return false;

  fd.set('replace_existing', '1');
  fd.set('replace_photo_id', String(replacePhotoId));
  return true;
}

function resetPhotoLocation() {
  setPhotoLocationMeta(null);
}

function renderSelfCheckinSuccess(res) {
  const card = document.getElementById('selfCheckinSuccessCard');
  if (!card || !res || !res.ok) return;
  const data = res.data || {};
  const walkTitle = data.walk_title || currentWalk?.title || 'this walk';
  const time = data.check_in_time ? (Pyrk.formatDateTime ? Pyrk.formatDateTime(data.check_in_time) : data.check_in_time) : 'Just now';
  const attendee = data.attendee_name || 'You';
  card.hidden = false;
  card.innerHTML = `<div class="checkin-success-head">
    <span class="checkin-success-icon">✓</span>
    <div>
      <p class="eyebrow">Check-in confirmed</p>
      <h3>You are checked in successfully</h3>
      <p class="muted">Welcome, ${Pyrk.esc(attendee)}. Your attendance for <strong>${Pyrk.esc(walkTitle)}</strong> has been recorded.</p>
    </div>
  </div>
  <div class="checkin-success-meta">
    <div><span>Status</span><strong>Checked in</strong></div>
    <div><span>Time</span><strong>${Pyrk.esc(time)}</strong></div>
    <div><span>Next step</span><strong>Enjoy the walk & upload photos</strong></div>
  </div>`;
  card.scrollIntoView({behavior: 'smooth', block: 'nearest'});
}

function isActiveRegistrationStatus(status) {
  return ['registered', 'approved'].includes(String(status || '').toLowerCase());
}

function updateRegistrationState(walk) {
  const btn = document.getElementById('registerBtn');
  const form = document.getElementById('waiverForm');
  const waiverSubmit = document.getElementById('waiverSubmitBtn');
  const statusBox = document.getElementById('walkJoinStatus');
  const joinPanel = document.getElementById('walkJoinPanel');
  const joinEyebrow = document.getElementById('walkJoinEyebrow');
  const joinTitle = document.getElementById('walkJoinTitle');
  const joinLead = document.getElementById('walkJoinLead');
  const joinBadge = document.getElementById('walkJoinTopBadge');
  if (!btn) return;

  const status = walk?.my_registration_status || walk?.registration_status || '';
  const attendanceStatus = String(walk?.my_attendance_status || '').toLowerCase();
  const registered = Number(walk?.is_registered || 0) === 1 || isActiveRegistrationStatus(status);
  const waiverRequired = Number(walk?.waiver_required || 0) === 1;
  const waiverSigned = Number(walk?.my_waiver_signed || 0) === 1 || waiverSignatureLocked;
  const checkedIn = ['checked_in', 'started', 'finished', 'manual_verified', 'partially_completed'].includes(attendanceStatus) || !!walk?.my_check_in_time;

  btn.dataset.registered = registered ? '1' : '0';
  btn.dataset.needsWaiver = (!registered && waiverRequired && !waiverSigned) ? '1' : '0';
  btn.classList.remove('btn-light', 'btn-secondary');

  const setStatus = (state, title, note, icon) => {
    if (!statusBox) return;
    statusBox.className = `walk-join-status ${state}`;
    statusBox.innerHTML = `<span class="walk-join-status-icon">${Pyrk.esc(icon)}</span><div><strong>${Pyrk.esc(title)}</strong><small>${Pyrk.esc(note)}</small></div>`;
  };

  if (joinPanel) joinPanel.classList.remove('is-action', 'is-ready', 'is-complete');

  if (registered) {
    if (joinPanel) joinPanel.classList.add('is-complete');
    if (joinEyebrow) joinEyebrow.textContent = checkedIn ? 'Attendance confirmed' : 'Registration confirmed';
    if (joinTitle) joinTitle.textContent = checkedIn ? 'You are checked in' : 'Your place is confirmed';
    if (joinLead) joinLead.textContent = checkedIn ? 'Your attendance is recorded. The walk remains available for route guidance, photos and updates.' : 'Your registration is complete. You can continue to the route map and walk information below.';
    if (joinBadge) joinBadge.textContent = checkedIn ? 'Checked in' : 'Registered';
    btn.hidden = true;
    btn.disabled = true;
    if (form) form.hidden = true;
    setStatus('is-complete', checkedIn ? 'Checked in and ready' : 'Registration complete', checkedIn ? 'Your attendance is recorded and this walk will remain available in your app.' : (waiverRequired ? 'Your waiver is signed and your place is confirmed.' : 'Your place in this walk is confirmed.'), '✓');
  } else if (waiverRequired && !waiverSigned) {
    if (joinPanel) joinPanel.classList.add('is-action');
    if (joinEyebrow) joinEyebrow.textContent = 'Action required';
    if (joinTitle) joinTitle.textContent = 'Sign the waiver & register';
    if (joinLead) joinLead.textContent = 'The waiver is now at the top of the walk page. Read it, sign once, and your registration is completed immediately.';
    if (joinBadge) joinBadge.textContent = '1 step remaining';
    btn.hidden = true;
    btn.disabled = false;
    if (form) form.hidden = false;
    if (waiverSubmit) {
      waiverSubmit.disabled = false;
      waiverSubmit.textContent = 'Sign waiver & register';
    }
    setStatus('is-action', 'One step to join', 'Read and sign the waiver below. Pyrk will register you in the same action.', '1');
  } else {
    if (joinPanel) joinPanel.classList.add('is-ready');
    if (joinEyebrow) joinEyebrow.textContent = 'Registration';
    if (joinTitle) joinTitle.textContent = waiverRequired ? 'Complete your registration' : 'Reserve your place';
    if (joinLead) joinLead.textContent = waiverRequired ? 'Your waiver is already signed. Confirm your place before continuing to the walk details.' : 'Register now, then continue to the route map and walk information below.';
    if (joinBadge) joinBadge.textContent = 'Ready to join';
    btn.hidden = false;
    btn.disabled = false;
    btn.textContent = waiverRequired ? 'Complete registration' : 'Register for this walk';
    if (waiverRequired) btn.classList.add('btn-secondary');
    if (form) form.hidden = true;
    setStatus('is-ready', waiverRequired ? 'Waiver already signed' : 'Registration available', waiverRequired ? 'Confirm your registration to reserve your place.' : 'Reserve your place now. No waiver is required for this walk.', waiverRequired ? '✓' : '1');
  }

  const msg = document.getElementById('walkMsg');
  if (registered && msg && !msg.textContent) {
    msg.className = 'notice ok';
    msg.textContent = checkedIn ? 'You are checked in. This walk remains available on your dashboard.' : 'You are registered for this walk. It will appear on your dashboard.';
  }
}

function updateSelfCheckinPolicy(walk) {
  const btn = document.getElementById('selfCheckinBtn');
  const note = document.getElementById('selfCheckinPolicyNote');
  if (!btn) return;

  const attendanceStatus = String(walk?.my_attendance_status || '').toLowerCase();
  const alreadyCheckedIn = ['checked_in', 'started', 'finished', 'manual_verified', 'partially_completed'].includes(attendanceStatus) || !!walk?.my_check_in_time;
  const allowed = Number(walk?.allow_self_checkin || 0) === 1;

  btn.disabled = alreadyCheckedIn || !allowed;
  btn.classList.toggle('btn-secondary', !allowed && !alreadyCheckedIn);
  btn.classList.toggle('btn-light', alreadyCheckedIn);
  btn.classList.toggle('btn', true);

  if (alreadyCheckedIn) {
    btn.textContent = 'Checked in';
    if (note) note.textContent = walk?.my_check_in_time ? `You checked in on ${Pyrk.formatDateTime(walk.my_check_in_time)}.` : 'You are already checked in for this walk.';
  } else if (allowed) {
    btn.textContent = 'Self Check-in';
    if (note) note.textContent = 'Self check-in is enabled for this walk. You must be registered and have signed the waiver if required.';
  } else {
    btn.textContent = 'Self Check-in disabled';
    if (note) note.textContent = 'Self check-in is disabled for this walk. Please ask a Pyrk admin or walk leader to check you in.';
  }
}


function updateSignatureUi(state = 'ready', message = '') {
  const form = document.getElementById('waiverForm');
  const pill = document.getElementById('signatureStatusPill');
  const help = document.getElementById('signatureHelpText');
  const hasInk = waiverSignatureHasInk || state === 'done' || state === 'locked';

  if (form) form.classList.toggle('signature-has-ink', hasInk);
  if (pill) {
    pill.classList.toggle('is-done', state === 'done');
    pill.classList.toggle('is-locked', state === 'locked');
    pill.textContent = state === 'locked' ? 'Signed & locked' : (hasInk ? 'Signature ready' : 'Ready to sign');
  }
  if (help && message) help.textContent = message;
}

function markWaiverSignatureChanged() {
  waiverSignatureHasInk = true;
  updateSignatureUi('done', 'Signature captured. Press Sign Waiver to continue, or clear it and sign again.');
}


function signatureCanvasPoint(canvas, event) {
  const rect = canvas.getBoundingClientRect();
  const clientX = event.clientX ?? (event.touches && event.touches[0] ? event.touches[0].clientX : 0);
  const clientY = event.clientY ?? (event.touches && event.touches[0] ? event.touches[0].clientY : 0);
  return {
    x: (clientX - rect.left) * (canvas.width / rect.width),
    y: (clientY - rect.top) * (canvas.height / rect.height)
  };
}

function resizeSignatureCanvas() {
  const canvas = document.getElementById('waiverSignatureCanvas');
  if (!canvas) return;
  const data = waiverSignatureHasInk ? canvas.toDataURL('image/png') : '';
  const rect = canvas.getBoundingClientRect();
  const ratio = Math.max(window.devicePixelRatio || 1, 1);
  const width = Math.max(320, Math.floor(rect.width || 640));
  const height = Math.max(160, Math.floor(rect.height || 180));
  canvas.width = Math.floor(width * ratio);
  canvas.height = Math.floor(height * ratio);
  const ctx = canvas.getContext('2d');
  ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
  ctx.lineCap = 'round';
  ctx.lineJoin = 'round';
  ctx.lineWidth = 3;
  ctx.strokeStyle = '#0f172a';
  ctx.fillStyle = '#ffffff';
  ctx.fillRect(0, 0, width, height);

  if (data) {
    const img = new Image();
    img.onload = () => ctx.drawImage(img, 0, 0, width, height);
    img.src = data;
  }
}


function drawSavedSignature(dataUrl) {
  const canvas = document.getElementById('waiverSignatureCanvas');
  if (!canvas || !dataUrl) return;
  resizeSignatureCanvas();
  const ctx = canvas.getContext('2d');
  const img = new Image();
  img.onload = () => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    const scale = Math.min(canvas.width / img.width, canvas.height / img.height);
    const w = img.width * scale;
    const h = img.height * scale;
    ctx.drawImage(img, (canvas.width - w) / 2, (canvas.height - h) / 2, w, h);
    waiverSignatureHasInk = true;
    const input = document.getElementById('waiverSignatureData');
    if (input) input.value = dataUrl;
    updateSignatureUi(waiverSignatureLocked ? 'locked' : 'done');
  };
  img.src = dataUrl;
}

function setWaiverSignatureLocked(signature = {}) {
  waiverSignatureLocked = true;

  const form = document.getElementById('waiverForm');
  const signedName = document.getElementById('waiverSignedName');
  const clearBtn = document.getElementById('clearSignatureBtn');
  const canvas = document.getElementById('waiverSignatureCanvas');
  const submit = form?.querySelector('button[type="submit"]');
  const panel = form?.querySelector('.digital-signature-panel');

  if (form) form.classList.add('waiver-signed', 'waiver-signature-locked');
  if (signedName) {
    signedName.value = signature.signed_name || signedName.value || '';
    signedName.readOnly = true;
    signedName.disabled = true;
  }
  if (clearBtn) {
    clearBtn.disabled = true;
    clearBtn.textContent = 'Locked';
    clearBtn.title = 'Waiver already signed. Signature cannot be cleared.';
  }
  if (canvas) {
    canvas.classList.add('signature-locked');
    canvas.setAttribute('aria-disabled', 'true');
  }
  if (submit) {
    submit.disabled = true;
    submit.textContent = 'Waiver signed';
    submit.classList.add('btn-light');
  }

  let lockNote = document.getElementById('waiverSignatureLockNote');
  if (!lockNote && panel) {
    lockNote = document.createElement('p');
    lockNote.id = 'waiverSignatureLockNote';
    lockNote.className = 'notice ok waiver-lock-note';
    panel.appendChild(lockNote);
  }
  if (lockNote) {
    const signedAt = signature.signed_at ? ` on ${Pyrk.formatDateTime ? Pyrk.formatDateTime(signature.signed_at) : signature.signed_at}` : '';
    lockNote.textContent = `Waiver signed${signedAt}. Signature is locked and cannot be cleared or changed.`;
  }

  updateSignatureUi('locked', 'Waiver signed. This signature is locked and cannot be changed.');

  if (signature.signature_data) {
    drawSavedSignature(signature.signature_data);
  }
}


function clearWaiverSignature() {
  if (waiverSignatureLocked) {
    Pyrk.toast('walkMsg', {ok:false, message:'Waiver already signed. Signature is locked and cannot be cleared.'});
    return;
  }
  const canvas = document.getElementById('waiverSignatureCanvas');
  const input = document.getElementById('waiverSignatureData');
  if (!canvas) return;
  waiverSignatureHasInk = false;
  if (input) input.value = '';
  resizeSignatureCanvas();
  updateSignatureUi('ready', 'Signature cleared. Draw your signature before signing the waiver.');
}

function captureWaiverSignatureData() {
  const canvas = document.getElementById('waiverSignatureCanvas');
  const input = document.getElementById('waiverSignatureData');
  if (!canvas || !input || !waiverSignatureHasInk) return '';
  const data = canvas.toDataURL('image/png');
  input.value = data;
  return data;
}

function initWaiverSignaturePad() {
  if (waiverSignaturePadReady) return;
  const canvas = document.getElementById('waiverSignatureCanvas');
  if (!canvas) return;
  waiverSignaturePadReady = true;
  resizeSignatureCanvas();
  updateSignatureUi('ready');

  const ctx = canvas.getContext('2d');
  let drawing = false;
  let last = null;

  function start(event) {
    if (waiverSignatureLocked) return;
    event.preventDefault();
    drawing = true;
    last = signatureCanvasPoint(canvas, event);
  }

  function move(event) {
    if (waiverSignatureLocked) return;
    if (!drawing || !last) return;
    event.preventDefault();
    const point = signatureCanvasPoint(canvas, event);
    ctx.beginPath();
    const ratio = Math.max(window.devicePixelRatio || 1, 1);
    ctx.moveTo(last.x / ratio, last.y / ratio);
    ctx.lineTo(point.x / ratio, point.y / ratio);
    ctx.stroke();
    last = point;
    markWaiverSignatureChanged();
    const input = document.getElementById('waiverSignatureData');
    if (input) input.value = '';
  }

  function end(event) {
    if (!drawing) return;
    event.preventDefault();
    drawing = false;
    last = null;
    captureWaiverSignatureData();
  }

  canvas.addEventListener('pointerdown', start);
  canvas.addEventListener('pointermove', move);
  canvas.addEventListener('pointerup', end);
  canvas.addEventListener('pointercancel', end);
  canvas.addEventListener('pointerleave', end);

  // Touch fallback for older mobile browsers.
  canvas.addEventListener('touchstart', start, {passive:false});
  canvas.addEventListener('touchmove', move, {passive:false});
  canvas.addEventListener('touchend', end, {passive:false});

  document.getElementById('clearSignatureBtn')?.addEventListener('click', clearWaiverSignature);
  window.addEventListener('resize', () => {
    if (!waiverSignatureHasInk) resizeSignatureCanvas();
  });
}


async function loadWalkDetail(){
  const res = await Pyrk.get('walks.php', {action:'detail', id: window.PYRK_WALK_ID});
  const box = document.getElementById('walkDetail');
  if(!box) return;
  if(!res.ok || !res.data.walk){ box.innerHTML = '<h1>Walk not found</h1>'; return; }
  const w = res.data.walk; currentWalk = w; updateSelfCheckinPolicy(w); updateRegistrationState(w);
  const walkDescription = String(w.description || w.route_description || '').trim();
  const descriptionBlock = walkDescription
    ? `<div class="walk-member-description"><span>Description</span><p>${Pyrk.esc(walkDescription)}</p></div>`
    : '';
  box.innerHTML = `<p class="eyebrow">${Pyrk.esc(w.status)} walk</p><h1>${Pyrk.esc(w.title)}</h1><div class="grid three"><div class="stat"><span>Date</span><strong>${Pyrk.esc(w.walk_date)}</strong></div><div class="stat"><span>Time</span><strong>${String(w.start_time).slice(0,5)}-${String(w.expected_finish_time).slice(0,5)}</strong></div><div class="stat"><span>Distance</span><strong>${Pyrk.esc(w.total_km)} KM</strong></div></div>${descriptionBlock}`;
}

async function loadWaiver(){
  const res = await Pyrk.get('waivers.php', {walk_id: window.PYRK_WALK_ID});
  const el = document.getElementById('waiverText');
  if(el) el.innerHTML = res.ok && res.data.template ? res.data.template.body_html : 'No waiver template found.';
  if (res.ok && res.data?.signature) {
    setWaiverSignatureLocked(res.data.signature);
    if (currentWalk) {
      currentWalk.my_waiver_signed = 1;
      updateRegistrationState(currentWalk);
    }
  }
}

document.getElementById('registerBtn')?.addEventListener('click', async()=>{
  const btn = document.getElementById('registerBtn');
  if (btn?.dataset.registered === '1') {
    Pyrk.toast('walkMsg', {ok:true, message:'You are already registered for this walk.'});
    return;
  }
  if (btn?.dataset.needsWaiver === '1') {
    Pyrk.toast('walkMsg', {ok:false, message:'Waiver is required before registration. Please complete the Registration & waiver section at the top of this page.'});
    return;
  }
  const res=await Pyrk.post('walks.php',{action:'register', walk_id:window.PYRK_WALK_ID});
  Pyrk.toast('walkMsg',res);
  if (res.ok) {
    await loadWalkDetail();
    if (window.refreshPyrkWalkRouteNavigation) await window.refreshPyrkWalkRouteNavigation();
  }
});

document.getElementById('waiverForm')?.addEventListener('submit', async e=>{
  e.preventDefault();
  if (waiverSignatureLocked) {
    Pyrk.toast('walkMsg', {ok:false, message:'Waiver already signed. Use Complete registration if your place is not yet confirmed.'});
    return;
  }
  const data=Object.fromEntries(new FormData(e.target));
  const signatureData = captureWaiverSignatureData();
  if (!String(data.signed_name || '').trim()) {
    Pyrk.toast('walkMsg', {ok:false, message:'Please type your full name.'});
    return;
  }
  if (!signatureData) {
    Pyrk.toast('walkMsg', {ok:false, message:'Please draw your digital signature before joining the walk.'});
    return;
  }

  const submit = document.getElementById('waiverSubmitBtn');
  if (submit) {
    submit.disabled = true;
    submit.textContent = 'Signing & registering...';
  }

  const res=await Pyrk.post('walks.php',{
    action:'sign_and_register',
    walk_id:window.PYRK_WALK_ID,
    signed_name:data.signed_name,
    signature_data:signatureData
  });
  Pyrk.toast('walkMsg',res);

  if (res.ok) {
    setWaiverSignatureLocked({
      signed_name: data.signed_name,
      signature_data: signatureData,
      signed_at: new Date().toISOString()
    });
    if (currentWalk) {
      currentWalk.my_waiver_signed = 1;
      currentWalk.is_registered = 1;
      currentWalk.my_registration_status = 'registered';
      updateRegistrationState(currentWalk);
    }
    await loadWalkDetail();
    if (window.refreshPyrkWalkRouteNavigation) await window.refreshPyrkWalkRouteNavigation();
  } else if (submit) {
    submit.disabled = false;
    submit.textContent = 'Sign waiver & register';
  }
});

document.getElementById('selfCheckinBtn')?.addEventListener('click', async()=>{
  if (!currentWalk || Number(currentWalk.allow_self_checkin || 0) !== 1) {
    Pyrk.toast('walkMsg', {ok:false, message:'Self check-in is disabled for this walk. Please ask a Pyrk admin or walk leader to check you in.'});
    return;
  }
  const btn = document.getElementById('selfCheckinBtn');
  if (btn) btn.disabled = true;
  const res=await Pyrk.post('attendance.php',{action:'self_checkin', walk_id:window.PYRK_WALK_ID});
  Pyrk.toast('walkMsg',res);
  if(res.ok){
    renderSelfCheckinSuccess(res);
    await loadWalkDetail();
    if (window.refreshPyrkWalkRouteNavigation) await window.refreshPyrkWalkRouteNavigation();
  } else if (btn) {
    btn.disabled = false;
  }
});

function cameraMsg(message, isError = false){
  const box = document.getElementById('cameraMsg');
  if(!box) return;
  box.textContent = message || '';
  box.className = isError ? 'notice bad' : 'notice';
}

function setPhotoSource(source, message = '') {
  document.querySelectorAll('.photo-source-card').forEach(card => card.classList.remove('is-selected'));
  const map = {
    gallery: document.querySelector('label[for="photoInput"].photo-source-card'),
    selfie: document.getElementById('openNativeSelfieBtn'),
    back: document.getElementById('openNativeBackBtn')
  };
  if (map[source]) map[source].classList.add('is-selected');
  if (message) cameraMsg(message);
}

async function startSelfieCamera(facingMode = 'user'){
  const video = document.getElementById('selfieVideo');
  const captureBtn = document.getElementById('captureSelfieBtn');
  const preview = document.getElementById('selfiePreview');
  if(!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia){
    cameraMsg('Your browser does not support direct camera capture. Use the front/back camera buttons instead.', true);
    return;
  }
  try {
    stopSelfieCamera(false);
    clearNativeCameraInputs();
    selfieStream = await navigator.mediaDevices.getUserMedia({video: { facingMode: { ideal: facingMode }, width: { ideal: 1280 }, height: { ideal: 720 } }, audio: false});
    video.srcObject = selfieStream;
    video.hidden = false;
    preview.hidden = true;
    captureBtn.disabled = false;
    document.getElementById('retakeSelfieBtn').hidden = true;
    cameraMsg(`${facingMode === 'environment' ? 'Back' : 'Front/selfie'} camera is ready. Press Capture photo.`);
  } catch (error) {
    cameraMsg('Camera permission was denied or no camera was found. Use the file picker instead.', true);
  }
}

function clearNativeCameraInputs(){
  nativeCapturedPhotoFile = null;
  const selfieInput = document.getElementById('nativeSelfieInput');
  const backInput = document.getElementById('nativeBackInput');
  if(selfieInput) selfieInput.value = '';
  if(backInput) backInput.value = '';
}

function selectedNativeCameraFile(){
  if (nativeCapturedPhotoFile) return nativeCapturedPhotoFile;
  const selfieInput = document.getElementById('nativeSelfieInput');
  const backInput = document.getElementById('nativeBackInput');
  if(selfieInput && selfieInput.files && selfieInput.files.length) return selfieInput.files[0];
  if(backInput && backInput.files && backInput.files.length) return backInput.files[0];
  return null;
}

function previewNativeCapturedPhoto(file) {
  const preview = document.getElementById('selfiePreview');
  if (!preview || !file) return;
  if (preview.src) URL.revokeObjectURL(preview.src);
  preview.src = URL.createObjectURL(file);
  preview.hidden = false;
  const retake = document.getElementById('retakeSelfieBtn');
  if (retake) retake.hidden = false;
}

function stopSelfieCamera(clearMessage = true){
  if(selfieStream){ selfieStream.getTracks().forEach(track => track.stop()); selfieStream = null; }
  const video = document.getElementById('selfieVideo');
  const captureBtn = document.getElementById('captureSelfieBtn');
  if(video){ video.srcObject = null; video.hidden = true; }
  if(captureBtn) captureBtn.disabled = true;
  if(clearMessage) cameraMsg('');
}

function captureSelfie(){
  const video = document.getElementById('selfieVideo');
  const canvas = document.getElementById('selfieCanvas');
  const preview = document.getElementById('selfiePreview');
  if(!video || !video.videoWidth){ cameraMsg('Camera is not ready yet.', true); return; }
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
  canvas.toBlob(blob => {
    if(!blob){ cameraMsg('Could not capture photo. Try again.', true); return; }
    selfieBlob = blob;
    preview.src = URL.createObjectURL(blob);
    preview.hidden = false;
    stopSelfieCamera(false);
    document.getElementById('retakeSelfieBtn').hidden = false;
    document.getElementById('photoInput').value = '';
    setPhotoSource('selfie', 'Photo captured. Add title/caption and upload.');
    capturePhotoLocation({silent: true});
  }, 'image/jpeg', 0.92);
}

function clearSelfie(){
  selfieBlob = null;
  const preview = document.getElementById('selfiePreview');
  if(preview){ if(preview.src) URL.revokeObjectURL(preview.src); preview.src = ''; preview.hidden = true; }
  const btn = document.getElementById('retakeSelfieBtn');
  if(btn) btn.hidden = true;
}

document.getElementById('startSelfieCameraBtn')?.addEventListener('click', () => startSelfieCamera('user'));
document.getElementById('startBackCameraBtn')?.addEventListener('click', () => startSelfieCamera('environment'));
async function openNativeCameraInput(inputId, direction) {
  const nativeBridge = window.PyrkNativeDevice;
  const platform = nativeBridge?.getPlatform?.() || 'web';

  // Capacitor Camera currently exposes the front/rear preference on iOS, while
  // Android's official camera activity does not guarantee that preference. For an
  // Android selfie, retain the existing capture="user" input so the system camera
  // receives the correct front-camera intent instead of pretending the preference
  // was applied. Back-camera capture and iOS use the Capacitor Camera plugin.
  const shouldUseCapacitorCamera = nativeBridge?.isAvailable('camera') && !(platform === 'android' && direction === 'front');

  if (shouldUseCapacitorCamera) {
    try {
      cameraMsg(`Opening native ${direction === 'front' ? 'selfie' : 'back'} camera...`);
      stopSelfieCamera(false);
      clearSelfie();
      clearNativeCameraInputs();
      const result = await nativeBridge.capturePhoto({direction, quality: 92});
      if (!result?.file || !isAllowedPhotoFile(result.file)) throw new Error('The native camera did not return a valid image.');
      nativeCapturedPhotoFile = result.file;
      nativeCapturedDirection = direction;
      const mainInput = document.getElementById('photoInput');
      if (mainInput) mainInput.value = '';
      previewNativeCapturedPhoto(result.file);
      setPhotoSource(direction === 'front' ? 'selfie' : 'back', `${direction === 'front' ? 'Selfie' : 'Back camera photo'} captured natively. Add title/caption and upload.`);
      capturePhotoLocation({silent: true});
      return;
    } catch (error) {
      const message = error?.message || 'Native camera could not capture the photo.';
      if (!/cancel/i.test(message)) cameraMsg(message, true);
      return;
    }
  }

  const input = document.getElementById(inputId);
  if (!input) return;
  nativeCapturedDirection = direction;
  input.value = '';
  input.click();
}

document.addEventListener('pyrk:native-camera-restored', (event) => {
  const file = event?.detail?.file;
  if (!file || !isAllowedPhotoFile(file)) return;
  clearSelfie();
  clearNativeCameraInputs();
  stopSelfieCamera(false);
  nativeCapturedPhotoFile = file;
  nativeCapturedDirection = 'back';
  const mainInput = document.getElementById('photoInput');
  if (mainInput) mainInput.value = '';
  previewNativeCapturedPhoto(file);
  setPhotoSource('back', 'Your camera photo was restored safely after the app resumed. Add title/caption and upload.');
  capturePhotoLocation({silent: true});
});

document.addEventListener('pyrk:native-camera-error', (event) => {
  const message = event?.detail?.message || 'Could not restore the captured photo.';
  cameraMsg(message, true);
});

document.getElementById('openNativeSelfieBtn')?.addEventListener('click', async (event) => {
  if (window.PyrkNativeDevice?.isAvailable('camera')) event.preventDefault();
  else if (event.currentTarget && String(event.currentTarget.tagName || '').toLowerCase() === 'label') return;
  await openNativeCameraInput('nativeSelfieInput', 'front');
});
document.getElementById('openNativeBackBtn')?.addEventListener('click', async (event) => {
  if (window.PyrkNativeDevice?.isAvailable('camera')) event.preventDefault();
  else if (event.currentTarget && String(event.currentTarget.tagName || '').toLowerCase() === 'label') return;
  await openNativeCameraInput('nativeBackInput', 'back');
});
document.getElementById('captureSelfieBtn')?.addEventListener('click', captureSelfie);
document.getElementById('retakeSelfieBtn')?.addEventListener('click', async () => {
  clearSelfie();
  resetPhotoLocation();
  if (window.PyrkNativeDevice?.isAvailable('camera')) {
    await openNativeCameraInput(nativeCapturedDirection === 'back' ? 'nativeBackInput' : 'nativeSelfieInput', nativeCapturedDirection);
  } else {
    startSelfieCamera('user');
  }
});
document.getElementById('capturePhotoLocationBtn')?.addEventListener('click', () => capturePhotoLocation({silent: false}));
document.getElementById('photoInput')?.addEventListener('change', () => {
  const file = selectedMainPhotoFile();
  if(file){
    if(!isAllowedPhotoFile(file)){
      Pyrk.toast('photoMsg', {ok:false, message:'Please select an image file only.'});
      document.getElementById('photoInput').value = '';
      return;
    }
    clearSelfie();
    clearNativeCameraInputs();
    stopSelfieCamera();
    setPhotoSource('gallery', 'Gallery photo selected. Add title/caption and upload.');
    resetPhotoLocation();
  }
});
document.getElementById('nativeSelfieInput')?.addEventListener('change', () => {
  const input = document.getElementById('nativeSelfieInput');
  if(input.files.length){
    if(!isAllowedPhotoFile(input.files[0])){ input.value = ''; cameraMsg('Please select an image file only.', true); return; }
    clearSelfie();
    nativeCapturedPhotoFile = null;
    stopSelfieCamera();
    const mainInput = document.getElementById('photoInput');
    const backInput = document.getElementById('nativeBackInput');
    if (mainInput) mainInput.value='';
    if (backInput) backInput.value='';
    setPhotoSource('selfie', 'Selfie selected. Add title/caption and upload.');
    capturePhotoLocation({silent: true});
  }
});
document.getElementById('nativeBackInput')?.addEventListener('change', () => {
  const input = document.getElementById('nativeBackInput');
  if(input.files.length){
    if(!isAllowedPhotoFile(input.files[0])){ input.value = ''; cameraMsg('Please select an image file only.', true); return; }
    clearSelfie();
    nativeCapturedPhotoFile = null;
    stopSelfieCamera();
    const mainInput = document.getElementById('photoInput');
    const selfieInput = document.getElementById('nativeSelfieInput');
    if (mainInput) mainInput.value='';
    if (selfieInput) selfieInput.value='';
    setPhotoSource('back', 'Back camera photo selected. Add title/caption and upload.');
    capturePhotoLocation({silent: true});
  }
});

document.getElementById('photoForm')?.addEventListener('submit', async e=>{
  e.preventDefault();
  const form = e.target;
  const submitBtn = form.querySelector('button[type="submit"]');
  const fd=new FormData(form);
  const nativeCameraFile = selectedNativeCameraFile();
  const mainPhotoFile = selectedMainPhotoFile();

  if (!confirmPhotoReplacementIfNeeded(fd)) {
    return;
  }

  try {
    if(selfieBlob){
      fd.set('photo', selfieBlob, `pyrk-camera-${Date.now()}.jpg`);
    } else {
      const chosenFile = nativeCameraFile || mainPhotoFile;
      if(!chosenFile){
        Pyrk.toast('photoMsg', {ok:false, message:'Please choose a photo or capture one from the front/back camera first.'});
        return;
      }
      cameraMsg('Preparing photo for upload...');
      const prepared = await preparePhotoForUpload(chosenFile);
      fd.set('photo', prepared.blob, prepared.name);
    }

    if(submitBtn) {
      submitBtn.disabled = true;
      submitBtn.dataset.originalText = submitBtn.dataset.originalText || submitBtn.textContent;
      submitBtn.textContent = 'Uploading photo...';
    }
    cameraMsg('Uploading photo. On iPhone this can take a little longer on mobile data. Please keep this page open.');
    await ensurePhotoLocationBeforeUpload(fd);
    const res=await Pyrk.postForm('photos.php', fd, {timeoutMs: PYRK_PHOTO_UPLOAD_TIMEOUT_MS});
    if (!res.ok && res.code === 'photo_limit_reached') {
      renderPhotoUploadPolicy(res.data?.upload_policy || null);
      Pyrk.toast('photoMsg', res);
      return;
    }
    Pyrk.toast('photoMsg',res);
    if(res.ok){
      if (res.data?.upload_policy) renderPhotoUploadPolicy(res.data.upload_policy);
      form.reset(); clearSelfie(); clearNativeCameraInputs(); stopSelfieCamera(); setPhotoSource('', ''); cameraMsg(''); resetPhotoLocation(); loadGallery();
    }
  } catch (error) {
    Pyrk.toast('photoMsg', {ok:false, message:error?.message || 'Could not prepare the selected photo.'});
  } finally {
    if(submitBtn) {
      submitBtn.disabled = false;
      if (submitBtn.dataset.originalText) submitBtn.textContent = submitBtn.dataset.originalText;
    }
  }
});

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

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

function contestHeader(data){
  const c = data.contest || {};
  const open = data.can_vote === true;
  const groups = (c.groups || []).map(g => g.group_name).filter(Boolean).join(', ');
  return `<div class="contest-gallery-header ${open ? 'open' : 'closed'}">
    <div>
      <p class="eyebrow">Photo contest</p>
      <h3>${open ? 'Voting is open' : 'Voting is not available'}</h3>
      <p class="muted">${Pyrk.esc(data.vote_reason || '')}</p>
    </div>
    <div class="contest-gallery-rules">
      <span class="pill ${open ? 'ok' : 'bad'}">${Pyrk.esc((c.voting_status || 'open') === 'closed' ? 'Closed' : 'Open')}</span>
      <span>${Pyrk.esc(permissionLabel(c.voting_permission))}</span>
      <span>${Pyrk.esc(voteModeLabel(c.vote_mode))}</span>
      ${groups ? `<span>Groups: ${Pyrk.esc(groups)}</span>` : ''}
    </div>
  </div>`;
}


function photoOwnerMeta(p, showOwnerNames = false){
  const votes = `${Number(p.votes_count || 0).toLocaleString()} votes`;
  if (showOwnerNames === true && Number(p.owner_name_visible || 0) === 1) {
    return `<p class="muted owner-photo-meta">By ${Pyrk.esc(p.uploaded_by || 'Member')} · ${votes}</p>`;
  }
  return `<p class="muted anonymous-photo-meta">Anonymous entry · ${Pyrk.esc(p.anonymous_label || 'Photo entry')} · ${votes}</p>`;
}

async function loadGallery(){
  const res = await Pyrk.get('photos.php',{walk_id:window.PYRK_WALK_ID, privacy_refresh:Date.now()});
  const box = document.getElementById('gallery');
  if(!box) return;
  if(!res.ok){ box.innerHTML = `<p class="bad">${Pyrk.esc(res.message)}</p>`; return; }
  renderPhotoUploadPolicy(res.data.upload_policy || null);
  const myVotes = new Set((res.data.my_votes || []).map(Number));
  const canVote = !!res.data.can_vote;
  const showOwnerNames = Number(res.data?.contest?.show_photo_owner_name || 0) === 1;
  const cards = (res.data.photos||[]).map(p => {
    const voted = myVotes.has(Number(p.id));
    return `<article class="photo-card contest-gallery-card ${p.status === 'winner' ? 'winner' : ''}">
      <img src="${Pyrk.esc(p.thumbnail_path)}" alt="${Pyrk.esc(p.title)}">
      <div class="photo-body">
        <div class="photo-title-row"><h3>${Pyrk.esc(p.title)} ${p.status==='winner' ? '🏆' : ''}</h3>${voted ? '<span class="pill ok">Your vote</span>' : ''}</div>
        <p>${Pyrk.esc(p.caption||'')}</p>
        ${photoOwnerMeta(p, showOwnerNames)}
        <div class="button-row">
          <button class="btn btn-small" onclick="votePhoto(${Number(p.id)})" ${canVote ? '' : 'disabled'}>${voted ? 'Change vote' : 'Vote'}</button>
          <button class="btn btn-secondary btn-small" onclick="sharePhoto('${Pyrk.esc(p.title)}','${Pyrk.esc(p.file_path)}')">Share</button>
        </div>
      </div>
    </article>`;
  }).join('') || '<p class="muted">No approved photos yet.</p>';
  box.innerHTML = contestHeader(res.data) + `<div class="gallery pro-contest-gallery">${cards}</div>`;
}

async function votePhoto(photoId){
  const res=await Pyrk.post('votes.php',{photo_id:photoId});
  alert(res.message);
  if(res.ok) loadGallery();
}

async function sharePhoto(title, path){
  const shareText = `${title} - Pyrk Walking Club photo contest`;
  if(navigator.share){ await navigator.share({title:'Pyrk Walking Club', text:shareText, url:new URL(path, location.href).toString()}); }
  else { prompt('Copy this Instagram caption/link:', `${shareText}\n${new URL(path, location.href).toString()}`); }
}

initWaiverSignaturePad(); loadWalkDetail(); loadWaiver(); loadGallery();
