/* Coding view — DOS groups, each revealed after a date is picked.
 * Per CPT row: one CPT box, then Modifier boxes and ICD boxes that open the next as you type/select.
 * Keyboard: CPT → Tab → first Modifier; selecting a modifier opens the next; Tab → first ICD; entering an ICD opens the next. */
(function () {
  var C = window.CODING || {};
  var API = C.api;
  function esc(s){ return (s==null?'':String(s)).replace(/[&<>"]/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];}); }
  function el(html){ var d=document.createElement('div'); d.innerHTML=html.trim(); return d.firstChild; }
  function note(msg,kind){ var n=document.getElementById('lookupNote'); var col={ok:'text-success',warn:'text-warning',muted:'text-secondary'}[kind]||'text-secondary';
    n.className=col; n.innerHTML='<i class="ti ti-info-circle me-1"></i>'+esc(msg); }

  var cptIdx = 0, dosIdx = 0;
  var dosGroups = document.getElementById('dosGroups');
  var TODAY = (function(){ var d=new Date(); return d.getFullYear()+'-'+('0'+(d.getMonth()+1)).slice(-2)+'-'+('0'+d.getDate()).slice(-2); })();

  /* generic autocomplete dropdown */
  function ac(input, fetcher, render, onPick){
    var box=document.createElement('div'); box.className='ac-list';
    var wrap=input.closest('.ac-box')||input.parentNode; wrap.appendChild(box);
    var items=[], active=-1, t=null;
    function close(){ box.style.display='none'; active=-1; }
    function show(list){ items=list||[]; box.innerHTML=items.map(function(it,i){return '<div class="it" data-i="'+i+'">'+render(it)+'</div>';}).join('');
      box.style.display=items.length?'block':'none';
      [].forEach.call(box.querySelectorAll('.it'),function(e){ e.onmousedown=function(ev){ev.preventDefault(); onPick(items[+e.dataset.i]); close();}; }); }
    input.addEventListener('input',function(){ clearTimeout(t); var q=input.value.trim(); if(q.length<1){close();return;} t=setTimeout(function(){ fetcher(q).then(show); },180); });
    input.addEventListener('keydown',function(ev){ if(box.style.display!=='block')return; var its=box.querySelectorAll('.it');
      if(ev.key==='ArrowDown'){ev.preventDefault();active=Math.min(active+1,its.length-1);}
      else if(ev.key==='ArrowUp'){ev.preventDefault();active=Math.max(active-1,0);}
      else if(ev.key==='Enter'){ if(active>=0){ev.preventDefault(); onPick(items[active]); close();} }
      else if(ev.key==='Escape'){close();return;}
      [].forEach.call(its,function(e,i){e.classList.toggle('active',i===active);}); });
    input.addEventListener('blur',function(){ setTimeout(close,150); });
    input._acOpen=function(){ return box.style.display==='block'; };
  }
  function cptFetch(q){ return fetch(API+'?action=cpt_search&facility_id='+C.facility+'&provider_id='+C.provider+'&q='+encodeURIComponent(q)).then(function(r){return r.json();}).then(function(d){return d.items||[];}); }
  function icdFetch(q){ return fetch(API+'?action=icd_search&q='+encodeURIComponent(q)).then(function(r){return r.json();}).then(function(d){return d.items||[];}); }
  function modSelect(val){
    return '<option value="">mod…</option>'+C.modifiers.map(function(m){return '<option '+(m===val?'selected':'')+'>'+esc(m)+'</option>';}).join('');
  }

  /* ---- age (per DOS) = DOS − DOB in completed years ---- */
  function ageAt(dosStr){
    var dobEl=document.getElementById('f_dob');
    var d=/^(\d{4})-(\d{2})-(\d{2})/.exec((dobEl&&dobEl.value)||'');
    var s=/^(\d{4})-(\d{2})-(\d{2})/.exec(dosStr||'');
    if(!d||!s) return '';
    var age=(+s[1])-(+d[1]); if((+s[2])<(+d[2])||((+s[2])===(+d[2])&&(+s[3])<(+d[3]))) age--;
    return (age>=0&&age<=130)?age:'';
  }
  function groupDate(group){ var i=group.querySelector('.dos-date'); return i?i.value:''; }
  function recalcGroupAge(group){ var b=group.querySelector('.dos-age b'); if(b){ var a=ageAt(groupDate(group)); b.textContent=(a===''?'—':a); } }
  function recalcAges(){ [].forEach.call(dosGroups.querySelectorAll('.dos-group'), recalcGroupAge); }
  function syncGroupDos(group){ var d=groupDate(group); [].forEach.call(group.querySelectorAll('.dos-in'), function(h){ h.value=d; }); }
  /* DOS guard: reject a future date; warn (overridable) if older than 45 days. */
  function checkDos(group){
    var di=group.querySelector('.dos-date'), w=group.querySelector('.dos-warn'); if(!di||!w) return;
    var v=di.value; di.classList.remove('is-invalid');
    if(!v){ w.style.display='none'; w.innerHTML=''; return; }
    if(v > TODAY){ di.value=''; syncGroupDos(group); recalcGroupAge(group); di.classList.add('is-invalid');
      w.className='dos-warn err'; w.style.display=''; w.innerHTML='<i class="ti ti-alert-triangle me-1"></i>Date of service can\'t be in the future.'; return; }
    // HARD block: a DOS after the record sign date is impossible (the note is signed AFTER the service). Keep the
    // value visible so the coder can fix it or tag the chart as a discrepancy (RFI / Disregard) — completion is blocked.
    var rsd=(document.getElementById('f_rsd')||{}).value||'';
    if(rsd && v > rsd){ di.classList.add('is-invalid');
      w.className='dos-warn err'; w.style.display=''; w.innerHTML='<i class="ti ti-alert-triangle me-1"></i>DOS is AFTER the record sign date ('+esc(rsd)+') — a service can\'t be dated after the note was signed. Fix the DOS, or tag this chart as a discrepancy (RFI / Disregard).'; return; }
    var days=Math.floor((new Date(TODAY+'T00:00:00') - new Date(v+'T00:00:00'))/86400000);
    if(days>45){ w.className='dos-warn warn'; w.style.display=''; w.innerHTML='<i class="ti ti-alert-circle me-1"></i>This DOS is '+days+' days old (over 45). Proceed if that\'s correct.'; }
    else { w.style.display='none'; w.innerHTML=''; }
  }

  /* ---- manual draft save (button) + entry-time code validation ---- */
  function setSaveNote(html, cls){ var n=document.getElementById('saveNote'); if(n){ n.className='small text-nowrap ms-2 '+(cls||'text-secondary'); n.innerHTML=html||''; } }
  function codeNote(msg, cls){ var n=document.getElementById('codeNote'); if(!n) return;
    if(!msg){ n.style.display='none'; n.innerHTML=''; return; }
    n.style.display=''; n.className='code-note '+(cls||'warn'); n.innerHTML='<i class="ti ti-alert-circle me-1"></i>'+msg; }
  function buildPayload(){
    var fd=new FormData(); fd.append('action','autosave'); fd.append('_csrf',C.csrf);
    var idEl=document.querySelector('#chartForm input[name="id"]'); fd.append('id', idEl?idEl.value:(C.excludeId||''));
    function add(name,id){ var el=document.getElementById(id); if(el!=null) fd.append(name, el.value); }
    add('patient_mrn','f_mrn'); add('patient_name','f_name'); add('patient_dob','f_dob'); add('gender','f_gender');
    add('record_sign_date','f_rsd'); add('scribe_flag','f_scribe');
    add('rendering_provider_id','f_render'); add('billing_provider_id','f_bill'); add('coded_provider_id','codedProvider');
    var cmt=document.querySelector('#chartForm textarea[name="comment"]'); if(cmt) fd.append('comment', cmt.value);
    var k=0;
    [].forEach.call(dosGroups.querySelectorAll('.cpt-line'), function(line){
      var c=line.querySelector('.cpt-in'); if(!c || c.value.trim()==='' || c.dataset.valid!=='1') return;   // only validated codes are saved
      fd.append('cpt['+k+'][code]', c.value.trim());
      var uin=line.querySelector('.units-in'); fd.append('cpt['+k+'][units]', uin?(Math.max(1,Math.round(parseFloat(uin.value)||1))):1);
      var dosIn=line.querySelector('.dos-in'); fd.append('cpt['+k+'][dos]', dosIn?dosIn.value:'');
      [].forEach.call(line.querySelectorAll('.icd-in'), function(i){ if(i.value.trim()!=='' && i.dataset.valid==='1') fd.append('cpt['+k+'][icds][]', i.value.trim()); });
      [].forEach.call(line.querySelectorAll('.mod-in'), function(m){ if(m.value!=='') fd.append('cpt['+k+'][mods][]', m.value); });
      k++;
    });
    // Every DOS group's date — even a group with NO CPT line (the RFI/Disregard case) — so the chart still gets a DOS.
    [].forEach.call(dosGroups.querySelectorAll('.dos-group'), function(g){ var d=groupDate(g); if(d) fd.append('group_dos[]', d); });
    return fd;
  }
  /* keepalive: refresh session + CSRF token so a long-open chart never gets logged out mid-work */
  function ping(){
    return fetch(API+'?action=ping', {credentials:'same-origin', headers:{'X-Requested-With':'XMLHttpRequest'}})
      .then(function(r){ return r.json().catch(function(){return null;}); })
      .then(function(d){ if(d && d.csrf) C.csrf=d.csrf; return d; })
      .catch(function(){ return null; });
  }
  function saveDraft(isRetry){ if(!C.editable) return;
    setSaveNote('<i class="ti ti-loader-2 me-1"></i>Saving…');
    fetch(document.getElementById('chartForm').action, {method:'POST', credentials:'same-origin', headers:{'X-Requested-With':'XMLHttpRequest'}, body:buildPayload()})
      .then(function(r){ return r.text().then(function(t){ var d=null; try{ d=JSON.parse(t); }catch(e){} return {status:r.status, d:d, raw:t}; }); })
      .then(function(res){
        if(res.d && res.d.ok){ setSaveNote('<i class="ti ti-check me-1"></i>Saved '+(res.d.cpts||0)+' CPT line(s)', 'text-success'); return; }
        // A dead/rotated session returns either an HTML login page (raw starts with '<') or a
        // JSON 401/419 with reason auth|idle|security|csrf. Refresh the token once and retry —
        // the coder's typed codes stay in the form the whole time, nothing is lost.
        var sessionDead = (!res.d && (res.raw||'').charAt(0)==='<')
                       || res.status===401 || res.status===419
                       || (res.d && ['csrf','auth','idle','security'].indexOf(res.d.reason)>=0);
        if(res.d && res.d.reason==='locked'){ setSaveNote('<i class="ti ti-x me-1"></i>Chart is read-only now', 'text-danger'); return; }
        if(sessionDead && !isRetry){
          setSaveNote('<i class="ti ti-loader-2 me-1"></i>Reconnecting…');
          ping().then(function(d){
            if(d && d.ok){ saveDraft(true); }
            else setSaveNote('<i class="ti ti-x me-1"></i>Session ended — sign in again in another tab, then click Save. Your codes are still here.', 'text-danger');
          });
          return;
        }
        var msg = sessionDead ? 'Session ended — sign in again, then Save (your codes are kept here)'
                : (res.d && res.d.reason) ? res.d.reason
                : 'Save failed (HTTP '+res.status+')';
        setSaveNote('<i class="ti ti-x me-1"></i>'+esc(msg), 'text-danger');
      })
      .catch(function(){ setSaveNote('<i class="ti ti-x me-1"></i>Network error — try again', 'text-danger'); });
  }

  /* entry-time validation: a typed CPT/ICD must match a real code, else it is REJECTED from the box */
  function validateCode(input, fetcher, matcher, kind){
    var v=(input.value||'').trim();
    if(v==='' || input.dataset.valid==='1') return;
    fetcher(v).then(function(items){
      if((items||[]).some(function(it){ return matcher(it, v.toUpperCase()); })){ input.dataset.valid='1'; input.classList.remove('is-invalid'); codeNote('');
        if(kind==='icd') dedupeIcd(input.closest('.cpt-line'), input);                        // no duplicate ICD on the same CPT
        if(kind==='cpt') dedupeCpt(input.closest('.dos-group'), input);                        // no duplicate CPT on the same date of service
        if(kind==='icd') refreshIcdNumbers(input.closest('.dos-group'));                       // keep line-1 ICD badges in sync post-validation
        return; }
      input.value=''; input.dataset.valid=''; input.classList.remove('is-invalid');          // not in the list → don't accept it
      if(kind==='icd') refreshIcdNumbers(input.closest('.dos-group'));                        // a rejected/cleared ICD must drop its stale badge
      if(kind==='cpt') codeNote('CPT <b>'+esc(v.toUpperCase())+'</b> is not in this provider\'s validation list — pick a valid CPT, or ask your supervisor to add it via Hierarchy.');
      else            codeNote('ICD <b>'+esc(v.toUpperCase())+'</b> is not a valid diagnosis code — pick one from the suggestions.');
    }).catch(function(){});
  }
  /* reject a duplicate ICD within the same CPT line (returns true if it cleared one) */
  function dedupeIcd(line, input){
    if(!line||!input) return false;
    var v=(input.value||'').trim().toUpperCase(); if(!v) return false;
    var dup=[].some.call(line.querySelectorAll('.icd-in'), function(o){ return o!==input && (o.value||'').trim().toUpperCase()===v; });
    if(dup){ input.value=''; input.dataset.valid=''; input.classList.remove('is-invalid');
      codeNote('ICD <b>'+esc(v)+'</b> is already on this CPT — duplicates aren\'t allowed.'); return true; }
    return false;
  }
  /* reject a duplicate CPT within the same DOS group — a coder may type the same code on a new line and click away;
     the same CPT on a DIFFERENT date of service is fine, so this is scoped to one .dos-group (returns true if cleared). */
  function dedupeCpt(group, input){
    if(!group||!input) return false;
    var v=(input.value||'').trim().toUpperCase(); if(!v) return false;
    var dup=[].some.call(group.querySelectorAll('.cpt-in'), function(o){ return o!==input && (o.value||'').trim().toUpperCase()===v; });
    if(dup){ input.value=''; input.dataset.valid=''; input.classList.remove('is-invalid');
      codeNote('CPT <b>'+esc(v)+'</b> is already coded on this date of service — duplicates aren\'t allowed.'); return true; }
    return false;
  }
  function validateCpt(input){ validateCode(input, cptFetch, function(it,V){ return (it.cpt_code||'').toUpperCase()===V; }, 'cpt'); }
  function validateIcd(input){ validateCode(input, icdFetch, function(it,V){ return (it.code_dotted||'').toUpperCase()===V || (it.code||'').toUpperCase()===V; }, 'icd'); }

  /* Coder-level CPT add was removed — CPTs are managed only via Hierarchy (supervisor/manager).
   * Coders pick from the provider's validated set; an unlisted CPT is rejected at entry. */

  /* ---- ICD / modifier chaining within a CPT row ---- */
  function icdInputs(line){ return line.querySelectorAll('.icd-in'); }
  function modInputs(line){ return line.querySelectorAll('.mod-in'); }
  function firstMod(line){ return line.querySelector('.mod-in'); }
  function firstIcd(line){ return line.querySelector('.icd-in'); }

  /* ICD numbering / reference (per date-of-service group): the FIRST CPT line's ICDs are numbered 1..N.
   * On a LATER line, typing just a number (e.g. "3") expands to that numbered ICD from the first line.
   * Anything non-numeric is a normal manual ICD (backward compatible). */
  function firstCptLine(group){ return group ? group.querySelector('.cpt-lines .cpt-line') : null; }
  function firstLineIcdCodes(group){
    var fl=firstCptLine(group); if(!fl) return [];
    return [].filter.call(fl.querySelectorAll('.icd-in'), function(i){ return (i.value||'').trim()!==''; })
             .map(function(i){ return (i.value||'').trim(); });
  }
  // If this input is on a NON-first line and holds a bare number that indexes line 1's ICDs, expand it in place.
  function resolveIcdRef(line, input){
    var group=line.closest('.dos-group'); if(!group) return;
    var fl=firstCptLine(group); if(!fl || fl===line) return;
    var v=(input.value||'').trim(); if(!/^[0-9]{1,2}$/.test(v)) return;
    var codes=firstLineIcdCodes(group), idx=parseInt(v,10)-1;
    if(idx>=0 && idx<codes.length){ input.value=codes[idx]; input.dataset.valid=''; }   // validateIcd (called next) re-confirms it
  }
  // Show a small 1..N badge on each filled ICD of the group's FIRST line so the coder knows which number to reuse.
  function refreshIcdNumbers(group){
    if(!group) return; var fl=firstCptLine(group); if(!fl) return; var n=0;
    [].forEach.call(fl.querySelectorAll('.icd-in'), function(i){
      var box=i.closest('.ac-box'); if(!box) return; var badge=box.querySelector('.icd-num');
      if((i.value||'').trim()!==''){ n++;
        if(!badge){ badge=document.createElement('span'); badge.className='icd-num';
          badge.style.cssText='position:absolute;top:-7px;left:-7px;min-width:15px;height:15px;padding:0 3px;background:#4f46e5;color:#fff;font-size:9px;line-height:15px;text-align:center;border-radius:8px;z-index:3;pointer-events:none;';
          box.style.position='relative'; box.appendChild(badge); }
        badge.textContent=n;
      } else if(badge){ badge.remove(); }
    });
  }
  function icdCell(line, val){
    var cell=el('<div class="ac-box cg-icd"><input class="form-control form-control-sm icd-in code-input" name="cpt['+line.dataset.k+'][icds][]" placeholder="ICD" autocomplete="off" value="'+esc(val||'')+'"></div>');
    var input=cell.querySelector('.icd-in');
    if(val) input.dataset.valid='1';                                                                       // resumed codes are already validated
    line.querySelector('.cg-icds').appendChild(cell);
    ac(input, icdFetch, function(it){return '<code>'+esc(it.code_dotted)+'</code> '+esc(it.description||'');},
       function(it){ input.value=it.code_dotted; input.dataset.valid='1'; input.classList.remove('is-invalid'); codeNote(''); if(dedupeIcd(line, input)) return; refreshIcdNumbers(line.closest('.dos-group')); openNextIcd(line, input).focus(); });
    input.addEventListener('input', function(){ input.dataset.valid=''; input.classList.remove('is-invalid'); if(input.value.trim()!=='') ensureTrailingIcd(line); });   // typing un-confirms until re-picked; opens the next box
    input.addEventListener('focus', function(){ ensureTrailingIcd(line); });                                   // re-entry: keep one empty box ready while you're in the ICD section
    input.addEventListener('blur', function(){ resolveIcdRef(line, input); validateIcd(input); setTimeout(function(){ trimIcds(line); refreshIcdNumbers(line.closest('.dos-group')); }, 0); });   // expand a numeric line-1 ICD ref, reject incomplete codes, wipe unused empty boxes, refresh line-1 numbers
    input.addEventListener('keydown', function(ev){
      if(ev.key==='Enter' && !input._acOpen()){ ev.preventDefault(); if(input.value.trim()!=='') openNextIcd(line, input).focus(); }   // ICDs are last in the row — Tab falls through to the next CPT line
    });
    return input;
  }
  function modCell(line, val){
    var cell=el('<div class="cg-mod"><select class="form-select form-select-sm mod-in" name="cpt['+line.dataset.k+'][mods][]">'+modSelect(val)+'</select></div>');
    var sel=cell.querySelector('.mod-in');
    line.querySelector('.cg-mods').appendChild(cell);
    sel.addEventListener('change', function(){ if(sel.value!==''){
        var v=sel.value, dup=[].some.call(line.querySelectorAll('.mod-in'), function(o){ return o!==sel && o.value===v; });
        if(dup){ sel.value=''; codeNote('Modifier <b>'+esc(v)+'</b> is already on this CPT — duplicates aren\'t allowed.'); return; }
        codeNote(''); openNextMod(line, sel).focus();
    } });   // selecting opens the next box (no duplicate modifier)
    sel.addEventListener('keydown', function(ev){
      if(ev.key==='Tab' && !ev.shiftKey){ ev.preventDefault(); ensureTrailingIcd(line); firstIcd(line).focus(); }   // Tab → first ICD (modifiers come before ICDs)
    });
    sel.addEventListener('focus', function(){ ensureTrailingMod(line); });                            // re-entry: keep one empty box ready while you're in the modifier section
    sel.addEventListener('blur',  function(){ setTimeout(function(){ trimMods(line); }, 0); });       // leaving the modifier section → wipe the unused empty box(es)
    return sel;
  }
  function ensureTrailingIcd(line){ var a=icdInputs(line), last=a[a.length-1]; return (!last || last.value.trim()!=='') ? icdCell(line,'') : last; }
  function ensureTrailingMod(line){ var a=modInputs(line), last=a[a.length-1]; return (!last || last.value!=='')   ? modCell(line,'') : last; }
  function openNextIcd(line, input){ var a=icdInputs(line), i=[].indexOf.call(a,input); return a[i+1] || icdCell(line,''); }
  function openNextMod(line, sel){   var a=modInputs(line), i=[].indexOf.call(a,sel);   return a[i+1] || modCell(line,''); }

  /* Trailing-empty cleanup: the next mod/ICD box only opens once you SELECT/type the current one (above).
   * When focus LEAVES a row's modifier (or ICD) section, drop the leftover empty box(es) you didn't use —
   * keeping the filled ones, or exactly ONE empty box as the entry point when the section is still empty. */
  function sectionHasFocus(line, wrapSel){ var w=line.querySelector(wrapSel); return !!(w && w.contains(document.activeElement)); }
  function trimTrailing(line, inputsFn, cellSel){
    if(!line) return;
    var a=[].slice.call(inputsFn(line)); if(!a.length) return;
    var lastFilled=-1; for(var j=0;j<a.length;j++) if((a[j].value||'').trim()!=='') lastFilled=j;
    var keep = lastFilled>=0 ? lastFilled : 0;                         // keep through the last filled box, else one entry box
    for(var i=a.length-1; i>keep; i--){ var cell=a[i].closest(cellSel); if(cell) cell.remove(); }
  }
  function trimMods(line){ if(!sectionHasFocus(line,'.cg-mods')) trimTrailing(line, modInputs, '.cg-mod'); }
  function trimIcds(line){ if(!sectionHasFocus(line,'.cg-icds')) trimTrailing(line, icdInputs, '.cg-icd'); }

  /* ---- a CPT row inside a DOS group ---- */
  function ensureTrailingLine(group){
    var lines=group.querySelectorAll('.cpt-line'), last=lines[lines.length-1];
    if(last && (last.querySelector('.cpt-in').value||'').trim()!=='') addCptLine(group, null, false);
  }
  function addCptLine(group, prefill, focusCpt){
    var k=cptIdx++;
    var holder=group.querySelector('.cpt-lines');
    var seq=holder.querySelectorAll('.cpt-line').length+1;
    var line=el('<div class="cpt-line" data-k="'+k+'">'+
      '<span class="cpt-seq">'+seq+'</span>'+
      '<input type="hidden" class="dos-in" name="cpt['+k+'][dos]" value="'+esc(groupDate(group))+'">'+
      '<div class="ac-box cg-cpt"><input class="form-control form-control-sm cpt-in code-input" name="cpt['+k+'][code]" placeholder="CPT code / description" autocomplete="off" value="'+esc(prefill&&prefill.code||'')+'"></div>'+
      '<div class="cg-units"><input type="number" min="1" step="1" class="form-control form-control-sm units-in" name="cpt['+k+'][units]" title="Units" aria-label="Units" value="'+(prefill&&prefill.units?Math.max(1,parseInt(prefill.units,10)||1):1)+'"></div>'+
      '<div class="cg-mods"></div>'+
      '<div class="cg-icds"></div>'+
      '<div class="cg-act">'+
        '<button type="button" tabindex="-1" class="btn btn-xs btn-ghost-danger rmline" title="Remove this CPT"><i class="ti ti-trash"></i></button>'+
      '</div>'+
    '</div>');
    holder.appendChild(line);

    ((prefill&&prefill.icds)||[]).forEach(function(v){ icdCell(line, v); });   // existing ICDs…
    ensureTrailingIcd(line);                                                   // …plus one empty, ready
    ((prefill&&prefill.mods)||[]).forEach(function(v){ modCell(line, v); });   // existing modifiers…
    ensureTrailingMod(line);                                                   // …plus one empty, ready

    var input=line.querySelector('.cpt-in');
    if(prefill&&prefill.code) input.dataset.valid='1';                          // resumed CPT already validated
    ac(input, cptFetch, function(it){return '<code>'+esc(it.cpt_code)+'</code> '+esc(it.description||'');}, function(it){
        input.value=it.cpt_code; input.dataset.valid='1'; input.classList.remove('is-invalid'); codeNote('');
        if(dedupeCpt(group, input)) return;                                     // already coded on this DOS → reject
        ensureTrailingLine(group);
        var fi=firstMod(line); if(fi) fi.focus();                               // CPT picked → first modifier
    });
    input.addEventListener('input', function(){ input.dataset.valid=''; input.classList.remove('is-invalid'); });   // typing un-confirms until re-picked / added-on
    input.addEventListener('blur', function(){ validateCpt(input); });          // reject incomplete CPTs on the way out
    input.addEventListener('change', function(){ if((input.value||'').trim()!=='') ensureTrailingLine(group); });
    // Tab on a BLANK trailing CPT box → jump straight to the chart Status (skip the empty mod/ICD boxes). First make
    // sure every 2nd-or-later CPT on a date already has a diagnosis; if not, send focus there instead of to Status.
    input.addEventListener('keydown', function(ev){
      if(ev.key!=='Tab' || ev.shiftKey || (input._acOpen && input._acOpen())) return;
      if((input.value||'').trim()!=='') return;     // only act when this CPT box is empty
      ev.preventDefault();
      var bad=secondaryCptNeedingIcd();
      if(bad){ var ib=ensureTrailingIcd(bad); codeNote('Add at least one ICD diagnosis on CPT <b>'+esc(cptCodeOf(bad))+'</b> — a second or later CPT on a date of service needs a diagnosis.'); ib.focus(); return; }
      var st=document.getElementById('f_status'); if(st){ codeNote(''); st.focus(); }
    });
    line.querySelector('.rmline').onclick=function(){ var h=line.parentNode; line.remove(); renumber(h); ensureTrailingLine(group); refreshIcdNumbers(group); };   // re-badge line-1 ICDs if the first line was removed
    var uin=line.querySelector('.units-in'); if(uin) uin.addEventListener('blur',function(){ uin.value=Math.max(1,Math.round(parseFloat(uin.value)||1)); });   // whole units, min 1
    if(focusCpt) input.focus();
    return line;
  }
  function renumber(holder){ [].forEach.call(holder.querySelectorAll('.cpt-line .cpt-seq'), function(s,i){ s.textContent=i+1; }); }

  /* A second-or-later CPT on a date of service must carry a diagnosis. Returns the first such .cpt-line that has a
     CPT entered but no filled ICD — used to block Tab-to-status and draft save until a diagnosis is added. */
  function secondaryCptNeedingIcd(){
    var groups=dosGroups.querySelectorAll('.dos-group');
    for(var g=0; g<groups.length; g++){
      var filled=[].filter.call(groups[g].querySelectorAll('.cpt-line'), function(l){ return ((l.querySelector('.cpt-in')||{}).value||'').trim()!==''; });
      for(var i=1; i<filled.length; i++){            // index 0 = the first CPT on this DOS; only the 2nd+ need a diagnosis here
        var hasIcd=[].some.call(filled[i].querySelectorAll('.icd-in'), function(x){ return (x.value||'').trim()!==''; });
        if(!hasIcd) return filled[i];
      }
    }
    return null;
  }
  function cptCodeOf(line){ return ((line&&line.querySelector('.cpt-in')||{}).value||'').trim(); }

  /* ---- a DOS group: pick a date, then its code rows appear ---- */
  function addDosGroup(prefill, focusDate){
    var d=dosIdx++;
    var date=(prefill&&prefill.date)||'';
    var group=el('<div class="dos-group" data-d="'+d+'">'+
      '<div class="dos-head">'+
        '<span class="dos-badge"><i class="ti ti-calendar-event"></i> DOS</span>'+
        '<input type="date" class="form-control form-control-sm dos-date" name="group_dos[]" max="'+TODAY+'" value="'+esc(date)+'">'+
        '<button type="button" tabindex="-1" class="btn btn-xs btn-outline-primary rsd-here" title="Copy the Record sign date into this date of service"><i class="ti ti-copy"></i></button>'+
        '<span class="dos-age">Age <b>—</b></span>'+
        '<button type="button" tabindex="-1" class="btn btn-xs btn-outline-primary add-cpt-here ms-auto"><i class="ti ti-plus me-1"></i>Add CPT</button>'+
        '<button type="button" tabindex="-1" class="btn btn-xs btn-ghost-danger rm-dos" title="Remove this date of service"><i class="ti ti-trash"></i></button>'+
      '</div>'+
      '<div class="dos-warn" style="display:none"></div>'+
      '<div class="dos-empty">Pick a date of service above to start adding codes.</div>'+
      '<div class="cpt-lines"></div>'+
    '</div>');
    dosGroups.appendChild(group);

    function setActive(on){ group.classList.toggle('active', on); }
    ((prefill&&prefill.lines)||[]).forEach(function(l){ addCptLine(group, l, false); });
    var active = date.trim()!=='' || group.querySelector('.cpt-line');
    setActive(active);
    if(active){ if(!group.querySelector('.cpt-line')) addCptLine(group, null, false); else ensureTrailingLine(group); }
    recalcGroupAge(group); checkDos(group);

    var dateIn=group.querySelector('.dos-date');
    function onDate(){ checkDos(group); syncGroupDos(group); recalcGroupAge(group);
      if(dateIn.value.trim()!==''){ setActive(true); if(!group.querySelector('.cpt-line')){ addCptLine(group, null, false).querySelector('.cpt-in').focus(); } } }
    dateIn.addEventListener('change', onDate); dateIn.addEventListener('input', onDate);
    group.querySelector('.add-cpt-here').onclick=function(){ addCptLine(group, null, true); };
    group.querySelector('.rm-dos').onclick=function(){ group.remove(); if(!dosGroups.querySelector('.dos-group')) addDosGroup(null, true); };
    var rsdHere=group.querySelector('.rsd-here');
    if(rsdHere) rsdHere.onclick=function(){ var rsd=document.getElementById('f_rsd'); if(rsd&&rsd.value){ dateIn.value=rsd.value;
      dateIn.dispatchEvent(new Event('change',{bubbles:true})); } };   // change (not a bare onDate call) so the billing-sheet Date-of-service mirror (#dosGroups listener) also fires
    if(focusDate) dateIn.focus();
    return group;
  }

  var HIST_ST={completed:'success',rfi:'orange',disregard:'danger',in_progress:'info',hold:'warning',pending:'secondary'};
  var HIST_GENDER={male:'Male',female:'Female',other:'Other',unknown:'Unknown'};
  /* match the server's fmt_date() (m/d/Y, em-dash when empty) so dates read identically
     before and after "Find patient" rebuilds the Past-coded-DOS panel. */
  function fmtDos(d){ var m=/^(\d{4})-(\d{2})-(\d{2})/.exec(String(d||'')); return m ? (m[2]+'/'+m[3]+'/'+m[1]) : (d ? String(d) : '—'); }
  /* ONE ultra-compact table. Demographic columns (mirror the live demographic container) are printed once per
     prior chart on its first line and left blank on its remaining code lines; codes follow per DOS. Kept
     byte-for-byte in step with the PHP renderer so the panel reads identically after "Find patient". */
  function renderHistory(hist){
    var box=document.getElementById('histBox');
    if(!hist||!hist.length){ box.innerHTML='<div class="text-secondary text-center py-3 small">No prior coded DOS for this patient.</div>'; return; }
    var rows=hist.map(function(h){
      var stBadge='<span class="badge bg-'+(HIST_ST[h.status]||'secondary')+'-lt">'+esc(String(h.status||'').replace(/_/g,' '))+'</span>';
      var aud='';
      if(h.audit_status){ var ac=h.audit_status==='ready_to_deliver'?'green':(h.audit_status==='rfi'?'orange':'danger');
        aud='<span class="badge bg-'+ac+'-lt" title="Audit outcome"><i class="ti ti-checklist"></i> '+esc(String(h.audit_status).replace(/_/g,' '))+(h.accuracy!=null?' · '+h.accuracy+'%':'')+'</span>'; }
      var prov=esc(h.provider||'—')+(h.auditor?' <span class="text-secondary">· aud: '+esc(h.auditor)+'</span>':'');
      var dobTxt=h.patient_dob?(esc(fmtDos(h.patient_dob))+((h.age_at_dos!=null&&h.age_at_dos!=='')?' <span class="text-secondary">('+(parseInt(h.age_at_dos,10)||0)+'y)</span>':'')):'—';
      var scribe=(+h.scribe_flag===1)?('Yes · '+esc(h.rendering_provider||'—')):'No';
      // flatten this chart's lines into rows, grouped by date of service (most-recent DOS first)
      var byDos={}, keys=[];
      (h.lines||[]).forEach(function(l){ var k=l.dos||''; if(!(k in byDos)){ byDos[k]=[]; keys.push(k); } byDos[k].push(l); });
      keys.sort().reverse();
      var flat=[]; keys.forEach(function(k){ byDos[k].forEach(function(l,i){ flat.push({dosKey:k, ln:l, firstInDos:i===0}); }); });
      if(!flat.length) flat.push({dosKey:null, ln:null, firstInDos:true});   // no codes → still show demographics
      return flat.map(function(r,ri){
        var chartFirst=ri===0;
        var dosCell = r.ln===null ? '<span class="text-secondary">— no codes —</span>'
                    : (r.firstInDos ? (r.dosKey?esc(fmtDos(r.dosKey)):'<span class="text-secondary">—</span>') : '');
        return '<tr class="'+(chartFirst?'cw-hist-grp':'')+'">'+
          '<td class="cw-demo" title="'+esc(h.patient_name||'')+'">'+(chartFirst?esc(h.patient_name||'—'):'')+'</td>'+
          '<td class="cw-demo">'+(chartFirst?esc(h.patient_mrn||'—'):'')+'</td>'+
          '<td class="cw-demo text-nowrap">'+(chartFirst?dobTxt:'')+'</td>'+
          '<td class="cw-demo">'+(chartFirst?esc(HIST_GENDER[h.gender]||'—'):'')+'</td>'+
          '<td class="cw-demo text-nowrap">'+(chartFirst?(h.record_sign_date?esc(fmtDos(h.record_sign_date)):'—'):'')+'</td>'+
          '<td class="cw-demo">'+(chartFirst?scribe:'')+'</td>'+
          '<td class="cw-demo" title="'+esc(h.rendering_provider||'')+'">'+(chartFirst?esc(h.rendering_provider||'—'):'')+'</td>'+
          '<td class="cw-demo" title="'+esc(h.billing_provider||'')+'">'+(chartFirst?esc(h.billing_provider||'—'):'')+'</td>'+
          '<td class="cw-demo">'+(chartFirst?prov:'')+'</td>'+
          '<td>'+(chartFirst?stBadge:'')+'</td>'+
          '<td>'+(chartFirst?(aud||'—'):'')+'</td>'+
          '<td class="text-nowrap">'+dosCell+'</td>'+
          '<td class="cw-cpt">'+(r.ln?esc(r.ln.cpt||''):'')+'</td>'+
          '<td class="text-center">'+(r.ln?esc(r.ln.units||1):'')+'</td>'+
          '<td>'+(r.ln?(esc((r.ln.mods||[]).join(', '))||'—'):'')+'</td>'+
          '<td>'+(r.ln?(esc((r.ln.icds||[]).join(', '))||'—'):'')+'</td></tr>';
      }).join('');
    }).join('');
    box.innerHTML='<div class="table-responsive"><table class="table table-sm table-vcenter card-table cw-hist-flat mb-0">'+
      '<thead><tr><th>Patient</th><th>MRN/PRN</th><th>DOB</th><th>Gender</th><th>Rec sign</th><th>Scribe?</th>'+
      '<th>Rendering</th><th>Billing</th><th>Coded prov</th><th>Status</th><th>Audit</th>'+
      '<th>DOS</th><th>CPT</th><th class="text-center">U</th><th>Mods</th><th>ICD code(s)</th></tr></thead>'+
      '<tbody>'+rows+'</tbody></table></div>';
  }

  /* ---- wire up ---- */
  document.getElementById('addDos').onclick=function(){ addDosGroup(null, true); };
  document.getElementById('codedProvider').addEventListener('change',function(){ C.provider=this.value; if(window.mToast)mToast('CPT validation set switched to this provider','success'); });
  /* Scribe? toggle re-points the Rendering picker to scribes — handled in the TomSelect init script on the page. */
  /* Billing provider mirrors Rendering by default (most charts share the provider). Keep mirroring
   * until the coder picks a billing provider by hand, then leave their choice alone. */
  (function(){
    var rend=document.getElementById('f_render'), bill=document.getElementById('f_bill'); if(!rend||!bill) return;
    var scr=document.getElementById('f_scribe');
    function isScribe(){ return scr && scr.value==='1'; }   // Scribe? → Rendering holds a scribe, do NOT mirror it into Billing
    // Write through TomSelect once it has wrapped #f_bill, else the native value (TomSelect reads it on init).
    // The silent flag keeps the visible control in sync WITHOUT firing change (so the mirror never sets billTouched).
    function setBill(v){ if(bill.tomselect) bill.tomselect.setValue(v, true); else bill.value=v; }
    var billTouched = bill.value!=='' && bill.value!==rend.value;     // pre-filled to something other than rendering = treat as set
    bill.addEventListener('change', function(){ billTouched = true; });
    rend.addEventListener('change', function(){ if(isScribe()) return; if(!billTouched || bill.value==='') setBill(rend.value); });
    if(!isScribe() && bill.value==='' && rend.value!=='') setBill(rend.value);   // initial mirror when blank
  })();
  document.getElementById('f_dob').addEventListener('change',recalcAges);
  document.getElementById('f_dob').addEventListener('input',recalcAges);
  /* DOS validity depends on the record sign date → re-check every DOS group when it changes. */
  (function(){ var rsd=document.getElementById('f_rsd'); if(rsd) ['change','input'].forEach(function(t){ rsd.addEventListener(t, function(){ [].forEach.call(dosGroups.querySelectorAll('.dos-group'), checkDos); }); }); })();
  document.getElementById('f_status').addEventListener('change',function(){
    var s=this.value, sec=(s==='rfi'||s==='disregard');
    document.getElementById('secWrap').style.display=sec?'':'none';
    if(sec){ var list=s==='rfi'?C.rfiReasons:C.disReasons; document.getElementById('f_secondary').innerHTML='<option value="">— choose —</option>'+list.map(function(r){return '<option>'+esc(r)+'</option>';}).join(''); }
    document.getElementById('commentLabel').innerHTML=sec?'Comment <span class="text-danger">*</span>':'Comment';
  });
  // Shared patient lookup: populate demographics + render PAST CODED DOS. Used by the "Find patient" button AND
  // auto-triggered when the coder types/pastes an MRN. When auto=true it stays quiet on a miss (no scary notes).
  // $mrnAutoLast is the auto-trigger's change-guard (shared so a server-normalized MRN write doesn't re-fire it).
  var mrnAutoLast = null;
  function doPatientLookup(mrn, name, auto){
    mrn=(mrn||'').trim(); name=(name||'').trim();
    if(!mrn && !name){ if(!auto) note('Enter an MRN/PRN or patient name first.','warn'); return; }
    return fetch(API+'?action=patient_lookup&facility_id='+C.facility+'&mrn='+encodeURIComponent(mrn)+'&name='+encodeURIComponent(name)+'&exclude_id='+C.excludeId,
        {credentials:'same-origin', headers:{'X-Requested-With':'XMLHttpRequest'}})   // XHR header → a dead session returns JSON 401, not an HTML redirect
      .then(function(r){return r.json();}).then(function(d){
        if(d.found && d.demographics){ var dm=d.demographics;
          if(dm.patient_name) document.getElementById('f_name').value=dm.patient_name;     // name · MRN · DOB · gender from the last stored entry
          if(dm.patient_mrn)  document.getElementById('f_mrn').value=dm.patient_mrn;
          if(dm.patient_dob)  document.getElementById('f_dob').value=dm.patient_dob;
          if(dm.gender)       document.getElementById('f_gender').value=dm.gender;
          mrnAutoLast=(document.getElementById('f_mrn').value||'').trim();   // treat the (possibly normalized) written MRN as already-looked-up → no redundant re-fire
          // Rendering & Billing provider are intentionally NOT changed by a patient lookup — they stay defaulted to
          // this chart's UPLOADED provider (the one shown beside the Queue button, i.e. the provider the inventory was
          // uploaded against). The coder can still change them by hand.
          recalcAges(); renderHistory(d.history);
          note('Existing patient found — demographics populated (rendering / billing provider kept as uploaded). Edit any field as needed.','ok'); }
        else if(d.found){ renderHistory(d.history); note('Prior charts found, but no stored demographics to copy.','muted'); }
        else if(!auto) note('No prior record for this patient.','muted'); })
      .catch(function(){ if(!auto) note('Lookup failed — please try again.','warn'); });
  }
  document.getElementById('btnFind').onclick=function(){
    doPatientLookup(document.getElementById('f_mrn').value, document.getElementById('f_name').value, false);
  };
  // Auto-populate demographics + PAST CODED DOS when the coder TYPES or PASTES an MRN (blur, paste, or debounced typing).
  (function(){
    var mrnEl=document.getElementById('f_mrn'); if(!mrnEl) return; var t=null;
    mrnAutoLast=(mrnEl.value||'').trim();   // seed with the loaded MRN so an UNCHANGED pre-filled MRN never auto-overwrites the coder's edited demographics on first blur
    function go(){ var v=(mrnEl.value||'').trim(); if(v.length<2 || v===mrnAutoLast) return; mrnAutoLast=v; doPatientLookup(v, '', true); }
    mrnEl.addEventListener('blur', go);
    mrnEl.addEventListener('paste', function(){ setTimeout(go, 60); });      // let the pasted text land first
    mrnEl.addEventListener('input', function(){ clearTimeout(t); t=setTimeout(go, 700); });
  })();

  /* require full demographics before a Completed submit — caught client-side so the coder never loses work */
  document.getElementById('chartForm').addEventListener('submit', function(ev){
    if(ev.submitter && ev.submitter.name==='draft'){               // "Save codes" draft: persist as-is, but a 2nd+ CPT on a date still needs a diagnosis
      var badD=secondaryCptNeedingIcd();
      if(badD){ ev.preventDefault(); var ib=ensureTrailingIcd(badD); ib.classList.add('is-invalid');
        codeNote('Add at least one ICD on CPT <b>'+esc(cptCodeOf(badD))+'</b> before saving — a second or later CPT on a date of service needs a diagnosis.'); ib.focus(); }
      return;
    }
    if(!C.editable) return;
    var st=(document.getElementById('f_status')||{}).value;
    if(st==='rfi' || st==='disregard'){
      // RFI / Disregard: patient demographics + a date of service are mandatory (no CPT codes required).
      var need2=[['f_mrn','Patient MRN/PRN'],['f_name','Patient name'],['f_dob','DOB'],['f_gender','Gender']], miss2=[], first2=null;
      need2.forEach(function(p){ var el=document.getElementById(p[0]); var bad=!el||(el.value||'').trim()===''; if(el) el.classList.toggle('is-invalid', bad); if(bad){ miss2.push(p[1]); if(!first2) first2=el; } });
      var anyDos=[].some.call(document.querySelectorAll('.dos-date'), function(d){ return (d.value||'').trim()!==''; });
      if(!anyDos){ miss2.push('a date of service (DOS)'); var d0=document.querySelector('.dos-date'); if(d0){ d0.classList.add('is-invalid'); if(!first2) first2=d0; } }
      if(miss2.length){ ev.preventDefault(); note('Required to tag RFI / Disregard: '+miss2.join('; ')+'. Enter the patient demographics and add a date of service above (no codes needed).','warn'); if(first2) first2.focus(); window.scrollTo({top:0,behavior:'smooth'}); }
      return;
    }
    if(st!=='completed') return;
    var scribing=(document.getElementById('f_scribe')||{}).value==='1';
    var need=[['f_mrn','Patient MRN/PRN'],['f_name','Patient name'],['f_dob','DOB'],['f_gender','Gender'],['f_rsd','Record sign date'],['f_render', scribing?'Scribe':'Rendering provider'],['f_bill','Billing provider']];
    var missing=[], first=null;
    need.forEach(function(p){ var el=document.getElementById(p[0]); var bad=!el||(el.value||'').trim()===''; if(el) el.classList.toggle('is-invalid', bad); if(bad){ missing.push(p[1]); if(!first) first=el; } });
    // Completed needs a real coded chart: ≥1 date of service (with a date) → ≥1 CPT in it → ≥1 ICD on every CPT row.
    var codedLines=[].filter.call(document.querySelectorAll('.cpt-line'), function(l){ return ((l.querySelector('.cpt-in')||{}).value||'').trim()!==''; });
    if(!codedLines.length){ missing.push('at least one CPT code on a date of service'); }
    else {
      var dosMissing=false;
      codedLines.forEach(function(l){
        var grp=l.closest('.dos-group'), di=grp&&grp.querySelector('.dos-date');
        if(!di || di.value.trim()===''){ dosMissing=true; if(di){ di.classList.add('is-invalid'); if(!first) first=di; } }
        if(![].some.call(l.querySelectorAll('.icd-in'), function(i){ return (i.value||'').trim()!==''; })){          // this CPT row has no ICD
          var ib=l.querySelector('.icd-in'); if(ib){ ib.classList.add('is-invalid'); if(!first) first=ib; }
          var code=((l.querySelector('.cpt-in')||{}).value||'').trim(); missing.push('at least one ICD on CPT '+(code||'a row'));
        }
      });
      if(dosMissing) missing.push('a date of service for every CPT entered');
    }
    // no incomplete codes: every typed CPT/ICD must have been confirmed from the suggestion list
    var unconfirmed=[];
    [].forEach.call(document.querySelectorAll('.cpt-in,.icd-in'),function(inp){ var v=(inp.value||'').trim();
      if(v!=='' && inp.dataset.valid!=='1'){ inp.classList.add('is-invalid'); unconfirmed.push(v); if(!first) first=inp; } });
    if(unconfirmed.length) missing.push('pick "'+unconfirmed.join('", "')+'" from the suggestions (incomplete code)');
    // HARD block: no DOS may be later than the record sign date (fix the DOS or move to discrepancy).
    var rsdv=(document.getElementById('f_rsd')||{}).value||'';
    if(rsdv){ var futDos=[]; [].forEach.call(document.querySelectorAll('.dos-date'), function(d){ var dv=(d.value||'').trim(); if(dv!=='' && dv>rsdv){ d.classList.add('is-invalid'); futDos.push(dv); if(!first) first=d; } });
      if(futDos.length) missing.push('DOS '+futDos.join(', ')+' is after the record sign date ('+rsdv+') — fix the DOS, or tag the chart as a discrepancy (RFI / Disregard)'); }
    if(missing.length){ ev.preventDefault(); note('Required to complete: '+missing.join('; ')+'.','warn'); if(first) first.focus(); window.scrollTo({top:0,behavior:'smooth'}); }
  });
  ['f_mrn','f_name','f_dob','f_gender','f_rsd','f_render','f_bill'].forEach(function(idv){
    var el=document.getElementById(idv); if(el) ['input','change'].forEach(function(t){ el.addEventListener(t,function(){ el.classList.remove('is-invalid'); }); });
  });
  /* "Save codes" is now a real full-page submit (name=draft) — reliable on the shared host even when
   * background fetch/XHR drops the session. No JS wiring needed; the server persists + reloads the chart. */
  /* Comment box auto-grows to fit its content (so a long RFI/discrepancy note is readable all together).
   * Modern browsers size it natively via CSS field-sizing:content (set on the textarea); this JS is the fallback
   * for browsers without it. */
  (function(){ var ta=document.querySelector('#chartForm textarea[name="comment"]'); if(!ta) return;
    if(window.CSS && CSS.supports && CSS.supports('field-sizing','content')) return;   // native auto-size — nothing to do
    ta.style.overflowY='hidden'; ta.style.resize='vertical';
    function grow(){ ta.style.height='auto'; ta.style.height=Math.min(ta.scrollHeight+2, 400)+'px'; if(ta.scrollHeight>400) ta.style.overflowY='auto'; }
    ta.addEventListener('input', grow); window.addEventListener('resize', grow); setTimeout(grow, 0); })();

  /* keepalive: ping every 4 min so a long-open chart keeps the session warm (best-effort). */
  if(C.editable){ setInterval(ping, 4*60*1000); }

  /* ---- initial render: rebuild DOS groups from existing lines, else one empty group awaiting a date ---- */
  if(C.existing && C.existing.length){
    var order=[], byDate={};
    C.existing.forEach(function(l){ var dk=l.dos||''; if(!(dk in byDate)){ byDate[dk]={date:l.dos||'', lines:[]}; order.push(byDate[dk]); } byDate[dk].lines.push(l); });
    order.forEach(function(g){ addDosGroup(g, false); });
  } else {
    addDosGroup(null, false);
  }
  recalcAges();
  [].forEach.call(dosGroups.querySelectorAll('.dos-group'), function(g){ refreshIcdNumbers(g); });   // number line-1 ICDs on load
  if(!C.editable){ [].forEach.call(document.querySelectorAll('#chartForm input,#chartForm select,#chartForm textarea,#chartForm button'),function(e){ e.disabled=true; }); }
  else {   // blank/manual chart (empty patient name) → land on Patient name; otherwise Gender (demographics pre-filled)
    var nameEl=document.getElementById('f_name'), gsel=document.getElementById('f_gender');
    if(nameEl && !nameEl.value.trim()) nameEl.focus();
    else if(gsel) gsel.focus();
  }
})();
