// ============================================================
// PHYSICAL EXAM GUIDE — step-by-step OSCE reference + report generator
// ============================================================
// Sources for the exam content:
//   - Bates' Guide to Physical Examination, 13th edition (technique)
//   - Nelson Textbook of Pediatrics, 22nd edition (age-specific content)
//   - Hutchison's Clinical Methods, 25th edition (systematic approach)
//   - Developmental reflex timing: Fenichel Clinical Pediatric Neurology, 8th ed
//
// Data model:
//   PE_DATA[ageGroup][system] = { overview, components: [{ name, steps: [{label, method, normal}], abnormalHints }] }
// Each step has its own Normal / Abnormal / Skip toggle so the physician
// ticks the exam off step-by-step and flags specific abnormal findings.
// Report generation (POST /api/generate-pe-narrative) receives the flat
// step array and produces a Technique + Findings narrative.
// ============================================================

  var SCALES = {};
  var SYSTEM_SCALES = {};
  var APTM_LEGEND = [];
  var INNOCENT_MURMURS = [];
  var RESP_SOUNDS = [];
  var CARDIAC_SOUNDS = [];
  var PE_DATA = {};
  var PE_GUIDE_DATA_URL = '/data/pe-guide.json';
  var peGuideDataPromise = null;

  function applyPeGuideData(data) {
    data = data || {};
    SCALES = data.scales || {};
    SYSTEM_SCALES = data.systemScales || {};
    APTM_LEGEND = data.aptmLegend || [];
    INNOCENT_MURMURS = data.innocentMurmurs || [];
    RESP_SOUNDS = data.respiratorySounds || [];
    CARDIAC_SOUNDS = data.cardiacSounds || [];
    PE_DATA = data.peData || {};
  }

  function loadPeGuideData() {
    if (!peGuideDataPromise) {
      peGuideDataPromise = fetch(PE_GUIDE_DATA_URL)
        .then(function (r) {
          if (!r.ok) throw new Error('Failed to load PE Guide data');
          return r.json();
        })
        .then(function (data) {
          applyPeGuideData(data);
          return data;
        });
    }
    return peGuideDataPromise;
  }

  // ────────────────────────────────────────────────────────────
  // STATE + RENDER
  // ────────────────────────────────────────────────────────────
  var _inited = false;
  // state keyed as 'system-componentIdx-stepIdx' → { label, method, normal, status, note }
  var state = {};
  var currentSystem = 'msk';
  var currentAgeGroup = '';

  document.addEventListener('tabChanged', function (e) {
    if (e.detail.tab !== 'peguide' || _inited) return;
    _inited = true;
    init().catch(function (err) {
      console.error('[PEGuide] init error', err);
      var content = document.getElementById('pe-content');
      if (content) content.innerHTML = '<p style="text-align:center;color:#ef4444;padding:40px;">Unable to load PE Guide data.</p>';
    });
  });

  async function init() {
    await loadPeGuideData();
    var ageSelect = document.getElementById('pe-age-group');
    var content = document.getElementById('pe-content');
    var actions = document.getElementById('pe-actions');

    function applyAgeGroup(ag) {
      currentAgeGroup = ag;
      state = {};
      document.getElementById('pe-output').classList.add('hidden');
      if (!currentAgeGroup || !PE_DATA[currentAgeGroup]) {
        content.innerHTML = '<p style="text-align:center;color:#9ca3af;padding:40px;">Select an age group above.</p>';
        actions.style.display = 'none';
        return;
      }
      renderSystem();
      actions.style.display = 'flex';
    }
    ageSelect.addEventListener('change', function () {
      applyAgeGroup(ageSelect.value);
      if (window.UIState) window.UIState.set('pe.ageGroup', ageSelect.value || '');
    });

    function applySystem(sys) {
      document.querySelectorAll('[data-pesystem]').forEach(function (b) {
        b.classList.toggle('active', b.dataset.pesystem === sys);
      });
      currentSystem = sys;
      state = {};
      document.getElementById('pe-output').classList.add('hidden');
      if (currentAgeGroup) renderSystem();
    }
    document.querySelectorAll('[data-pesystem]').forEach(function (btn) {
      btn.addEventListener('click', function () {
        applySystem(btn.dataset.pesystem);
        if (window.UIState) window.UIState.set('pe.system', btn.dataset.pesystem);
      });
    });

    // Restore persisted selections. Age group must be restored first because
    // the system render depends on it.
    if (window.UIState) {
      var savedAge = window.UIState.get('pe.ageGroup');
      if (savedAge && PE_DATA[savedAge]) {
        ageSelect.value = savedAge;
        applyAgeGroup(savedAge);
      }
      var savedSys = window.UIState.get('pe.system');
      if (savedSys && /^(msk|neuro|resp|cv)$/.test(savedSys)) {
        applySystem(savedSys);
      }
    }

    document.getElementById('pe-all-normal').addEventListener('click', setAllNormal);
    document.getElementById('pe-all-clear').addEventListener('click', clearAll);
    document.getElementById('pe-generate-btn').addEventListener('click', generate);
  }

  // Responsive two-column grid helper: side-by-side on wide screens, stacked on narrow
  var TWO_COL_GRID = 'display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px;align-items:start;';

  // Render a single sound card with native <audio controls> (play/pause/seek
  // free on all platforms incl. mobile). Falls back to a synth play button
  // when the sound has no real recording.
  function renderSoundCard(s) {
    var player = '<audio class="pe-audio" controls preload="none" src="' + esc(s.src) + '" style="width:100%;height:36px;"></audio>';
    var html = '';
    html += '<div style="border:1px solid var(--g200);border-radius:8px;padding:10px;background:#fff;">';
    html += '  <div style="font-weight:600;font-size:13px;color:var(--g800);margin-bottom:4px;">' + esc(s.title) + '</div>';
    if (s.where)    html += '  <div style="font-size:11px;color:var(--g500);margin-bottom:3px;"><strong>Where:</strong> ' + esc(s.where) + '</div>';
    if (s.rate)     html += '  <div style="font-size:11px;color:var(--g500);margin-bottom:3px;"><strong>Rate:</strong> ' + esc(s.rate) + '</div>';
    if (s.features) html += '  <div style="font-size:12px;color:var(--g700);line-height:1.5;margin-bottom:3px;"><strong>Features:</strong> ' + esc(s.features) + '</div>';
    if (s.clinical) html += '  <div style="font-size:11px;color:var(--g600);line-height:1.5;margin-bottom:8px;"><strong>Clinical:</strong> ' + esc(s.clinical) + '</div>';
    html += '  <div style="margin-top:6px;">' + player + '</div>';
    html += '</div>';
    return html;
  }

  function renderSoundsLibrary(title, sounds, accent, accentTint, icon) {
    var html = '';
    html += '<div class="card" style="margin-bottom:14px;border:1px solid ' + accent + '33;">';
    html += '  <div class="card-header" style="background:' + accentTint + ';"><h3 style="margin:0;font-size:14px;color:' + accent + ';"><i class="fas ' + icon + '"></i> ' + esc(title) + '</h3></div>';
    html += '  <div style="padding:10px 14px;display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:10px;">';
    sounds.forEach(function (s) { html += renderSoundCard(s); });
    html += '  </div>';
    html += '</div>';
    return html;
  }

  function renderSystem() {
    var content = document.getElementById('pe-content');
    var group = PE_DATA[currentAgeGroup];
    if (!group || !group[currentSystem]) {
      content.innerHTML = '<p style="text-align:center;color:#9ca3af;padding:40px;">No data for this combination.</p>';
      return;
    }
    var section = group[currentSystem];
    var accentMap    = { msk: '#0891b2', neuro: '#7c3aed', resp: '#0ea5e9', cv: '#e11d48' }; // cyan / purple / sky / rose
    var accentTintMap = { msk: '#ecfeff', neuro: '#f5f3ff', resp: '#f0f9ff', cv: '#fff1f2' };
    var iconMap       = { msk: 'bone',    neuro: 'brain',   resp: 'lungs',   cv: 'heart-pulse' };
    var labelMap      = { msk: 'Musculoskeletal', neuro: 'Neurologic', resp: 'Respiratory', cv: 'Cardiovascular' };
    var accent     = accentMap[currentSystem]    || '#0891b2';
    var accentTint = accentTintMap[currentSystem] || '#ecfeff';
    var icon       = iconMap[currentSystem]       || 'circle-info';
    var sysLabel   = labelMap[currentSystem]      || currentSystem;
    var html = '';

    // ─ Overview banner ─
    html += '<div class="card" style="margin-bottom:14px;border-left:4px solid ' + accent + ';">';
    html += '  <div class="card-header" style="border:0;padding:14px 16px 6px;"><h3 style="margin:0;font-size:16px;color:' + accent + ';"><i class="fas fa-' + icon + '"></i> ' + esc(group.label) + ' — ' + sysLabel + '</h3></div>';
    html += '  <div style="padding:0 16px 14px;font-size:13px;line-height:1.65;color:var(--g700);">' + esc(section.overview) + '</div>';
    html += '</div>';

    // ─ APTM cardiac auscultation diagram + innocent-murmur map (cv only) ─
    if (currentSystem === 'cv') {
      html += '<div class="card" style="margin-bottom:14px;border:1px solid ' + accent + '33;">';
      html += '  <div class="card-header" style="background:' + accentTint + ';"><h3 style="margin:0;font-size:14px;color:' + accent + ';"><i class="fas fa-stethoscope"></i> Auscultation landmarks — APTM + Erb\'s</h3></div>';
      // Image ALWAYS on its own row (full width on every device, responsive
      // max-width caps it so it isn't huge on desktop). Tap/click to open
      // full-size in a new tab — native browser zoom works there. Legend
      // below stacks on narrow screens via auto-fit grid with minmax.
      html += '  <div style="padding:14px 16px 6px;">';
      html += '    <a href="/images/pe-guide/aptm.png" target="_blank" rel="noopener" aria-label="Open APTM diagram full size in a new tab" style="display:block;text-align:center;cursor:zoom-in;">';
      html += '      <img src="/images/pe-guide/aptm.png" alt="APTM cardiac auscultation points diagram" style="max-width:100%;width:100%;max-width:420px;height:auto;display:inline-block;border-radius:8px;border:1px solid var(--g200);"/>';
      html += '      <div style="font-size:11px;color:var(--g500);margin-top:6px;"><i class="fas fa-up-right-from-square" style="margin-right:4px;"></i>Tap / click to open full-size in a new tab (pinch to zoom on mobile)</div>';
      html += '    </a>';
      html += '  </div>';
      html += '  <div style="padding:4px 16px 14px;">';
      html += '    <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,260px),1fr));gap:10px 16px;font-size:12px;line-height:1.55;">';
      APTM_LEGEND.forEach(function (p) {
        html += '<div style="display:flex;gap:10px;align-items:flex-start;">';
        html += '  <div style="flex-shrink:0;width:24px;height:24px;border-radius:50%;background:' + p.color + ';color:#fff;font-weight:700;display:flex;align-items:center;justify-content:center;font-size:12px;">' + p.letter + '</div>';
        html += '  <div style="flex:1;min-width:0;">';
        html += '    <div style="font-weight:600;color:var(--g800);font-size:13px;">' + esc(p.title) + '</div>';
        html += '    <div style="color:var(--g600);font-size:11px;margin-top:1px;">' + esc(p.location) + '</div>';
        html += '    <div style="color:var(--g700);margin-top:3px;font-size:12px;"><strong>Listen for:</strong> ' + esc(p.listen) + '</div>';
        if (p.innocent) {
          html += '    <div style="color:#047857;margin-top:2px;font-size:11px;"><i class="fas fa-leaf" style="margin-right:3px;"></i><em>Innocent:</em> ' + esc(p.innocent) + '</div>';
        }
        html += '  </div>';
        html += '</div>';
      });
      html += '    </div>';
      html += '  </div>';

      // ─ Cardiac sounds library ─
      html += '  <div style="border-top:1px solid var(--g200);padding:14px;">';
      html += '    <div style="font-weight:600;font-size:13px;color:' + accent + ';margin-bottom:10px;"><i class="fas fa-volume-high"></i> Cardiac sounds library</div>';
      html += '    <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:10px;">';
      CARDIAC_SOUNDS.forEach(function (s) {
        html += renderSoundCard(s);
      });
      html += '    </div>';
      html += '  </div>';

      // ─ Innocent murmur reference panel ─
      html += '  <div style="border-top:1px solid var(--g200);padding:14px;background:#f0fdf4;">';
      html += '    <div style="font-weight:600;font-size:13px;color:#047857;margin-bottom:8px;"><i class="fas fa-leaf"></i> Classic innocent murmurs (benign, no workup needed)</div>';
      html += '    <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:10px;">';
      INNOCENT_MURMURS.forEach(function (m) {
        html += '<div style="background:#fff;border:1px solid var(--g200);border-radius:8px;padding:10px 12px;">';
        html += '  <div style="font-weight:600;color:var(--g800);font-size:13px;margin-bottom:3px;">' + esc(m.name) + '</div>';
        html += '  <div style="font-size:11px;color:var(--g500);margin-bottom:4px;">Age: ' + esc(m.age) + ' · Location: ' + esc(m.location) + '</div>';
        html += '  <div style="font-size:12px;color:var(--g700);line-height:1.55;"><strong>Sound:</strong> ' + esc(m.character) + '</div>';
        html += '  <div style="font-size:12px;color:var(--g700);line-height:1.55;margin-top:3px;"><strong>Confirm innocent:</strong> ' + esc(m.confirm) + '</div>';
        html += '</div>';
      });
      html += '    </div>';
      html += '    <div style="margin-top:10px;font-size:11px;color:var(--g600);line-height:1.55;"><strong>The 7 "S" innocent-murmur criteria:</strong> Soft (≤ 2/6) · Systolic · Short · Single (no S3/S4) · Small (non-radiating) · Sweet (musical) · Sensitive to position/respiration. Any murmur breaking this pattern — diastolic, ≥ grade 3, radiating, continuous, accompanied by symptoms — deserves pediatric cardiology referral.</div>';
      html += '  </div>';
      html += '</div>';
    }

    // ─ Respiratory sounds library (only for resp system) ─
    if (currentSystem === 'resp') {
      html += renderSoundsLibrary('Respiratory sounds library', RESP_SOUNDS, accent, accentTint, 'fa-lungs');
    }

    // ─ Grading scales reference (collapsible) ─
    var scaleKeys = SYSTEM_SCALES[currentSystem] || [];
    if (scaleKeys.length) {
      html += '<details class="card" style="margin-bottom:14px;background:' + accentTint + ';border:1px solid ' + accent + '33;">';
      html += '  <summary style="padding:10px 16px;cursor:pointer;font-weight:600;font-size:13px;color:' + accent + ';list-style:none;display:flex;align-items:center;gap:8px;"><i class="fas fa-book-medical"></i> Grading scales &amp; reference <span style="font-weight:400;color:var(--g500);font-size:11px;">(click to expand)</span></summary>';
      html += '  <div style="padding:0 16px 14px;display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:14px;">';
      scaleKeys.forEach(function (sk) {
        var sc = SCALES[sk]; if (!sc) return;
        html += '<div style="background:#fff;border:1px solid var(--g200);border-radius:8px;padding:10px 12px;">';
        html += '  <div style="font-weight:600;font-size:12px;color:var(--g800);margin-bottom:6px;"><i class="fas ' + sc.icon + '" style="color:' + accent + ';margin-right:6px;"></i>' + esc(sc.title) + '</div>';
        html += '  <table style="width:100%;font-size:11px;line-height:1.5;border-collapse:collapse;">';
        sc.rows.forEach(function (row) {
          html += '<tr><td style="padding:2px 0;white-space:nowrap;color:' + accent + ';font-weight:600;font-family:ui-monospace,monospace;">' + esc(row[0]) + '</td><td style="padding:2px 0 2px 10px;color:var(--g700);">' + esc(row[1]) + '</td></tr>';
        });
        html += '  </table>';
        html += '</div>';
      });
      html += '  </div>';
      html += '</details>';
    }

    // ─ Components ─
    section.components.forEach(function (c, ci) {
      html += '<div class="card" style="margin-bottom:12px;border-left:3px solid ' + accent + ';">';

      // Header
      html += '  <div style="padding:12px 16px 8px;display:flex;align-items:center;justify-content:space-between;gap:10px;">';
      html += '    <h3 style="margin:0;font-size:15px;color:var(--g800);">' + esc(c.name) + '</h3>';
      html += '    <span style="font-size:10px;font-weight:600;padding:3px 8px;border-radius:10px;background:' + accent + '22;color:' + accent + ';text-transform:uppercase;letter-spacing:0.5px;white-space:nowrap;">' + c.steps.length + ' steps</span>';
      html += '  </div>';

      // Significance line
      if (c.significance) {
        html += '  <div style="padding:0 16px 6px;font-size:12px;color:var(--g600);line-height:1.55;"><i class="fas fa-bullseye" style="color:' + accent + ';margin-right:6px;font-size:11px;"></i>' + esc(c.significance) + '</div>';
      }

      // Teaching pearl
      if (c.pearl) {
        html += '  <div style="margin:6px 16px 10px;padding:10px 12px;background:#fef7e7;border-left:3px solid #f59e0b;border-radius:0 6px 6px 0;font-size:12px;line-height:1.6;color:#78350f;"><i class="fas fa-lightbulb" style="color:#f59e0b;margin-right:6px;"></i><em>' + esc(c.pearl) + '</em></div>';
      }

      // Steps
      html += '  <div style="padding:4px 12px 10px;">';
      c.steps.forEach(function (s, si) {
        var key = currentSystem + '-' + ci + '-' + si;
        if (!state[key]) state[key] = { component: c.name, label: s.label, method: s.method, normal: s.normal, status: null, note: '' };
        var st = state[key].status;
        var rowBg = st === 'normal' ? '#ecfdf5' : st === 'abnormal' ? '#fef2f2' : 'transparent';
        var rowBorder = st === 'normal' ? '#a7f3d0' : st === 'abnormal' ? '#fecaca' : 'var(--g200)';
        html += '<div class="pe-step" data-pe-key="' + key + '" style="padding:10px 12px;border:1px solid ' + rowBorder + ';border-radius:8px;margin-bottom:6px;background:' + rowBg + ';transition:background 0.12s,border-color 0.12s;">';
        html += '  <div style="display:flex;align-items:flex-start;gap:10px;">';
        html += '    <div style="flex-shrink:0;width:22px;height:22px;border-radius:50%;background:' + accent + '22;color:' + accent + ';font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;line-height:1;">' + (si + 1) + '</div>';
        html += '    <div style="flex:1;min-width:0;">';
        html += '      <div style="font-weight:600;font-size:13px;color:var(--g800);">' + esc(s.label) + '</div>';
        html += '      <div style="font-size:12px;color:var(--g600);margin-top:3px;line-height:1.55;"><span style="color:var(--g500);font-weight:600;text-transform:uppercase;letter-spacing:0.5px;font-size:10px;">How</span> &nbsp;' + esc(s.method) + '</div>';
        html += '      <div style="font-size:12px;color:var(--g600);margin-top:2px;line-height:1.55;"><span style="color:#059669;font-weight:600;text-transform:uppercase;letter-spacing:0.5px;font-size:10px;">Normal</span> &nbsp;' + esc(s.normal) + '</div>';
        html += '    </div>';
        html += '    <div style="display:flex;gap:3px;flex-shrink:0;">';
        html += '      <button class="visit-status-btn ' + (st === 'normal' ? 'done' : '') + '" data-pe-status="normal" data-pe-key="' + key + '" title="Normal">✓</button>';
        html += '      <button class="visit-status-btn ' + (st === 'abnormal' ? 'refused' : '') + '" data-pe-status="abnormal" data-pe-key="' + key + '" title="Abnormal">✗</button>';
        html += '      <button class="visit-status-btn ' + (st === null ? 'not-due' : '') + '" data-pe-status="skip" data-pe-key="' + key + '" title="Skip">—</button>';
        html += '    </div>';
        html += '  </div>';
        html += '  <div class="pe-abnormal-detail" style="margin-top:8px;' + (st === 'abnormal' ? '' : 'display:none;') + '">';
        html += '    <input type="text" class="pe-note" data-pe-key="' + key + '" placeholder="Describe the abnormal finding (e.g., 4/5 strength, left pronator drift)" value="' + esc(state[key].note || '') + '" style="width:100%;padding:6px 10px;font-size:12px;border:1px solid #fecaca;border-radius:6px;background:#fff;">';
        html += '  </div>';
        html += '</div>';
      });
      html += '  </div>';

      // Watch-for
      if (c.abnormalHints && c.abnormalHints.length) {
        html += '<div style="margin:0 12px 12px;padding:10px 12px;background:#fef2f2;border-left:3px solid #ef4444;border-radius:0 6px 6px 0;font-size:11px;line-height:1.7;color:#7f1d1d;">';
        html += '<div style="font-weight:600;text-transform:uppercase;letter-spacing:0.5px;color:#991b1b;margin-bottom:4px;font-size:10px;"><i class="fas fa-triangle-exclamation"></i> Watch for</div>';
        html += c.abnormalHints.map(esc).join(' &nbsp;·&nbsp; ');
        html += '</div>';
      }

      html += '</div>';
    });

    content.innerHTML = html;

    content.querySelectorAll('[data-pe-status]').forEach(function (btn) {
      btn.addEventListener('click', function () { handleStatus(btn); });
    });
    content.querySelectorAll('.pe-note').forEach(function (inp) {
      inp.addEventListener('input', function () {
        var key = inp.dataset.peKey;
        if (state[key]) state[key].note = inp.value;
      });
    });
    // Single-playback policy: when any <audio> starts, pause every other one.
    content.querySelectorAll('.pe-audio').forEach(function (a) {
      a.addEventListener('play', function () {
        content.querySelectorAll('.pe-audio').forEach(function (other) {
          if (other !== a && !other.paused) other.pause();
        });
      });
    });
  }

  function handleStatus(btn) {
    var key = btn.dataset.peKey;
    var status = btn.dataset.peStatus;
    if (!state[key]) return;
    state[key].status = (status === 'skip') ? null : status;
    var row = btn.closest('.pe-step');
    if (row) {
      var s = state[key].status;
      row.style.background     = s === 'normal' ? '#ecfdf5'  : s === 'abnormal' ? '#fef2f2'  : 'transparent';
      row.style.borderColor    = s === 'normal' ? '#a7f3d0'  : s === 'abnormal' ? '#fecaca'  : 'var(--g200)';
      row.querySelectorAll('[data-pe-status]').forEach(function (b) {
        b.classList.remove('done', 'refused', 'not-due');
        if (b.dataset.peStatus === 'normal'   && state[key].status === 'normal')   b.classList.add('done');
        if (b.dataset.peStatus === 'abnormal' && state[key].status === 'abnormal') b.classList.add('refused');
        if (b.dataset.peStatus === 'skip'     && state[key].status === null)      b.classList.add('not-due');
      });
      var detail = row.querySelector('.pe-abnormal-detail');
      if (detail) detail.style.display = (state[key].status === 'abnormal') ? '' : 'none';
    }
  }

  function setAllNormal() {
    Object.keys(state).forEach(function (k) { if (k.indexOf(currentSystem + '-') === 0) state[k].status = 'normal'; });
    renderSystem();
  }
  function clearAll() {
    Object.keys(state).forEach(function (k) { if (k.indexOf(currentSystem + '-') === 0) { state[k].status = null; state[k].note = ''; } });
    renderSystem();
  }

  function generate() {
    if (!currentAgeGroup || !PE_DATA[currentAgeGroup]) return;
    var steps = Object.keys(state)
      .filter(function (k) { return k.indexOf(currentSystem + '-') === 0; })
      .map(function (k) { return state[k]; });

    var assessed = steps.filter(function (s) { return s.status !== null; });
    if (assessed.length === 0) { showToast('Mark at least one step Normal or Abnormal', 'error'); return; }

    var btn = document.getElementById('pe-generate-btn');
    btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Generating...';

    var modelSel = document.querySelector('#peguide-tab .tab-model-select');
    var model = modelSel ? modelSel.value : '';
    var format = document.getElementById('pe-format').value;

    fetch('/api/generate-pe-narrative', {
      method: 'POST',
      headers: getAuthHeaders(),
      body: JSON.stringify({
        steps:         steps,
        ageGroup:      PE_DATA[currentAgeGroup].label,
        system:        currentSystem,
        patientAge:    document.getElementById('pe-age').value,
        patientGender: document.getElementById('pe-gender').value,
        model:         model,
        format:        format
      })
    })
    .then(function (r) { return r.json(); })
    .then(function (data) {
      if (!data.success) { showToast(data.error || 'Generation failed', 'error'); return; }
      var out = document.getElementById('pe-output');
      out.classList.remove('hidden');
      document.getElementById('pe-narrative-text').textContent = data.narrative;
      var tag = document.getElementById('pe-model-tag');
      if (tag) tag.textContent = data.model || '';
      var bar = document.getElementById('pe-summary-bar');
      if (bar && data.summary) {
        bar.classList.remove('hidden');
        bar.innerHTML = '<span style="color:#10b981;">' + data.summary.normal + ' normal</span> · <span style="color:#ef4444;">' + data.summary.abnormal + ' abnormal</span> · <span style="color:#9ca3af;">' + data.summary.notAssessed + ' skipped</span>';
      }
    })
    .catch(function (err) { console.error('[PEGuide] generate error', err); showToast('Request failed', 'error'); })
    .finally(function () {
      btn.disabled = false; btn.innerHTML = '<i class="fas fa-wand-magic-sparkles"></i> Generate Exam Report';
    });
  }

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