// ── Sessions page logic ────────────────────────────────────────────────────────

let currentType = 'prs';
let sessions    = [];
let currentSession = null;
let _rigCache    = {};
let _gpModalData    = {};
let _analysisCache  = null; // { analysisId, groups, groupPhotos }
let _openModalGpId  = null;
let _sessionPhotoModal = null;
let _sessionPhotoGroupId = null;
let _sessionPhotoGpId    = null; // existing GroupPhoto id when replacing

const TYPE_PATHS = {
  'prs':           '/sessions/prs/',
  'free-practice': '/sessions/free-practice/',
  'speed-shooting':'/sessions/speed-shooting/',
};

// ── Unit selector ─────────────────────────────────────────────────────────────
// fVel, fDist, getDistUnit, getVelUnit, setDistUnit, setVelUnit → utils.js

function renderUnitSelector() {
  const vel  = getVelUnit();
  const dist = getDistUnit();
  return `
    <div class="d-flex align-items-center gap-2 ms-auto">
      <div class="btn-group btn-group-sm" role="group" aria-label="Velocity unit">
        ${['fps','mps'].map(u => `
          <button type="button"
                  class="btn btn-outline-secondary${vel===u?' active':''}"
                  onclick="applyUnits('vel','${u}')">${u}</button>`).join('')}
      </div>
      <div class="btn-group btn-group-sm" role="group" aria-label="Distance unit">
        ${['mm','moa','mrad'].map(u => `
          <button type="button"
                  class="btn btn-outline-secondary${dist===u?' active':''}"
                  onclick="applyUnits('dist','${u}')">${u.toUpperCase()}</button>`).join('')}
      </div>
    </div>`;
}

function applyUnits(key, val) {
  if (key === 'vel') setVelUnit(val); else setDistUnit(val);
  const wrap = document.getElementById('unitSelectorWrap');
  if (wrap) wrap.innerHTML = renderUnitSelector();
  // Re-render from cache — no API call needed
  if (_analysisCache) renderAnalysisGroupsFromCache();
  // Sync the modal's unit buttons and stats if the modal is open
  const modalEl = document.getElementById('groupPhotoModal');
  applyDistUnitButtons(modalEl);
  if (modalEl?.classList.contains('show') && _openModalGpId != null)
    renderGroupPhotoModalStats(_openModalGpId);
}

// Unit switch inside the group photo modal
document.getElementById('groupPhotoModal')?.addEventListener('click', e => {
  const btn = e.target.closest('[data-dist-unit]');
  if (!btn) return;
  applyUnits('dist', btn.dataset.distUnit);
});

// showToast → utils.js

// ── Tab switching ─────────────────────────────────────────────────────────────

document.getElementById('sessionTabs').addEventListener('click', e => {
  const link = e.target.closest('[data-type]');
  if (!link) return;
  e.preventDefault();
  document.querySelectorAll('#sessionTabs .nav-link').forEach(l => l.classList.remove('active'));
  link.classList.add('active');
  currentType = link.dataset.type;
  currentSession = null;
  document.getElementById('noSessionMsg').classList.remove('d-none');
  document.getElementById('sessionDetail').classList.add('d-none');
  loadSessions();
});

// ── Load sessions list ────────────────────────────────────────────────────────

async function loadSessions() {
  document.getElementById('listSpinner').classList.remove('d-none');
  document.getElementById('listEmpty').classList.add('d-none');
  document.getElementById('sessionsList').innerHTML = '';
  try {
    sessions = await apiGetAll(TYPE_PATHS[currentType]);
    renderList();
  } catch(e) {
    document.getElementById('listSpinner').innerHTML =
      '<p class="text-danger small">Failed to load sessions.</p>';
  }
}

function renderList() {
  document.getElementById('listSpinner').classList.add('d-none');
  const list  = document.getElementById('sessionsList');
  const empty = document.getElementById('listEmpty');
  if (!sessions.length) { empty.classList.remove('d-none'); list.innerHTML = ''; return; }
  empty.classList.add('d-none');
  list.innerHTML = sessions.map(s => {
    const label = s.competition_name || s.name || '(unnamed)';
    const sub   = s.date || '';
    const active = currentSession && currentSession.id === s.id ? 'active' : '';
    return `
      <div class="session-item mb-1 ${active}" onclick="selectSession(${s.id})">
        <div class="fw-semibold small">${esc(label)}</div>
        <div class="text-muted small">${esc(sub)}</div>
      </div>`;
  }).join('');
}

// ── Select & display session ──────────────────────────────────────────────────

async function selectSession(id) {
  document.getElementById('noSessionMsg').classList.add('d-none');
  document.getElementById('sessionDetail').classList.add('d-none');
  _analysisCache = null;

  try {
    currentSession = await apiGet(`${TYPE_PATHS[currentType]}${id}/`);
    renderList();
    renderDetail();
    document.getElementById('sessionDetail').classList.remove('d-none');
  } catch(e) {
    showToast('Failed to load session.', 'danger');
  }
}

function renderDetail() {
  const s = currentSession;
  const titleLabel = s.competition_name || s.name || '(unnamed)';
  document.getElementById('detailTitle').textContent = titleLabel;
  // Hide write controls for anonymous viewers
  document.getElementById('togglePublicBtn').classList.toggle('d-none', !_isAuth);
  document.getElementById('deleteSessionBtn').classList.toggle('d-none', !_isAuth);
  document.getElementById('detailMeta').textContent =
    [s.date, s.location].filter(Boolean).join(' · ');

  // Meta card
  let metaHtml = '';
  if (currentType === 'prs') {
    metaHtml = metaRow([
      [t('sessions.meta.competition'), s.competition_name],
      [t('sessions.meta.category'),   s.category],
      [t('sessions.meta.date'),        s.date],
      [t('sessions.meta.location'),    s.location],
      [t('sessions.meta.rig'),         s.rig_detail ? s.rig_detail.name : null],
      [t('sessions.meta.ammo'),        ammoLabel(s)],
    ]);
  } else if (currentType === 'free-practice') {
    metaHtml = metaRow([
      [t('sessions.meta.date'),        s.date],
      [t('sessions.meta.location'),    s.location],
      [t('sessions.meta.distance'),    s.distance_m ? s.distance_m + ' m' : null],
      [t('sessions.meta.target'),      s.target_description],
      [t('sessions.meta.rounds'),      s.rounds_fired],
      [t('sessions.meta.rig'),         s.rig_detail ? s.rig_detail.name : null],
      [t('sessions.meta.ammo'),        ammoLabel(s)],
    ]);
  } else {
    metaHtml = metaRow([
      [t('sessions.meta.date'),     s.date],
      [t('sessions.meta.location'), s.location],
      [t('sessions.meta.format'),   s.format],
      [t('sessions.meta.rounds'),   s.rounds_fired],
      [t('sessions.meta.rig'),      s.rig_detail ? s.rig_detail.name : null],
      [t('sessions.meta.ammo'),     ammoLabel(s)],
    ]);
  }
  document.getElementById('metaFields').innerHTML = metaHtml;

  // Weather fields
  document.getElementById('weatherFields').innerHTML = `
    <div class="col-sm-4">
      <label class="form-label small">${t('sessions.weather.temp')}</label>
      <input type="number" step="0.1" class="form-control form-control-sm" id="wTemp" value="${s.temperature_c ?? ''}">
    </div>
    <div class="col-sm-4">
      <label class="form-label small">${t('sessions.weather.wind')}</label>
      <input type="number" step="0.1" class="form-control form-control-sm" id="wWind" value="${s.wind_speed_ms ?? ''}">
    </div>
    <div class="col-sm-4">
      <label class="form-label small">${t('sessions.weather.dir')}</label>
      <input type="number" class="form-control form-control-sm" id="wDir" min="0" max="359" value="${s.wind_direction_deg ?? ''}">
    </div>
    <div class="col-sm-4">
      <label class="form-label small">${t('sessions.weather.humidity')}</label>
      <input type="number" class="form-control form-control-sm" id="wHumidity" min="0" max="100" value="${s.humidity_pct ?? ''}">
    </div>
    <div class="col-sm-4">
      <label class="form-label small">${t('sessions.weather.pressure')}</label>
      <input type="number" step="0.1" class="form-control form-control-sm" id="wPressure" value="${s.pressure_hpa ?? ''}">
    </div>
    <div class="col-sm-12">
      <label class="form-label small">${t('sessions.weather.notes')}</label>
      <input type="text" class="form-control form-control-sm" id="wNotes" value="${esc(s.weather_notes || '')}">
    </div>`;

  // Public toggle
  renderPublicToggle(s.is_public);

  // Analysis section
  renderAnalysisSection(s);

  // PRS stages
  const stagesSection = document.getElementById('stagesSection');
  if (currentType === 'prs') {
    stagesSection.classList.remove('d-none');
    renderStages(s.stages || []);
  } else {
    stagesSection.classList.add('d-none');
  }
}

// ── Analysis section ──────────────────────────────────────────────────────────

async function renderAnalysisSection(s) {
  const body = document.getElementById('analysisBody');
  if (s.analysis_detail) {
    const a = s.analysis_detail;
    body.innerHTML = `
      <div class="d-flex align-items-center flex-wrap gap-2 mb-3">
        <div>
          <strong>${esc(a.name)}</strong>
          ${a.date ? `<span class="text-muted ms-2 small">${esc(a.date)}</span>` : ''}
        </div>
        <a href="/chrono.html?id=${a.id}" target="_blank" class="btn btn-sm btn-outline-secondary ms-auto">
          <i class="bi bi-box-arrow-up-right me-1"></i>${t('sessions.analysis.view')}
        </a>
        <button class="btn btn-sm btn-outline-danger" onclick="unlinkAnalysis()">
          <i class="bi bi-x-lg me-1"></i>${t('sessions.analysis.unlink')}
        </button>
      </div>
      <div class="d-flex align-items-center gap-2 mb-3 flex-wrap">
        <span class="text-muted small">Units:</span>
        <div id="unitSelectorWrap" class="d-flex gap-2">${renderUnitSelector()}</div>
      </div>
      <div id="analysisGroups">
        <div class="text-center py-3"><div class="spinner-border spinner-border-sm text-primary"></div></div>
      </div>`;
    loadAnalysisGroups(s.analysis);
  } else {
    // Show analysis picker
    let analysisOpts = '<option value="">— Select —</option>';
    try {
      const items = await apiGetAll('/tools/chronograph/');
      analysisOpts += items.map(a => `<option value="${a.id}">${esc(a.name)}${a.date ? ' — ' + a.date : ''}</option>`).join('');
    } catch(e) {}

    body.innerHTML = `
      <p class="text-muted small mb-2">${t('sessions.analysis.none')}</p>
      <div class="d-flex gap-2 align-items-end">
        <select class="form-select form-select-sm" id="analysisPicker" style="max-width:300px">${analysisOpts}</select>
        <button class="btn btn-sm btn-outline-primary" onclick="linkAnalysis()">
          <i class="bi bi-link-45deg me-1"></i>${t('sessions.analysis.link')}
        </button>
      </div>`;
  }
}

async function loadAnalysisGroups(analysisId) {
  const container = document.getElementById('analysisGroups');
  if (!container) return;
  try {
    const detail = await apiGet(`/tools/chronograph/${analysisId}/`);
    const groups = detail.shot_groups || [];
    if (!groups.length) {
      container.innerHTML = '<p class="text-muted small">No shot groups in this analysis.</p>';
      return;
    }
    const groupPhotos = await Promise.all(groups.map(g =>
      apiGet(`/photos/group-photos/?shot_group=${g.id}`)
        .then(d => asList(d))
        .catch(() => [])
    ));
    _analysisCache = { analysisId, groups, groupPhotos };
    renderAnalysisGroupsFromCache();
  } catch(e) {
    const c = document.getElementById('analysisGroups');
    if (c) c.innerHTML = '<span class="text-danger small">Failed to load analysis data.</span>';
  }
}

function renderAnalysisGroupsFromCache() {
  const container = document.getElementById('analysisGroups');
  if (!container || !_analysisCache) return;
  const { groups, groupPhotos } = _analysisCache;

  _gpModalData = {};

  container.innerHTML = groups.map((g, i) => {
    const st   = g.stats || {};
    const gp   = (groupPhotos[i] || [])[0];
    const ammo = g.ammo_detail
      ? `${g.ammo_detail.brand} ${g.ammo_detail.name}`
      : g.ammo_batch_detail
        ? `${g.ammo_batch_detail.recipe_name} ${g.ammo_batch_detail.powder_charge_gr}gr`
        : null;

    if (gp) _gpModalData[gp.id] = { gp, group: g };

    const distM = g.distance_m ? parseFloat(g.distance_m) : null;

    const statsHtml = st.count ? `
      <div class="d-flex flex-wrap gap-3 small mt-1">
        <span><span class="text-muted">Shots:</span> <strong>${st.count}</strong></span>
        <span><span class="text-muted">Avg:</span> <strong>${fVel(st.avg_fps, st.avg_mps)}</strong></span>
        <span><span class="text-muted">SD:</span> <strong>${fVel(st.sd_fps, (st.sd_fps * 0.3048).toFixed(1))}</strong></span>
        <span><span class="text-muted">ES (vel):</span> <strong>${fVel(st.es_fps, (st.es_fps * 0.3048).toFixed(1))}</strong></span>
      </div>` : '<p class="text-muted small mb-0 mt-1">No shots recorded.</p>';

    const gpEsLabel = gp?.analysis?.group_size_mm != null
      ? `<span class="small text-muted">${fDist(gp.analysis.group_size_mm, gp.analysis.group_size_moa, distM)}</span>`
      : '';

    const photoHtml = gp ? `
      <div class="ms-auto d-flex flex-column align-items-end gap-1" style="min-width:100px">
        <img src="/api/photos/${gp.photo.id}/data/"
             style="width:100px;height:75px;object-fit:cover;border-radius:5px;cursor:pointer"
             onclick="openGroupPhotoModal(${gp.id})" title="Click to enlarge">
        ${gpEsLabel}
        <div class="d-flex gap-1 flex-wrap justify-content-end">
          <a href="/group-size.html?gp=${gp.id}" class="btn btn-xs btn-outline-primary py-0 px-2 small">
            <i class="bi bi-crosshair2 me-1"></i>Measure
          </a>
          <button class="btn btn-xs btn-outline-secondary py-0 px-2 small"
                  onclick="openSessionPhotoModal(${g.id}, ${gp.id})">
            <i class="bi bi-arrow-repeat"></i>
          </button>
        </div>
      </div>` : `
      <div class="ms-auto">
        <button class="btn btn-xs btn-outline-secondary py-0 px-2 small"
                onclick="openSessionPhotoModal(${g.id})">
          <i class="bi bi-plus-lg me-1"></i>Add photo
        </button>
      </div>`;

    return `
      <div class="border rounded p-3 mb-2 bg-white">
        <div class="d-flex align-items-start gap-2">
          <div class="flex-grow-1">
            <div class="fw-semibold small">
              ${esc(g.label)}
              ${g.distance_m ? `<span class="text-muted fw-normal ms-1">@ ${g.distance_m} m</span>` : ''}
              ${ammo ? `<span class="badge bg-light text-dark border ms-2 fw-normal">${esc(ammo)}</span>` : ''}
            </div>
            ${statsHtml}
          </div>
          ${photoHtml}
        </div>
      </div>`;
  }).join('');
}

function renderGroupPhotoModalStats(gpId) {
  const data = _gpModalData[gpId];
  if (!data) return;
  const { gp, group } = data;
  const st    = group.stats || {};
  const an    = gp.analysis;
  const distM = group.distance_m ? parseFloat(group.distance_m) : null;

  const statParts = [];
  if (st.count) {
    statParts.push(`<span><span class="text-muted">Shots:</span> <strong>${st.count}</strong></span>`);
    statParts.push(`<span><span class="text-muted">Avg:</span> <strong>${fVel(st.avg_fps, st.avg_mps)}</strong></span>`);
    statParts.push(`<span><span class="text-muted">SD:</span> <strong>${fVel(st.sd_fps, (st.sd_fps * 0.3048).toFixed(1))}</strong></span>`);
    statParts.push(`<span><span class="text-muted">ES (vel):</span> <strong>${fVel(st.es_fps, (st.es_fps * 0.3048).toFixed(1))}</strong></span>`);
  }
  if (an) {
    if (an.group_size_mm != null)
      statParts.push(`<span><span class="text-muted">Group ES:</span> <strong>${fDist(an.group_size_mm, an.group_size_moa, distM)}</strong></span>`);
    if (an.mean_radius_mm != null)
      statParts.push(`<span><span class="text-muted">Mean radius:</span> <strong>${fDist(an.mean_radius_mm, an.mean_radius_moa, distM)}</strong></span>`);
    const wx = an.windage_offset_mm != null ? parseFloat(an.windage_offset_mm) : null;
    const wy = an.elevation_offset_mm != null ? parseFloat(an.elevation_offset_mm) : null;
    if (wx != null && wy != null && (Math.abs(wx) > 0.5 || Math.abs(wy) > 0.5)) {
      const wDir = wx > 0 ? 'R' : 'L';
      const eDir = wy > 0 ? 'H' : 'Low';
      statParts.push(`<span><span class="text-muted">Offset:</span> <strong>${fDist(Math.abs(wx), an.windage_offset_moa, distM)} ${wDir} / ${fDist(Math.abs(wy), an.elevation_offset_moa, distM)} ${eDir}</strong></span>`);
    }
  }
  document.getElementById('gpModalStats').innerHTML =
    statParts.length ? statParts.join('') : '<span class="text-muted">No data computed yet.</span>';
}

function openGroupPhotoModal(gpId) {
  const data = _gpModalData[gpId];
  if (!data) return;
  const { gp, group } = data;

  _openModalGpId = gpId;

  document.getElementById('gpModalTitle').textContent =
    group.label + (group.distance_m ? ` — ${group.distance_m} m` : '');

  renderGroupPhotoModalStats(gpId);

  document.getElementById('gpModalMeasureBtn').href = `/group-size.html?gp=${gp.id}`;
  document.getElementById('gpModalOpenBtn').href    = `/api/photos/${gp.photo.id}/data/`;
  document.getElementById('gpModalImg').src         = `/api/photos/${gp.photo.id}/data/`;

  const modalEl = document.getElementById('groupPhotoModal');
  applyDistUnitButtons(modalEl);
  bootstrap.Modal.getOrCreateInstance(modalEl).show();
}

document.getElementById('groupPhotoModal')?.addEventListener('hidden.bs.modal', () => {
  _openModalGpId = null;
});

// ── Session photo upload ───────────────────────────────────────────────────────

function openSessionPhotoModal(groupId, existingGpId = null) {
  _sessionPhotoGroupId = groupId;
  _sessionPhotoGpId    = existingGpId || null;
  document.getElementById('sessionPhotoFileInput').value = '';
  document.getElementById('sessionPhotoCaption').value   = '';
  document.getElementById('sessionPhotoAlert').classList.add('d-none');
  const title = document.querySelector('#sessionPhotoModal .modal-title');
  if (title) title.textContent = existingGpId ? 'Replace photo' : 'Add photo';
  if (!_sessionPhotoModal)
    _sessionPhotoModal = new bootstrap.Modal('#sessionPhotoModal');
  _sessionPhotoModal.show();
}

document.addEventListener('DOMContentLoaded', () => {
  document.getElementById('sessionPhotoSubmitBtn').addEventListener('click', async () => {
    const alertEl  = document.getElementById('sessionPhotoAlert');
    const spinner  = document.getElementById('sessionPhotoSpinner');
    const submitBtn = document.getElementById('sessionPhotoSubmitBtn');
    alertEl.classList.add('d-none');

    const file = document.getElementById('sessionPhotoFileInput').files[0];
    if (!file) {
      alertEl.textContent = 'Please select an image file.';
      alertEl.classList.remove('d-none');
      return;
    }

    submitBtn.disabled = true;
    spinner.classList.remove('d-none');
    try {
      if (_sessionPhotoGpId)
        await apiDelete(`/photos/group-photos/${_sessionPhotoGpId}/`);

      const formData = new FormData();
      formData.append('file', file);
      const photo = await apiFetch('/photos/upload/', { method: 'POST', body: formData })
        .then(async r => {
          if (!r.ok) { const e = await r.json().catch(() => ({})); throw Object.assign(new Error('Upload failed'), { data: e }); }
          return r.json();
        });

      const caption = document.getElementById('sessionPhotoCaption').value.trim();
      await apiPost('/photos/group-photos/', {
        photo_id: photo.id,
        shot_group: _sessionPhotoGroupId,
        ...(caption ? { caption } : {}),
      });

      _sessionPhotoModal.hide();
      showToast(_sessionPhotoGpId ? 'Photo replaced.' : 'Photo added.');
      // Reload the analysis groups section
      const analysisId = currentSession?.analysis;
      if (analysisId) loadAnalysisGroups(analysisId);
    } catch(e) {
      alertEl.textContent = (e.data && (e.data.detail || JSON.stringify(e.data))) || 'Upload failed.';
      alertEl.classList.remove('d-none');
    } finally {
      submitBtn.disabled = false;
      spinner.classList.add('d-none');
    }
  });
});

async function linkAnalysis() {
  const val = document.getElementById('analysisPicker')?.value;
  if (!val) return;
  try {
    await apiPatch(`${TYPE_PATHS[currentType]}${currentSession.id}/`, { analysis: parseInt(val) });
    currentSession = await apiGet(`${TYPE_PATHS[currentType]}${currentSession.id}/`);
    renderAnalysisSection(currentSession);
    showToast('Analysis linked.');
  } catch(e) {
    showToast('Failed to link analysis.', 'danger');
  }
}

async function unlinkAnalysis() {
  try {
    await apiPatch(`${TYPE_PATHS[currentType]}${currentSession.id}/`, { analysis: null });
    currentSession = await apiGet(`${TYPE_PATHS[currentType]}${currentSession.id}/`);
    renderAnalysisSection(currentSession);
    showToast('Analysis unlinked.');
  } catch(e) {
    showToast('Failed to unlink analysis.', 'danger');
  }
}

// ── Stages (PRS) ──────────────────────────────────────────────────────────────

function ammoLabel(s) {
  if (s.ammo_detail) {
    const cal = s.ammo_detail.caliber_detail?.name || '';
    return `${s.ammo_detail.brand} ${s.ammo_detail.name}${cal ? ' (' + cal + ')' : ''}`;
  }
  if (s.reloaded_batch_detail) return `${s.reloaded_batch_detail.recipe_name} — ${s.reloaded_batch_detail.powder_charge_gr}gr ${s.reloaded_batch_detail.powder}`;
  return null;
}

function metaRow(pairs) {
  return pairs.filter(([,v]) => v != null && v !== '').map(([k, v]) =>
    `<div class="col-sm-4"><span class="text-muted">${k}</span><br><strong>${esc(String(v))}</strong></div>`
  ).join('');
}

function renderStages(stages) {
  const list = document.getElementById('stagesList');
  if (!stages.length) {
    list.innerHTML = `<p class="text-muted small">${t('sessions.stages.none')}</p>`;
    return;
  }
  list.innerHTML = stages.map(st => stageCard(st)).join('');
}

function stageCard(st) {
  const hasResults = st.hits != null || st.score != null || st.time_taken_s != null;
  const posLabel = { PRONE:'Prone', STANDING:'Standing', SITTING:'Sitting',
    KNEELING:'Kneeling', BARRICADE:'Barricade', UNSUPPORTED:'Unsupported', OTHER:'Other' }[st.position] || st.position;

  return `
  <div class="card mb-2 stage-card ${hasResults ? 'has-results' : ''}" id="stage-${st.id}">
    <div class="card-header d-flex align-items-center justify-content-between py-2">
      <span class="fw-semibold">Stage ${st.order} — ${esc(posLabel)} @ ${st.distance_m}m</span>
      <div class="d-flex gap-1">
        ${hasResults
          ? `<span class="badge bg-success">${t('sessions.stage.badge.done')}</span>`
          : `<span class="badge bg-secondary">${t('sessions.stage.badge.pending')}</span>`}
        <button class="btn btn-xs btn-outline-danger py-0 px-1" onclick="deleteStage(${st.id})" title="${t('btn.delete')}">
          <i class="bi bi-trash"></i>
        </button>
      </div>
    </div>
    <div class="card-body py-2">
      <div class="row g-3">

        <!-- Prep -->
        <div class="col-md-4">
          <div class="phase-label text-secondary mb-1">${t('sessions.stage.prep')}</div>
          <table class="table table-sm mb-0" style="font-size:.85rem">
            <tr><td class="text-muted">${t('sessions.stage.target')}</td><td>${st.target_width_cm ? st.target_width_cm + ' × ' + st.target_height_cm + ' cm' : '—'}</td></tr>
            <tr><td class="text-muted">${t('sessions.stage.maxtime')}</td><td>${st.max_time_s ? st.max_time_s + 's' : '—'}</td></tr>
            <tr><td class="text-muted">${t('sessions.stage.shots')}</td><td>${st.shots_count}</td></tr>
            ${st.notes_prep ? `<tr><td class="text-muted">${t('sessions.stage.notes')}</td><td>${esc(st.notes_prep)}</td></tr>` : ''}
          </table>
        </div>

        <!-- Execution -->
        <div class="col-md-4">
          <div class="phase-label text-primary mb-1">${t('sessions.stage.corrections')}</div>
          <div class="d-flex gap-2 align-items-end mb-2">
            <div>
              <label class="form-label small mb-0">${t('sessions.stage.computed.elev')}</label>
              <input class="form-control form-control-sm corr-field" readonly
                     value="${st.computed_elevation != null ? st.computed_elevation + ' ' + (st.correction_unit||'') : '—'}" style="background:#f8f9fa">
            </div>
            <div>
              <label class="form-label small mb-0">${t('sessions.stage.computed.wind')}</label>
              <input class="form-control form-control-sm corr-field" readonly
                     value="${st.computed_windage != null ? st.computed_windage + ' ' + (st.correction_unit||'') : '—'}" style="background:#f8f9fa">
            </div>
          </div>
          <button class="btn btn-xs btn-outline-primary py-0 px-2 mb-2" onclick="computeCorrections(${st.id})">
            <i class="bi bi-calculator me-1"></i>Compute
          </button>
          <div class="d-flex gap-2">
            <div>
              <label class="form-label small mb-0">${t('sessions.stage.actual.elev')}</label>
              <input type="number" step="0.25" class="form-control form-control-sm corr-field"
                     id="aElev-${st.id}" value="${st.actual_elevation ?? ''}">
            </div>
            <div>
              <label class="form-label small mb-0">${t('sessions.stage.actual.wind')}</label>
              <input type="number" step="0.25" class="form-control form-control-sm corr-field"
                     id="aWind-${st.id}" value="${st.actual_windage ?? ''}">
            </div>
            <div class="align-self-end">
              <button class="btn btn-xs btn-outline-secondary py-0 px-2" onclick="saveCorrections(${st.id})">${t('sessions.stage.save.corrections')}</button>
            </div>
          </div>
        </div>

        <!-- Results -->
        <div class="col-md-4">
          <div class="phase-label text-success mb-1">${t('sessions.stage.results')}</div>
          <div class="d-flex gap-2 align-items-end">
            <div>
              <label class="form-label small mb-0">${t('sessions.stage.hits')} / ${st.shots_count}</label>
              <input type="number" min="0" max="${st.shots_count}" class="form-control form-control-sm corr-field"
                     id="rHits-${st.id}" value="${st.hits ?? ''}">
            </div>
            <div>
              <label class="form-label small mb-0">${t('sessions.stage.score')}</label>
              <input type="number" min="0" class="form-control form-control-sm corr-field"
                     id="rScore-${st.id}" value="${st.score ?? ''}">
            </div>
            <div>
              <label class="form-label small mb-0">${t('sessions.stage.time')}</label>
              <input type="number" step="0.1" class="form-control form-control-sm corr-field"
                     id="rTime-${st.id}" value="${st.time_taken_s ?? ''}">
            </div>
          </div>
          <button class="btn btn-xs btn-outline-success py-0 px-2 mt-2" onclick="saveResults(${st.id})">
            <i class="bi bi-floppy me-1"></i>${t('sessions.stage.save.results')}
          </button>
        </div>

      </div>
    </div>
  </div>`;
}

async function computeCorrections(stageId) {
  try {
    const result = await apiPost(
      `/sessions/prs/${currentSession.id}/stages/${stageId}/compute-corrections/`, {}
    );
    showToast(result.message || 'Corrections computed.');
    currentSession = await apiGet(`${TYPE_PATHS['prs']}${currentSession.id}/`);
    renderStages(currentSession.stages || []);
  } catch(e) {
    showToast('Failed to compute corrections.', 'danger');
  }
}

async function saveCorrections(stageId) {
  const elevation = document.getElementById(`aElev-${stageId}`).value || null;
  const windage   = document.getElementById(`aWind-${stageId}`).value || null;
  try {
    await apiPatch(`/sessions/prs/${currentSession.id}/stages/${stageId}/`, {
      actual_elevation: elevation,
      actual_windage: windage,
    });
    showToast('Corrections saved.');
  } catch(e) {
    showToast('Failed to save corrections.', 'danger');
  }
}

async function saveResults(stageId) {
  const hits  = document.getElementById(`rHits-${stageId}`).value || null;
  const score = document.getElementById(`rScore-${stageId}`).value || null;
  const time  = document.getElementById(`rTime-${stageId}`).value || null;
  try {
    await apiPatch(`/sessions/prs/${currentSession.id}/stages/${stageId}/`, {
      hits: hits ? parseInt(hits) : null,
      score: score ? parseInt(score) : null,
      time_taken_s: time || null,
    });
    showToast('Results saved.');
    currentSession = await apiGet(`${TYPE_PATHS['prs']}${currentSession.id}/`);
    renderStages(currentSession.stages || []);
  } catch(e) {
    showToast('Failed to save results.', 'danger');
  }
}

async function deleteStage(stageId) {
  if (!confirm('Delete this stage?')) return;
  try {
    await apiDelete(`/sessions/prs/${currentSession.id}/stages/${stageId}/`);
    currentSession = await apiGet(`${TYPE_PATHS['prs']}${currentSession.id}/`);
    renderStages(currentSession.stages || []);
    showToast('Stage deleted.');
  } catch(e) {
    showToast('Failed to delete stage.', 'danger');
  }
}

// ── Weather save ──────────────────────────────────────────────────────────────

document.getElementById('saveWeatherBtn').addEventListener('click', async () => {
  if (!currentSession) return;
  const payload = {
    temperature_c:      document.getElementById('wTemp').value || null,
    wind_speed_ms:      document.getElementById('wWind').value || null,
    wind_direction_deg: document.getElementById('wDir').value || null,
    humidity_pct:       document.getElementById('wHumidity').value || null,
    pressure_hpa:       document.getElementById('wPressure').value || null,
    weather_notes:      document.getElementById('wNotes').value,
  };
  try {
    await apiPatch(`${TYPE_PATHS[currentType]}${currentSession.id}/`, payload);
    showToast(t('sessions.weather.save') + ' ✓');
  } catch(e) {
    showToast('Failed to save weather.', 'danger');
  }
});

// ── Public toggle (renderPublicToggle → utils.js) ────────────────────────────

document.getElementById('togglePublicBtn').addEventListener('click', async () => {
  if (!currentSession) return;
  const newVal = !currentSession.is_public;
  try {
    await apiPatch(`${TYPE_PATHS[currentType]}${currentSession.id}/`, { is_public: newVal });
    currentSession.is_public = newVal;
    renderPublicToggle(newVal);
    showToast(newVal ? 'Session is now public.' : 'Session is now private.');
  } catch(e) {
    showToast('Failed to update visibility.', 'danger');
  }
});

// ── Delete session ────────────────────────────────────────────────────────────

document.getElementById('deleteSessionBtn').addEventListener('click', async () => {
  if (!currentSession) return;
  if (!confirm(t('sessions.delete.confirm'))) return;
  try {
    await apiDelete(`${TYPE_PATHS[currentType]}${currentSession.id}/`);
    sessions = sessions.filter(s => s.id !== currentSession.id);
    currentSession = null;
    renderList();
    document.getElementById('noSessionMsg').classList.remove('d-none');
    document.getElementById('sessionDetail').classList.add('d-none');
    showToast('Session deleted.');
  } catch(e) {
    showToast('Failed to delete session.', 'danger');
  }
});

// ── Create session ────────────────────────────────────────────────────────────

const createModal = new bootstrap.Modal('#createModal');

document.getElementById('newSessionBtn').addEventListener('click', async () => {
  document.getElementById('prsFields').classList.toggle('d-none', currentType !== 'prs');
  document.getElementById('freePracticeFields').classList.toggle('d-none', currentType !== 'free-practice');
  document.getElementById('speedFields').classList.toggle('d-none', currentType !== 'speed-shooting');

  const typeLabels = {
    prs: t('sessions.tab.prs'),
    'free-practice': t('sessions.tab.fp'),
    'speed-shooting': t('sessions.tab.speed'),
  };
  document.getElementById('createModalTitle').innerHTML =
    `<i class="bi bi-plus-lg me-2"></i>${typeLabels[currentType]}`;

  document.getElementById('createDate').value = new Date().toISOString().slice(0, 10);
  document.getElementById('createAlert').classList.add('d-none');

  // Load rigs
  const sel = document.getElementById('createRig');
  sel.innerHTML = '<option value="">— None —</option>';
  try {
    const rigs = await apiGet('/rigs/');
    const rigData = asList(rigs);
    // Cache rig details for caliber filtering
    rigData.forEach(r => { _rigCache[r.id] = r; });
    sel.innerHTML = '<option value="">— None —</option>' +
      rigData.map(r => `<option value="${r.id}">${esc(r.name)}</option>`).join('');
  } catch(e) {}

  // Reset ammo
  document.querySelectorAll('input[name=ammoType]').forEach(r => { r.checked = r.value === 'none'; });
  document.getElementById('createAmmo').classList.add('d-none');
  document.getElementById('createBatch').classList.add('d-none');
  document.getElementById('suggestAmmoWrap').classList.add('d-none');

  // Reset analysis
  document.querySelectorAll('input[name=analysisMode]').forEach(r => { r.checked = r.value === 'none'; });
  document.getElementById('createAnalysisPicker').classList.add('d-none');
  document.getElementById('createAnalysisUpload').classList.add('d-none');
  document.getElementById('createAnalysisCsv').value = '';

  // Load existing analyses into picker (lazy, only if not already loaded)
  const picker = document.getElementById('createAnalysisPicker');
  if (!picker.options.length || picker.options[0]?.value === '') {
    picker.innerHTML = '<option value="">— Select analysis —</option>';
    try {
      const items = await apiGetAll('/tools/chronograph/');
      picker.innerHTML = '<option value="">— Select analysis —</option>' +
        items.map(a => `<option value="${a.id}">${esc(a.name)}${a.date ? ' — ' + a.date : ''}</option>`).join('');
    } catch(e) {}
  }

  createModal.show();
});

// ── Rig → caliber → ammo filtering ──────────────────────────────────────────

document.getElementById('createRig').addEventListener('change', () => {
  // When rig changes, if factory ammo is already selected, re-filter by new caliber
  const v = document.querySelector('input[name=ammoType]:checked')?.value;
  if (v === 'factory') reloadAmmoForRig();
});

async function reloadAmmoForRig() {
  const rigId = document.getElementById('createRig').value;
  let caliber = null;

  if (rigId) {
    // Check cache first; if full rig detail not cached, fetch it
    let rig = _rigCache[rigId];
    if (!rig || rig.primary_caliber === undefined) {
      try { rig = await apiGet(`/rigs/${rigId}/`); _rigCache[rigId] = rig; } catch(e) {}
    }
    caliber = rig?.primary_caliber || null;
  }

  const ammoSel = document.getElementById('createAmmo');
  ammoSel.innerHTML = '<option value="">— Select ammo —</option>';
  try {
    const caliberId = caliber?.id || null;
    const url = caliberId
      ? `/gears/ammo/?status=VERIFIED&caliber=${caliberId}&page_size=100`
      : `/gears/ammo/?status=VERIFIED&page_size=100`;
    const data = await apiGet(url);
    const items = asList(data);
    ammoSel.innerHTML = '<option value="">— Select ammo —</option>' +
      items.map(a => {
        const calName = a.caliber_detail?.name || '';
        return `<option value="${a.id}">${esc(a.brand + ' ' + a.name)}${calName ? ' (' + esc(calName) + ')' : ''}</option>`;
      }).join('');
    if (caliberId && !items.length) {
      ammoSel.innerHTML += `<option disabled>No verified ammo found for caliber ${esc(caliber.name)}</option>`;
    }
  } catch(e) {}
}

// ── Ammo type radio toggle ───────────────────────────────────────────────────

document.querySelectorAll('input[name=ammoType]').forEach(radio => {
  radio.addEventListener('change', async () => {
    const v = document.querySelector('input[name=ammoType]:checked').value;
    const ammoSel  = document.getElementById('createAmmo');
    const batchSel = document.getElementById('createBatch');
    const suggestWrap = document.getElementById('suggestAmmoWrap');
    ammoSel.classList.add('d-none');
    batchSel.classList.add('d-none');
    suggestWrap.classList.add('d-none');

    if (v === 'factory') {
      ammoSel.classList.remove('d-none');
      suggestWrap.classList.remove('d-none');
      await reloadAmmoForRig();
    } else if (v === 'reload') {
      batchSel.classList.remove('d-none');
      if (!batchSel.options.length || batchSel.options[0].value === '') {
        try {
          const items = await apiGetAll('/reloading/batches/');
          batchSel.innerHTML = '<option value="">— Select batch —</option>' +
            items.map(b => `<option value="${b.id}">${esc(b.recipe || '')} — ${b.powder_charge_gr}gr</option>`).join('');
        } catch(e) {}
      }
    }
  });
});

// ── Analysis mode toggle ──────────────────────────────────────────────────────

document.querySelectorAll('input[name=analysisMode]').forEach(radio => {
  radio.addEventListener('change', () => {
    const v = document.querySelector('input[name=analysisMode]:checked').value;
    document.getElementById('createAnalysisPicker').classList.toggle('d-none', v !== 'link');
    document.getElementById('createAnalysisUpload').classList.toggle('d-none', v !== 'upload');
  });
});

// ── Suggest ammo ─────────────────────────────────────────────────────────────

document.getElementById('toggleSuggestAmmo').addEventListener('click', async e => {
  e.preventDefault();
  const form = document.getElementById('suggestAmmoForm');
  form.classList.toggle('d-none');
  if (!form.classList.contains('d-none')) {
    const sel = document.getElementById('saCaliber');
    if (sel && (!sel.options.length || sel.options[0].text === 'Loading…')) {
      await loadCalibersIntoSelect(sel);
    }
  }
});

document.getElementById('submitSuggestAmmo').addEventListener('click', async () => {
  const alertEl = document.getElementById('saAlert');
  alertEl.classList.add('d-none');
  const brand      = document.getElementById('saBrand').value.trim();
  const name       = document.getElementById('saName').value.trim();
  const caliberId  = document.getElementById('saCaliber').value;
  const weight     = document.getElementById('saBulletWeight').value;
  const bulletType = document.getElementById('saBulletType').value;

  if (!brand || !name || !caliberId || !weight || !bulletType) {
    alertEl.textContent = 'Brand, name, caliber, bullet weight and bullet type are required.';
    alertEl.classList.remove('d-none');
    return;
  }

  try {
    const payload = {
      brand, name,
      caliber: parseInt(caliberId),
      bullet_weight_gr: parseFloat(weight),
      bullet_type: bulletType,
    };
    const created = await apiPost('/gears/ammo/', payload);
    // Add it to the ammo selector and select it
    const ammoSel = document.getElementById('createAmmo');
    const calName = created.caliber_detail?.name || '';
    const opt = new Option(`${created.brand} ${created.name}${calName ? ' (' + calName + ')' : ''} — pending`, created.id, true, true);
    ammoSel.appendChild(opt);
    document.getElementById('suggestAmmoForm').classList.add('d-none');
    showToast('Ammo suggestion submitted!');
  } catch(e) {
    alertEl.textContent = (e.data && formatErrors(e.data)) || 'Submission failed.';
    alertEl.classList.remove('d-none');
  }
});

// ── Create session submit ─────────────────────────────────────────────────────

document.getElementById('createSubmitBtn').addEventListener('click', async () => {
  const alertEl  = document.getElementById('createAlert');
  const spinner  = document.getElementById('createSpinner');
  alertEl.classList.add('d-none');

  const ammoType = document.querySelector('input[name=ammoType]:checked').value;
  const payload = {
    name:     document.getElementById('createName').value.trim(),
    date:     document.getElementById('createDate').value,
    location: document.getElementById('createLocation').value.trim(),
  };

  const rigVal = document.getElementById('createRig').value;
  if (rigVal) payload.rig = parseInt(rigVal);

  if (ammoType === 'factory') {
    const v = document.getElementById('createAmmo').value;
    if (v) payload.ammo = parseInt(v);
  } else if (ammoType === 'reload') {
    const v = document.getElementById('createBatch').value;
    if (v) payload.reloaded_batch = parseInt(v);
  }

  if (currentType === 'prs') {
    payload.competition_name = document.getElementById('createCompetition').value.trim();
    payload.category         = document.getElementById('createCategory').value.trim();
  } else if (currentType === 'free-practice') {
    const d = document.getElementById('createDistance').value;
    if (d) payload.distance_m = parseInt(d);
    payload.target_description = document.getElementById('createTarget').value.trim();
  } else {
    payload.format = document.getElementById('createFormat').value.trim();
  }

  if (!payload.date) {
    alertEl.textContent = 'Date is required.';
    alertEl.classList.remove('d-none');
    return;
  }

  const analysisMode = document.querySelector('input[name=analysisMode]:checked').value;
  if (analysisMode === 'link') {
    const v = document.getElementById('createAnalysisPicker').value;
    if (v) payload.analysis = parseInt(v);
  }

  // Validate CSV upload fields before disabling the button
  if (analysisMode === 'upload') {
    const csvFile = document.getElementById('createAnalysisCsv').files[0];
    if (!csvFile) {
      alertEl.textContent = 'Please select a CSV file for the analysis.';
      alertEl.classList.remove('d-none');
      return;
    }
  }

  spinner.classList.remove('d-none');
  document.getElementById('createSubmitBtn').disabled = true;

  try {
    // If uploading a new analysis, do that first and attach the ID
    if (analysisMode === 'upload') {
      const csvFile  = document.getElementById('createAnalysisCsv').files[0];
      const velUnit  = document.querySelector('input[name=createAnalysisVelUnit]:checked')?.value || 'fps';
      const chronoType = document.getElementById('createAnalysisChronoType').value;
      const analysisName = document.getElementById('createAnalysisName').value.trim() || payload.name;
      const formData = new FormData();
      formData.append('file', csvFile);
      formData.append('velocity_unit', velUnit);
      formData.append('chrono_type', chronoType);
      if (analysisName) formData.append('name', analysisName);
      if (payload.date) formData.append('date', payload.date);
      const analysis = await apiFetch('/tools/chronograph/upload/', { method: 'POST', body: formData })
        .then(async r => {
          if (!r.ok) { const e = await r.json().catch(() => ({})); throw Object.assign(new Error('CSV upload failed'), { data: e }); }
          return r.json();
        });
      payload.analysis = analysis.id;
    }

    const created = await apiPost(TYPE_PATHS[currentType], payload);
    sessions.unshift(created);
    renderList();
    createModal.hide();
    await selectSession(created.id);
    showToast('Session created!');
  } catch(e) {
    alertEl.textContent = (e.data && formatErrors(e.data)) || 'Failed to create session.';
    alertEl.classList.remove('d-none');
  } finally {
    spinner.classList.add('d-none');
    document.getElementById('createSubmitBtn').disabled = false;
  }
});

// ── Add Stage (PRS) ───────────────────────────────────────────────────────────

const addStageModal = new bootstrap.Modal('#addStageModal');

document.getElementById('addStageBtn').addEventListener('click', () => {
  if (!currentSession) return;
  const nextOrder = (currentSession.stages || []).length + 1;
  document.getElementById('stageOrder').value    = nextOrder;
  document.getElementById('stagePosition').value = 'PRONE';
  document.getElementById('stageDistance').value = '';
  document.getElementById('stageMaxTime').value  = '';
  document.getElementById('stageShots').value    = '1';
  document.getElementById('stageTargetW').value  = '';
  document.getElementById('stageTargetH').value  = '';
  document.getElementById('stageNotePrep').value = '';
  document.getElementById('stageAlert').classList.add('d-none');
  addStageModal.show();
});

document.getElementById('stageSubmitBtn').addEventListener('click', async () => {
  const alertEl = document.getElementById('stageAlert');
  alertEl.classList.add('d-none');

  const dist = document.getElementById('stageDistance').value;
  if (!dist) {
    alertEl.textContent = 'Distance is required.';
    alertEl.classList.remove('d-none');
    return;
  }

  const payload = {
    order:       parseInt(document.getElementById('stageOrder').value),
    position:    document.getElementById('stagePosition').value,
    distance_m:  parseInt(dist),
    shots_count: parseInt(document.getElementById('stageShots').value) || 1,
    notes_prep:  document.getElementById('stageNotePrep').value.trim(),
  };
  const maxTime = document.getElementById('stageMaxTime').value;
  if (maxTime) payload.max_time_s = parseInt(maxTime);
  const tw = document.getElementById('stageTargetW').value;
  const th = document.getElementById('stageTargetH').value;
  if (tw) payload.target_width_cm  = parseFloat(tw);
  if (th) payload.target_height_cm = parseFloat(th);

  try {
    await apiPost(`/sessions/prs/${currentSession.id}/stages/`, payload);
    currentSession = await apiGet(`${TYPE_PATHS['prs']}${currentSession.id}/`);
    renderStages(currentSession.stages || []);
    addStageModal.hide();
    showToast('Stage added.');
  } catch(e) {
    alertEl.textContent = (e.data && formatErrors(e.data)) || 'Failed to add stage.';
    alertEl.classList.remove('d-none');
  }
});

// ── Utility ───────────────────────────────────────────────────────────────────

function esc(s) {
  if (s == null) return '';
  return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}

// ── Init ──────────────────────────────────────────────────────────────────────

const _isAuth = !!getAccess();
if (!_isAuth) {
  document.getElementById('newSessionBtn').classList.add('d-none');
}

applyTranslations();
loadSessions();
