document.querySelectorAll('[data-menu]').forEach(button=>button.addEventListener('click',()=>document.body.classList.toggle('menu-open')));
document.querySelectorAll('[data-menu-close]').forEach(el=>el.addEventListener('click',()=>document.body.classList.remove('menu-open')));
const collapseButton=document.querySelector('[data-sidebar-collapse]');
const savedSidebar=localStorage.getItem('dentalcare-sidebar');
if(savedSidebar==='collapsed')document.body.classList.add('sidebar-collapsed');
collapseButton?.addEventListener('click',()=>{document.body.classList.toggle('sidebar-collapsed');localStorage.setItem('dentalcare-sidebar',document.body.classList.contains('sidebar-collapsed')?'collapsed':'expanded');});
window.addEventListener('resize',()=>{if(window.innerWidth>760)document.body.classList.remove('menu-open');});

// DentalCare v0.6.3: global navigation and scroll governance.
// Policy: sidebar/main navigation opens a new page at the page focus; internal
// page controls, pagination, filters, anchors, dialogs, and validation keep the
// operator in the current workspace.
const dentalScrollStateKey = 'dentalcare:restore-scroll:v4';
try { if ('scrollRestoration' in history) history.scrollRestoration = 'manual'; } catch (_) {}
const dentalScrollCurrentUrl = () => `${location.pathname}${location.search}`;
const dentalScrollSamePath = url => {
  try { return new URL(url, location.href).pathname === location.pathname; } catch (_) { return false; }
};
const dentalIsApplicationNavLink = link => Boolean(link?.closest?.('.sidebar,.primary-nav,.grouped-nav,[data-sidebar-nav],[data-main-nav]'));
const dentalNormalizeScrollTarget = value => {
  const raw = String(value || '').trim();
  if (!raw) return '';
  return raw.startsWith('#') ? raw : `#${raw.replace(/^#/, '')}`;
};
const dentalExplicitScrollTarget = element => {
  const host = element?.closest?.('[data-scroll-target]');
  const target = element?.dataset?.scrollTarget || host?.dataset?.scrollTarget || '';
  return dentalNormalizeScrollTarget(target);
};
const dentalContextHashTarget = element => {
  const explicit = dentalExplicitScrollTarget(element);
  if (explicit) return explicit;
  const context = element?.closest?.('[data-scroll-context][id], .server-pagination[id], .table-wrap[id], .card[id]');
  return context?.id ? `#${context.id}` : '';
};
const dentalIsInternalWorkspaceLink = link => {
  if (!link || dentalIsApplicationNavLink(link)) return false;
  return Boolean(link.closest('main,.main,.content,.module-toolbar,.toolbar,.tabs,.server-pagination,.card,dialog,[data-preserve-scroll-scope],[data-scroll-context]'));
};
const dentalSafeCss = value => {
  try { return CSS && CSS.escape ? CSS.escape(value) : String(value).replace(/[^a-zA-Z0-9_-]/g, '\\$&'); } catch (_) { return String(value).replace(/[^a-zA-Z0-9_-]/g, '\\$&'); }
};
const dentalFindHashTarget = hash => {
  if (!hash || hash === '#') return null;
  let id = String(hash).replace(/^#/, '');
  try { id = decodeURIComponent(id); } catch (_) {}
  return document.getElementById(id) || document.querySelector(`[name="${dentalSafeCss(id)}"]`) || null;
};
const dentalScrollableAncestor = element => {
  let node = element?.parentElement || null;
  while (node && node !== document.body && node !== document.documentElement) {
    const style = window.getComputedStyle(node);
    const y = style.overflowY;
    if ((y === 'auto' || y === 'scroll' || y === 'overlay') && node.scrollHeight > node.clientHeight + 8) return node;
    node = node.parentElement;
  }
  return document.scrollingElement || document.documentElement;
};
const dentalElementVisibleInContainer = (element, container) => {
  if (!element) return false;
  const rect = element.getBoundingClientRect();
  if (!container || container === document.scrollingElement || container === document.documentElement || container === document.body) {
    const topPad = Math.min(128, Math.max(72, Math.round(window.innerHeight * 0.12)));
    return rect.top >= topPad && rect.bottom <= window.innerHeight - 18;
  }
  const host = container.getBoundingClientRect();
  return rect.top >= host.top + 12 && rect.bottom <= host.bottom - 12;
};
const dentalScrollTargetIntoView = (target, options = {}) => {
  if (!target) return false;
  const container = dentalScrollableAncestor(target);
  const force = Boolean(options.force);
  if (!force && dentalElementVisibleInContainer(target, container)) return true;
  if (container && container !== document.scrollingElement && container !== document.documentElement && container !== document.body) {
    const host = container.getBoundingClientRect();
    const rect = target.getBoundingClientRect();
    const nextTop = Math.max(0, container.scrollTop + (rect.top - host.top) - 18);
    try { container.scrollTo({ top: nextTop, behavior: options.behavior || 'smooth' }); } catch (_) { container.scrollTop = nextTop; }
  } else {
    const topPad = Number(options.offset || 92);
    const nextTop = Math.max(0, window.scrollY + target.getBoundingClientRect().top - topPad);
    try { window.scrollTo({ top: nextTop, left: 0, behavior: options.behavior || 'smooth' }); } catch (_) { window.scrollTo(0, nextTop); }
  }
  try { target.setAttribute('tabindex', target.getAttribute('tabindex') || '-1'); target.focus({ preventScroll: true }); } catch (_) {}
  return true;
};
const dentalStoreScrollForNextLoad = (reason = 'workflow', targetHash = '') => {
  try {
    sessionStorage.setItem(dentalScrollStateKey, JSON.stringify({
      path: location.pathname,
      url: dentalScrollCurrentUrl(),
      x: window.scrollX || 0,
      y: window.scrollY || 0,
      hash: location.hash || '',
      targetHash: targetHash || '',
      reason,
      at: Date.now()
    }));
  } catch (_) {}
};
const dentalRestoreScrollAfterLoad = () => {
  let raw = null;
  try { raw = sessionStorage.getItem(dentalScrollStateKey); } catch (_) { return; }
  if (!raw) {
    const target = dentalFindHashTarget(location.hash || '');
    if (target) requestAnimationFrame(() => dentalScrollTargetIntoView(target, { force: true, behavior: 'auto' }));
    return;
  }
  let state = null;
  try { state = JSON.parse(raw); } catch (_) { try { sessionStorage.removeItem(dentalScrollStateKey); } catch (__) {} return; }
  const fresh = state && (Date.now() - Number(state.at || 0)) < 10 * 60 * 1000;
  if (!fresh || state.path !== location.pathname) {
    try { sessionStorage.removeItem(dentalScrollStateKey); } catch (_) {}
    const crossPageTarget = dentalFindHashTarget(location.hash || '');
    if (crossPageTarget) requestAnimationFrame(() => dentalScrollTargetIntoView(crossPageTarget, { force: true, behavior: 'auto' }));
    return;
  }
  try { sessionStorage.removeItem(dentalScrollStateKey); } catch (_) {}
  const targetHash = String(state.targetHash || location.hash || '');
  const target = dentalFindHashTarget(targetHash);
  if (target && targetHash) {
    requestAnimationFrame(() => { dentalScrollTargetIntoView(target, { force: true, behavior: 'auto' }); window.setTimeout(() => dentalScrollTargetIntoView(target, { force: true, behavior: 'auto' }), 120); });
    return;
  }
  const y = Math.max(0, Number(state.y || 0));
  const x = Math.max(0, Number(state.x || 0));
  const restore = () => window.scrollTo({ top: y, left: x, behavior: 'auto' });
  requestAnimationFrame(() => { restore(); window.setTimeout(restore, 80); window.setTimeout(restore, 220); });
};
const dentalHandleHashNavigation = (hash, push = true) => {
  if (!hash || hash === '#') return true;
  const target = dentalFindHashTarget(hash);
  if (!target) return false;
  if (push) {
    try { history.pushState(null, '', `${location.pathname}${location.search}${hash}`); } catch (_) {}
  }
  dentalScrollTargetIntoView(target, { force: false, behavior: 'smooth' });
  return true;
};
window.dentalStoreScrollForNextLoad = dentalStoreScrollForNextLoad;
window.dentalScrollTargetIntoView = dentalScrollTargetIntoView;
window.dentalNavigateToHash = dentalHandleHashNavigation;
// DentalCare v0.6.3.1: safe offline transaction guard.
// A web transaction is valid only when the server receives it. Cached/open pages
// must not pretend that queue, cashier, appointment, inventory, or clinical
// actions were saved while the clinic connection is offline.
const dentalOfflineCriticalPaths = /(?:queue|billing|appointments|appointment_requests|clinical_workspace|doctor_treatment|inventory|sterilization|patients)\.php(?:$|[?#])/i;
const dentalOfflineBanner = (() => {
  let banner = null;
  const ensure = () => {
    if (banner) return banner;
    banner = document.createElement('div');
    banner.className = 'offline-transaction-banner';
    banner.setAttribute('role', 'status');
    banner.setAttribute('aria-live', 'polite');
    banner.innerHTML = '<strong>Offline mode:</strong> do not process live transactions. Use the paper/offline log, then encode and reconcile when the server is reachable.';
    document.body.prepend(banner);
    return banner;
  };
  const sync = () => {
    const offline = navigator.onLine === false;
    const host = ensure();
    host.hidden = !offline;
    document.documentElement.toggleAttribute('data-dental-offline', offline);
  };
  window.addEventListener('online', sync);
  window.addEventListener('offline', sync);
  document.addEventListener('DOMContentLoaded', sync, { once: true });
  requestAnimationFrame(sync);
  return { sync };
})();
const dentalFormTargetsCriticalTransaction = form => {
  const method = String(form?.method || 'get').toLowerCase();
  if (method !== 'post') return false;
  if (form.matches('[data-allow-offline],[data-offline-safe]')) return false;
  const action = form.getAttribute('action') || location.href;
  try { return dentalOfflineCriticalPaths.test(new URL(action, location.href).pathname); } catch (_) { return dentalOfflineCriticalPaths.test(location.pathname); }
};
document.addEventListener('submit', event => {
  const form = event.target;
  if (!(form instanceof HTMLFormElement)) return;
  if (navigator.onLine !== false || !dentalFormTargetsCriticalTransaction(form)) return;
  event.preventDefault();
  dentalOfflineBanner.sync();
  window.alert('The system appears offline. This transaction was not submitted. Use the clinic offline/paper log now, then encode it when DentalCare is online again.');
}, true);
document.addEventListener('submit', event => {
  const form = event.target;
  if (!(form instanceof HTMLFormElement)) return;
  if (form.matches('[data-no-scroll-restore]')) return;
  const method = String(form.method || 'get').toLowerCase();
  const action = form.getAttribute('action') || location.href;
  if (method === 'post' || dentalScrollSamePath(action) || form.matches('[data-preserve-scroll],[data-auto-submit]')) {
    dentalStoreScrollForNextLoad(`form:${String(form.querySelector('[name="action"]')?.value || method)}`, dentalContextHashTarget(form));
  }
}, true);
document.addEventListener('change', event => {
  const field = event.target;
  const form = field?.form;
  if (!(form instanceof HTMLFormElement)) return;
  if (!field.matches('select,input[type="checkbox"],input[type="radio"]')) return;
  if (form.matches('[data-auto-submit],[data-preserve-scroll]') || field.getAttribute('onchange')?.includes('submit')) {
    dentalStoreScrollForNextLoad(`field-change:${String(field.name || field.type || 'input')}`, dentalContextHashTarget(form || field));
  }
}, true);
document.addEventListener('click', event => {
  const link = event.target.closest?.('a[href]');
  if (!link) return;
  if (link.target || link.hasAttribute('download')) return;
  const href = link.getAttribute('href') || '';
  if (!href || href.startsWith('javascript:')) return;
  if (href === '#' || href === '#!') { event.preventDefault(); return; }
  let url = null;
  try { url = new URL(href, location.href); } catch (_) { return; }
  const samePath = url.pathname === location.pathname;
  const sameQuery = url.search === location.search;
  const hash = url.hash || '';
  if (samePath && sameQuery && hash) {
    event.preventDefault();
    dentalHandleHashNavigation(hash, true);
    return;
  }
  const preserveTarget = hash || dentalContextHashTarget(link);
  if (dentalIsApplicationNavLink(link) && !link.matches('[data-preserve-scroll]')) return;
  if (link.matches('[data-preserve-scroll]') || samePath || link.closest('[data-preserve-scroll-scope]') || (url.origin === location.origin && dentalIsInternalWorkspaceLink(link))) {
    dentalStoreScrollForNextLoad('internal-page-link', preserveTarget);
  }
}, true);
dentalRestoreScrollAfterLoad();

document.addEventListener('click',event=>{document.querySelectorAll('.quick-create[open],.context-more[open]').forEach(details=>{if(!details.contains(event.target))details.removeAttribute('open');});});
document.addEventListener('keydown',event=>{if(event.key==='Escape'){document.body.classList.remove('menu-open');document.querySelectorAll('details[open]').forEach(details=>details.removeAttribute('open'));}if(event.key==='/'&&!/INPUT|TEXTAREA|SELECT/.test(document.activeElement?.tagName||'')){event.preventDefault();document.querySelector('.global-search input')?.focus();}});
const dentalFieldIsVisible=field=>Boolean(field&&!field.disabled&&field.type!=='hidden'&&!field.closest('[hidden]')&&(field.offsetParent!==null||field.closest('dialog[open]')));
const dentalFocusFirstRequired=scope=>{const fields=[...scope.querySelectorAll('[required],[data-required-first]')].filter(dentalFieldIsVisible);const target=fields.find(field=>{if(field.matches('[type=checkbox],[type=radio]'))return !field.checked;return String(field.value||'').trim()==='';})||fields[0];if(target){target.focus({preventScroll:true});if(!scope?.hasAttribute?.('data-dialog-start-top'))dentalScrollTargetIntoView(target,{force:false,behavior:'smooth'});if(typeof target.select==='function'&&target.matches('input:not([type=date]):not([type=time]):not([type=number])'))target.select();}return target;};
const dentalScrollDialogTop=dialog=>{try{dialog.scrollTop=0;dialog.querySelector('form')?.scrollTo?.({top:0,left:0,behavior:'auto'});}catch(_){}};
const dentalRestoreWindowScroll=(x,y)=>{try{window.scrollTo({left:x,top:y,behavior:'auto'});}catch(_){window.scrollTo(x,y);}};
const dentalInitialDialogFocus=dialog=>{const preferred=dialog.querySelector('[data-dialog-initial-focus]');if(preferred&&dentalFieldIsVisible(preferred)){try{preferred.focus({preventScroll:true});}catch(_){preferred.focus();}if(typeof preferred.select==='function'&&preferred.matches('input:not([type=date]):not([type=time]):not([type=number])'))preferred.select();return preferred;}return dentalFocusFirstRequired(dialog);};
const dentalAfterDialogOpen=dialog=>{const keepX=window.scrollX||window.pageXOffset||0;const keepY=window.scrollY||window.pageYOffset||0;document.body.classList.add('dialog-open');requestAnimationFrame(()=>{if(dialog?.hasAttribute?.('data-dialog-start-top'))dentalScrollDialogTop(dialog);dentalInitialDialogFocus(dialog);dentalRestoreWindowScroll(keepX,keepY);window.setTimeout(()=>dentalRestoreWindowScroll(keepX,keepY),60);window.setTimeout(()=>dentalRestoreWindowScroll(keepX,keepY),180);});};
window.dentalFocusFirstRequired=dentalFocusFirstRequired;
window.dentalAfterDialogOpen=dentalAfterDialogOpen;
const dentalDialogOpeners=new WeakMap();
const dentalProtectedForm=dialog=>dialog?.querySelector('form[data-protect-unsaved]');
const dentalDialogCanClose=dialog=>{
  const form=dentalProtectedForm(dialog);
  if(!form||form.dataset.dirty!=='1')return true;
  if(!window.confirm('Discard the unsaved changes in this form?'))return false;
  form.reset();form.dataset.dirty='0';dialog.classList.remove('has-unsaved-changes');
  return true;
};
document.addEventListener('click',event=>{
  const open=event.target.closest?.('[data-open]');
  if(open){
    const dialog=document.getElementById(open.dataset.open);
    if(dialog){
      event.preventDefault();
      dentalDialogOpeners.set(dialog,open);
      dialog.showModal();
      dentalAfterDialogOpen(dialog);
    }
  }
  const close=event.target.closest?.('[data-close]');
  if(close){
    const dialog=close.closest('dialog');
    if(dialog){event.preventDefault();if(dentalDialogCanClose(dialog))dialog.close();}
  }
});
document.querySelectorAll('dialog').forEach(dialog=>{
  dialog.addEventListener('cancel',event=>{if(!dentalDialogCanClose(dialog))event.preventDefault();});
  dialog.addEventListener('close',()=>{
    if(!document.querySelector('dialog[open]'))document.body.classList.remove('dialog-open');
    const opener=dentalDialogOpeners.get(dialog);
    if(opener?.isConnected)requestAnimationFrame(()=>opener.focus({preventScroll:true}));
  });
});


document.querySelectorAll('[data-assignment-manager]').forEach(manager=>{
  const sections=[...manager.querySelectorAll('[data-assignment-section]')];
  const refreshSection=section=>{
    const checkboxes=[...section.querySelectorAll('[data-assignment-option] input[type="checkbox"]')];
    const count=checkboxes.filter(input=>input.checked).length;
    const host=section.querySelector('[data-assignment-count]');if(host)host.textContent=String(count);
  };
  const refreshSummary=()=>{
    const counts=sections.map(section=>section.querySelectorAll('[data-assignment-option] input[type="checkbox"]:checked').length);
    const summary=manager.querySelector('[data-assignment-summary]');
    if(summary)summary.textContent=`${counts[0]||0} branches · ${counts[1]||0} services · ${counts[2]||0} favorites`;
  };
  sections.forEach(section=>{
    const filter=section.querySelector('[data-assignment-filter]');
    const options=[...section.querySelectorAll('[data-assignment-option]')];
    const applyFilter=()=>{const term=String(filter?.value||'').trim().toLowerCase();options.forEach(option=>{option.hidden=term!==''&&!String(option.dataset.searchText||option.textContent||'').toLowerCase().includes(term);});};
    filter?.addEventListener('input',applyFilter);
    section.querySelectorAll('[data-assignment-toggle]').forEach(button=>button.addEventListener('click',()=>{
      const checked=button.dataset.assignmentToggle==='all';
      options.filter(option=>!option.hidden).forEach(option=>{const input=option.querySelector('input[type="checkbox"]');if(input&&!input.disabled)input.checked=checked;});
      refreshSection(section);refreshSummary();
    }));
    section.addEventListener('change',event=>{if(event.target.matches('input[type="checkbox"]')){refreshSection(section);refreshSummary();}});
    refreshSection(section);
  });
  refreshSummary();
});




document.addEventListener('click',event=>{
  const button=event.target.closest?.('[data-queue-transition]');
  if(!button)return;
  const dialog=document.getElementById('queue-transition-dialog');
  if(!dialog)return;
  event.preventDefault();
  const action=button.dataset.actionLabel||'Update queue status';
  const patient=button.dataset.patient||'this patient';
  const from=String(button.dataset.fromStatus||'').replaceAll('_',' ');
  const next=String(button.dataset.nextStatus||'').replaceAll('_',' ');
  const id=document.getElementById('queue-transition-id');
  const status=document.getElementById('queue-transition-status');
  const title=document.getElementById('queue-transition-title');
  const copy=document.getElementById('queue-transition-copy');
  const note=document.getElementById('queue-transition-note');
  const submit=document.getElementById('queue-transition-submit');
  if(id)id.value=button.dataset.queueId||'';
  if(status)status.value=button.dataset.nextStatus||'';
  if(title)title.textContent=action;
  if(copy)copy.textContent=`${patient}: ${from} → ${next}. This exception is recorded in the queue history.`;
  if(note)note.value='';
  if(submit)submit.textContent=action;
  dialog.showModal();
  dentalAfterDialogOpen(dialog);
  requestAnimationFrame(()=>{try{note?.focus({preventScroll:true});}catch(_){note?.focus();}});
});

document.querySelectorAll('[data-copy-text]').forEach(button=>button.addEventListener('click',async()=>{
  const original=button.dataset.copyLabel||button.textContent||'Copy';
  let value=button.dataset.copyText||'';
  try{value=new URL(value,window.location.href).href;}catch(e){}
  try{
    if(navigator.clipboard?.writeText)await navigator.clipboard.writeText(value);
    else{const input=document.createElement('textarea');input.value=value;input.style.position='fixed';input.style.opacity='0';document.body.append(input);input.select();document.execCommand('copy');input.remove();}
    button.textContent='Link copied';
  }catch(e){button.textContent='Copy failed';}
  setTimeout(()=>button.textContent=original,1800);
}));
document.querySelectorAll('[data-pay]').forEach(b=>b.addEventListener('click',()=>{const d=JSON.parse(b.dataset.pay);document.getElementById('pay-invoice-id').value=d.id;document.getElementById('pay-amount').value=d.balance.toFixed(2);document.getElementById('pay-label').textContent=`${d.patient||'Patient'} · ${d.invoice} · Balance ₱${d.balance.toFixed(2)}`;const dialog=document.getElementById('payment-form');if(dialog){dialog.showModal();dentalAfterDialogOpen(dialog);}}));

document.querySelectorAll('[data-invoice-for]').forEach(button=>button.addEventListener('click',()=>{
  const data=JSON.parse(button.dataset.invoiceFor);
  const patient=document.getElementById('invoice-patient');
  const queue=document.getElementById('invoice-queue-entry');
  const description=document.getElementById('invoice-description');
  const encounter=document.getElementById('invoice-encounter-id');
  if(patient)patient.value=String(data.patient_id);
  if(queue)queue.value=String(data.queue_id);
  if(description)description.value=data.description||'Dental services';
  if(encounter)encounter.value=String(data.encounter_id||'');
  const dialog=document.getElementById('invoice-form');
  if(dialog){dialog.showModal();dentalAfterDialogOpen(dialog);}
}));

document.querySelectorAll('[data-auto-open-dialog]').forEach(el=>{
  const dialog=document.getElementById(el.dataset.autoOpenDialog);
  if(dialog&&typeof dialog.showModal==='function'&&!dialog.open){
    dialog.showModal();
    dentalAfterDialogOpen(dialog);
  }
});

document.addEventListener('close',event=>{
  const dialog=event.target;
  if(!(dialog instanceof HTMLDialogElement))return;
  if(!dialog.matches('[data-auto-open-dialog]'))return;
  const url=new URL(window.location.href);
  if(url.searchParams.has('manage_id')){
    url.searchParams.delete('manage_id');
    history.replaceState(null,'',url.pathname+(url.searchParams.toString()?`?${url.searchParams}`:'')+url.hash);
  }
},true);

document.querySelector('[data-public-menu]')?.addEventListener('click',()=>{
  document.querySelector('[data-public-nav]')?.classList.toggle('open');
});

document.querySelectorAll('[data-public-nav] a').forEach(link=>link.addEventListener('click',()=>{
  document.querySelector('[data-public-nav]')?.classList.remove('open');
}));

const birthDateInput=document.querySelector('[data-birth-date]');
const guardianFields=document.querySelector('[data-guardian-fields]');
const refreshGuardianVisibility=()=>{
  if(!birthDateInput||!guardianFields)return;
  if(!birthDateInput.value){guardianFields.hidden=true;return;}
  const birth=new Date(`${birthDateInput.value}T00:00:00`);
  const today=new Date();
  let age=today.getFullYear()-birth.getFullYear();
  const month=today.getMonth()-birth.getMonth();
  if(month<0||(month===0&&today.getDate()<birth.getDate()))age--;
  guardianFields.hidden=age>=18;
};
birthDateInput?.addEventListener('change',refreshGuardianVisibility);
refreshGuardianVisibility();

document.querySelectorAll('[data-hours-toggle]').forEach(toggle=>{
  const sync=()=>document.querySelectorAll(`[data-hours-field="${toggle.dataset.hoursToggle}"]`).forEach(field=>field.disabled=!toggle.checked);
  toggle.addEventListener('change',sync);sync();
});
const exceptionOpen=document.querySelector('[data-exception-open]');
const syncException=()=>document.querySelectorAll('[data-exception-time]').forEach(el=>el.disabled=exceptionOpen&&!exceptionOpen.checked);
exceptionOpen?.addEventListener('change',syncException);syncException();

const availabilityForm=document.querySelector('[data-availability-form]');
if(availabilityForm){
  const day=availabilityForm.querySelector('[data-availability-day]');
  const start=availabilityForm.querySelector('[data-availability-start]');
  const end=availabilityForm.querySelector('[data-availability-end]');
  const note=availabilityForm.querySelector('[data-availability-note]');
  const hours=JSON.parse(availabilityForm.dataset.hours||'{}');
  const sync=()=>{const h=hours[day.value];if(!h||!h.open){start.disabled=end.disabled=true;note.textContent='The clinic is closed on this day.';return;}start.disabled=end.disabled=false;start.min=h.start;start.max=h.end;end.min=h.start;end.max=h.end;if(!start.value)start.value=h.start;if(!end.value)end.value=h.end;note.textContent=`Allowed clinic hours: ${h.start}–${h.end}`;};
  day.addEventListener('change',sync);sync();
}
const allDay=document.querySelector('[data-all-day]');
const syncAllDay=()=>document.querySelectorAll('[data-exception-part]').forEach(el=>el.disabled=!!allDay?.checked);
allDay?.addEventListener('change',syncAllDay);syncAllDay();

function slotSelectedServiceOptions(container){
  const primary=container.querySelector('[data-slot-service]')?.selectedOptions?.[0];
  const additional=[...(container.querySelector('[data-slot-additional-services]')?.selectedOptions||[])];
  return [primary,...additional].filter(Boolean).filter((option,index,list)=>option.value&&list.findIndex(item=>item.value===option.value)===index);
}

function slotServiceIds(container){
  return slotSelectedServiceOptions(container).map(option=>String(option.value));
}

function updateServiceBundleSummary(container){
  const options=slotSelectedServiceOptions(container);
  const host=container.querySelector('[data-service-bundle-summary]');
  const total=options.reduce((sum,option)=>sum+Number(option.dataset.duration||0)+Number(option.dataset.preparation||0)+Number(option.dataset.cleanup||0),0);
  if(host){
    if(!options.length)host.textContent='Select one or more services.';
    else host.textContent=`${options.length} planned service${options.length===1?'':'s'} · estimated ${total<60?`${total} min`:`${Math.floor(total/60)} hr${Math.floor(total/60)===1?'':'s'}${total%60?` ${total%60} min`:''}`}`;
  }
}

function syncAssignedChair(container,chairName){
  const hidden=container.querySelector('[data-slot-chair]');
  const display=container.querySelector('[data-slot-chair-display]');
  const value=String(chairName||'').trim();
  if(hidden)hidden.value=value;
  if(display)display.value=value||'Assign a chair to this dentist first';
}

function syncInhouseAssignment(container,slot=null){
  const provider=container.querySelector('[data-inhouse-provider]');
  const chairId=container.querySelector('[data-slot-chair-id]');
  if(provider)provider.value=slot?.inhouse_provider_user_id||'';
  if(chairId)chairId.value=slot?.chair_id||'';
  syncAssignedChair(container,slot?.chair_name||'');
  const display=container.querySelector('[data-slot-chair-display]');
  const requiresChair=slotSelectedServiceOptions(container).some(option=>String(option.dataset.requiresChair||'1')==='1');
  if(display){
    if(slot?.chair_name)display.value=slot.chair_name;
    else display.value=requiresChair?'Assigned automatically with the selected time':'No treatment chair required';
  }
}

async function refreshReturningContext(container){
  if(!container.hasAttribute('data-returning-booking'))return;
  const form=container.closest('form');
  const status=container.querySelector('[data-continuity-status]');
  const value=name=>String(form?.querySelector(`[name="${name}"]`)?.value||'').trim();
  const patientNo=value('patient_no');
  const first=value('first_name');
  const last=value('last_name');
  const birth=value('birth_date');
  const mobile=value('mobile');
  const email=value('email');
  const complete=!!birth&&(!!patientNo||(!!first&&!!last&&(!!mobile||!!email)));
  if(!complete){
    container.dataset.fixedDoctor='';
    container.dataset.bookingBlocked='0';
    const date=container.querySelector('[data-slot-date]');if(date)date.disabled=false;
    if(status){status.textContent='Enter the patient identity above. When a matching record has an established dentist, the dates below will be limited to that dentist.';status.className='continuity-schedule-status span-2';}
    container.dispatchEvent(new CustomEvent('returning-context-change'));
    return;
  }
  const requestId=String((Number(container.dataset.contextRequestId||0)+1));
  container.dataset.contextRequestId=requestId;
  if(status){status.textContent='Checking the established dentist and future schedule…';status.className='continuity-schedule-status span-2 is-loading';}
  try{
    const body=new URLSearchParams({
      csrf:value('csrf'),branch_id:String(container.dataset.branchId||''),patient_no:patientNo,
      first_name:first,last_name:last,birth_date:birth,mobile,email
    });
    const response=await fetch('api/returning_booking_context.php',{method:'POST',headers:{Accept:'application/json','Content-Type':'application/x-www-form-urlencoded;charset=UTF-8'},body});
    const data=await response.json();
    if(container.dataset.contextRequestId!==requestId)return;
    if(!data.ok)throw new Error(data.message||'The continuity schedule could not be checked.');
    container.dataset.fixedDoctor=String(data.doctor_id||'');
    container.dataset.bookingBlocked=data.locked?'1':'0';
    const date=container.querySelector('[data-slot-date]');
    if(date)date.disabled=!!data.locked;
    if(status){status.textContent=data.message;status.className=`continuity-schedule-status span-2 ${data.locked?'error':(data.doctor_id?'success':'')}`;}
    container.dispatchEvent(new CustomEvent('returning-context-change'));
  }catch(error){
    if(container.dataset.contextRequestId!==requestId)return;
    container.dataset.fixedDoctor='';
    container.dataset.bookingBlocked='0';
    const date=container.querySelector('[data-slot-date]');if(date)date.disabled=false;
    if(status){status.textContent=error.message;status.className='continuity-schedule-status span-2 error';}
  }
}

function slotProviderType(container){
  const options=slotSelectedServiceOptions(container);
  if(!options.length)return 'dentist';
  return options.some(option=>String(option.dataset.providerType||'dentist')!=='inhouse')?'dentist':'inhouse';
}

function slotPatientType(container){
  return String(container.dataset.patientType||'');
}

function slotProviderPreference(container){
  if(slotProviderType(container)==='inhouse')return 'clinic_decides';
  const selected=container.querySelector('[name="provider_preference_mode"]:checked');
  return selected?.value==='clinic_decides'?'clinic_decides':'specific';
}

function syncProviderFields(container){
  const providerType=slotProviderType(container);
  const preference=slotProviderPreference(container);
  const doctor=container.querySelector('[data-slot-doctor]');
  const doctorLabel=container.querySelector('[data-slot-doctor-label]')||doctor?.closest('label');
  const choice=container.querySelector('[data-provider-choice]');
  const continuity=container.querySelector('[data-continuity-note]');
  if(providerType==='inhouse'){
    if(choice)choice.hidden=true;
    if(doctor){doctor.required=false;doctor.disabled=false;doctor.innerHTML='<option value="0" selected>In-house clinic team</option>';doctor.value='0';doctor.dataset.inhouse='1';}
    if(doctorLabel)doctorLabel.hidden=true;
    if(continuity)continuity.hidden=true;
    syncInhouseAssignment(container,null);
  }else{
    if(choice)choice.hidden=false;
    if(doctor){delete doctor.dataset.inhouse;doctor.required=preference==='specific';doctor.disabled=preference!=='specific';if(preference==='clinic_decides')doctor.value='';}
    if(doctorLabel)doctorLabel.hidden=preference==='clinic_decides';
    if(continuity)continuity.hidden=false;
  }
  return providerType;
}

async function refreshAvailableDates(container){
  const branch=container.dataset.branchId;
  const service=container.querySelector('[data-slot-service]');
  const serviceIds=slotServiceIds(container);
  const doctor=container.querySelector('[data-slot-doctor]');
  const dateInput=container.querySelector('[data-slot-date]');
  const host=container.querySelector('[data-available-dates]');
  const scheduleFirst=container.hasAttribute('data-schedule-first');
  const providerType=syncProviderFields(container);
  const preference=slotProviderPreference(container);
  const fixedDoctor=(providerType==='dentist'&&preference==='specific')?String(container.dataset.fixedDoctor||''):'';
  if(!host||!service?.value)return;
  if(providerType==='dentist'&&preference==='specific'&&container.dataset.bookingBlocked==='1'){host.textContent='Online dates are unavailable until the clinic arranges the established dentist or an approved transfer.';host.classList.add('error');return;}
  if(providerType==='dentist'&&!scheduleFirst&&!doctor?.value){host.textContent='Choose a dentist to see available dates.';return;}
  host.classList.remove('error');
  host.classList.add('is-loading');
  host.textContent='Checking the next available dates…';
  try{
    const doctorId=providerType==='inhouse'?'0':(scheduleFirst?(preference==='clinic_decides'?'':fixedDoctor):doctor.value);
    const patientType=slotPatientType(container);
    const patientId=String(container.closest('form')?.querySelector('[name="patient_id"]')?.value||'');
    const response=await fetch(`api/available_dates.php?branch_id=${encodeURIComponent(branch)}&service_id=${encodeURIComponent(service.value)}&service_ids=${encodeURIComponent(serviceIds.join(','))}&doctor_id=${encodeURIComponent(doctorId)}&patient_id=${encodeURIComponent(patientId)}&patient_type=${encodeURIComponent(patientType)}&horizon=120&limit=18`,{headers:{Accept:'application/json'}});
    const data=await response.json();
    if(!data.ok)throw new Error(data.message||'Available dates could not be loaded.');
    host.replaceChildren();
    if(!data.dates.length){const empty=document.createElement('p');empty.className='available-date-empty';empty.textContent=data.message;host.append(empty);return;}
    const intro=document.createElement('div');intro.className='available-date-intro';intro.textContent=providerType==='inhouse'?'Available in-house service dates':'Next available dates';host.append(intro);
    const strip=document.createElement('div');strip.className='available-date-strip';
    data.dates.forEach(item=>{
      const button=document.createElement('button');button.type='button';button.className='available-date-tile';button.dataset.availableDate=item.date;
      if(dateInput?.value===item.date)button.classList.add('active');
      const weekday=document.createElement('span');weekday.textContent=item.weekday;
      const day=document.createElement('strong');day.textContent=item.day;
      const month=document.createElement('small');month.textContent=`${item.month} · ${item.slot_count} slot${item.slot_count===1?'':'s'}`;
      button.append(weekday,day,month);
      button.addEventListener('click',()=>{
        if(!dateInput)return;
        dateInput.value=item.date;
        host.querySelectorAll('.available-date-tile').forEach(tile=>tile.classList.toggle('active',tile===button));
        dateInput.dispatchEvent(new Event('change',{bubbles:true}));
      });
      strip.append(button);
    });
    host.append(strip);
  }catch(error){host.textContent=error.message;host.classList.add('error');}
  finally{host.classList.remove('is-loading');}
}

async function refreshSlotPicker(container){
  const branch=container.dataset.branchId;
  const service=container.querySelector('[data-slot-service]');
  const serviceIds=slotServiceIds(container);
  const doctor=container.querySelector('[data-slot-doctor]');
  const date=container.querySelector('[data-slot-date]');
  const time=container.querySelector('[data-slot-time]');
  const status=container.querySelector('[data-slot-status]');
  const reschedule=container.querySelector('[data-reschedule-note]');
  const scheduleFirst=container.hasAttribute('data-schedule-first');
  const providerType=syncProviderFields(container);
  const preference=slotProviderPreference(container);
  const fixedDoctor=(providerType==='dentist'&&preference==='specific')?String(container.dataset.fixedDoctor||''):'';
  if(providerType==='dentist'&&preference==='specific'&&container.dataset.bookingBlocked==='1'){
    if(time){time.innerHTML='<option value="">Contact the clinic to arrange this visit</option>';time.disabled=true;}
    if(doctor&&scheduleFirst){doctor.innerHTML='<option value="">Established dentist schedule unavailable</option>';doctor.disabled=true;}
    if(status){status.textContent='The established dentist does not currently have a complete online schedule at this branch.';status.className='slot-status error';}
    syncAssignedChair(container,'');return;
  }
  if(!service?.value||!date?.value){
    if(time)time.innerHTML='<option value="">Select service and date</option>';
    if(doctor&&scheduleFirst&&providerType==='dentist')doctor.innerHTML='<option value="">Choose a time first</option>';
    return;
  }
  if(providerType==='dentist'&&!scheduleFirst&&!doctor?.value){time.innerHTML='<option value="">Select service, dentist, and date</option>';return;}
  if(status){status.textContent='Checking published availability…';status.className='slot-status';}
  if(time)time.disabled=true;
  try{
    const doctorId=providerType==='inhouse'?'0':(scheduleFirst?(preference==='clinic_decides'?'':fixedDoctor):doctor.value);
    const patientType=slotPatientType(container);
    const patientId=String(container.closest('form')?.querySelector('[name="patient_id"]')?.value||'');
    const response=await fetch(`api/available_slots.php?branch_id=${encodeURIComponent(branch)}&service_id=${encodeURIComponent(service.value)}&service_ids=${encodeURIComponent(serviceIds.join(','))}&doctor_id=${encodeURIComponent(doctorId)}&patient_id=${encodeURIComponent(patientId)}&patient_type=${encodeURIComponent(patientType)}&date=${encodeURIComponent(date.value)}`,{headers:{Accept:'application/json'}});
    const data=await response.json();
    if(!data.ok)throw new Error(data.message||'Availability could not be loaded.');
    const isInhouse=data.provider_type==='inhouse'||providerType==='inhouse';
    if(isInhouse){
      const previousTime=time?.value||container.dataset.preferredTime||'';
      time.replaceChildren(new Option('Choose an available time',''));
      data.slots.forEach(slot=>{
        const resource=[slot.inhouse_provider_name,slot.chair_name].filter(Boolean).join(' · ');
        const option=new Option(`${slot.label}${resource?` · ${resource}`:''}`,slot.time,slot.time===previousTime,slot.time===previousTime);
        option.dataset.providerId=String(slot.inhouse_provider_user_id||'');
        option.dataset.chairId=String(slot.chair_id||'');
        time.append(option);
      });
      time.disabled=false;
      const syncResources=()=>{
        const slot=data.slots.find(item=>item.time===time.value)||null;
        syncInhouseAssignment(container,slot);
        if(doctor){doctor.innerHTML=`<option value="0" selected>${slot?.inhouse_provider_name||'In-house clinic team'}</option>`;doctor.value='0';doctor.disabled=false;}
        if(status){
          status.textContent=slot?`Assigned to ${slot.inhouse_provider_name}${slot.chair_name?` · ${slot.chair_name}`:''}.`:(data.slots.length?`${data.slots.length} complete in-house time${data.slots.length===1?'':'s'} available.`:(data.message||'No in-house time is available on this date.'));
          status.className='slot-status '+(data.slots.length?'success':'error');
        }
        if(reschedule){const changed=!!date.value&&!!time.value&&((container.dataset.preferredDate&&date.value!==container.dataset.preferredDate)||(container.dataset.preferredTime&&time.value!==container.dataset.preferredTime));reschedule.hidden=!changed;const note=reschedule.querySelector('textarea');if(note)note.required=changed;}
      };
      time.onchange=syncResources;
      syncResources();
      return;
    }
    if(scheduleFirst){
      const previousTime=time?.value||container.dataset.preferredTime||'';
      const previousDoctor=doctor?.value||container.dataset.preferredDoctor||'';
      time.innerHTML='<option value="">Choose an available time</option>'+data.times.map(slot=>`<option value="${slot.time}" ${slot.time===previousTime?'selected':''}>${slot.label}</option>`).join('');
      time.disabled=false;
      const syncReschedule=()=>{
        if(!reschedule)return;
        const preferredDoctor=String(container.dataset.preferredDoctor||'');
        const hasCompleteSelection=!!date.value&&!!time.value&&!!doctor.value;
        const preferredService=String(container.dataset.preferredService||'');const selectedService=String(container.querySelector('[data-slot-service]')?.value||'');const changed=hasCompleteSelection&&((!!container.dataset.preferredDate&&date.value!==container.dataset.preferredDate)||(!!container.dataset.preferredTime&&time.value!==container.dataset.preferredTime)||(!!preferredDoctor&&String(doctor.value)!==preferredDoctor)||(!!preferredService&&selectedService!==preferredService));
        reschedule.hidden=!changed;const note=reschedule.querySelector('textarea');if(note)note.required=changed;
      };
      const filterDoctors=()=>{
        const selected=data.times.find(slot=>slot.time===time.value);const list=selected?.doctors||[];const currentDoctor=doctor.value||previousDoctor;
        const preference=slotProviderPreference(container);
        if(preference==='clinic_decides'){
          doctor.innerHTML='<option value="">Clinic will assign the dentist</option>';
          doctor.value='';doctor.required=false;doctor.disabled=true;syncAssignedChair(container,'');
          if(status){status.textContent=list.length?`${list.length} qualified dentist${list.length===1?' is':'s are'} available at ${time.options[time.selectedIndex]?.text||time.value}. The clinic will make the final assignment.`:'Choose another date or time. No dentist accepting new patients is available for this selection.';status.className='slot-status '+(list.length?'success':'error');}
        }else{
          doctor.innerHTML='<option value="">Choose an available dentist</option>'+list.map(item=>{const specialty=item.specialty_name?` · ${item.specialty_name}`:'';return `<option value="${item.id}" data-chair="${String(item.chair_name||'').replaceAll('&','&amp;').replaceAll('"','&quot;')}" ${String(item.id)===String(currentDoctor)?'selected':''}>${item.name}${specialty}</option>`;}).join('');
          doctor.required=true;doctor.disabled=list.length===0;if(list.length===1&&!doctor.value)doctor.value=String(list[0].id);
          const selectedDoctor=list.find(item=>String(item.id)===String(doctor.value));syncAssignedChair(container,selectedDoctor?.chair_name||'');
          if(status){status.textContent=list.length?`${list.length} dentist${list.length===1?' is':'s are'} available at ${time.options[time.selectedIndex]?.text||time.value}.`:'Choose another date or time. No dentist accepting new patients is available for this selection.';status.className='slot-status '+(list.length?'success':'error');}
        }
        syncReschedule();
      };
      time.onchange=filterDoctors;doctor.onchange=()=>{const selected=data.times.find(slot=>slot.time===time.value)?.doctors?.find(item=>String(item.id)===String(doctor.value));syncAssignedChair(container,selected?.chair_name||'');syncReschedule();};
      date.addEventListener('change',syncReschedule,{once:true});filterDoctors();
      if(data.reschedule_required&&status){status.textContent=data.message;status.className='slot-status error';}
    }else{
      time.innerHTML='<option value="">Choose an available time</option>'+data.slots.map(slot=>`<option value="${slot.time}" data-doctor-id="${slot.doctor_id}">${slot.label}</option>`).join('');
      time.disabled=false;syncAssignedChair(container,data.doctor_chair||data.slots[0]?.chair_name||'');
      if(status){status.textContent=data.slots.length?`${data.slots.length} available time${data.slots.length===1?'':'s'} found${data.doctor_chair?` · ${data.doctor_chair}`:''}.`:(data.message||'No available doctor-and-chair times for this selection. Choose another date.');status.className='slot-status '+(data.slots.length?'success':'error');}
    }
  }catch(error){
    if(time)time.innerHTML='<option value="">No times available</option>';
    if(status){status.textContent=error.message;status.className='slot-status error';}
    syncAssignedChair(container,'');if(reschedule){reschedule.hidden=true;reschedule.querySelector('textarea')?.removeAttribute('required');}
  }
}
document.querySelectorAll('[data-slot-picker]').forEach(container=>{
  const scheduleFirst=container.hasAttribute('data-schedule-first');
  const service=container.querySelector('[data-slot-service]');
  const additional=container.querySelector('[data-slot-additional-services]');
  const doctor=container.querySelector('[data-slot-doctor]');
  const date=container.querySelector('[data-slot-date]');
  let refreshTimer=0;
  let datesTimer=0;
  const autoRefresh=()=>{
    window.clearTimeout(refreshTimer);
    refreshTimer=window.setTimeout(()=>refreshSlotPicker(container),120);
  };
  const autoDates=()=>{
    window.clearTimeout(datesTimer);
    datesTimer=window.setTimeout(()=>refreshAvailableDates(container),160);
  };
  [service,additional,doctor,date].filter(Boolean).forEach(el=>{
    el.addEventListener('change',autoRefresh);
    if(el.matches('input'))el.addEventListener('input',autoRefresh);
  });
  const serviceChanged=()=>{updateServiceBundleSummary(container);syncProviderFields(container);if(slotProviderType(container)==='inhouse'){container.dataset.fixedDoctor='';container.dataset.bookingBlocked='0';}autoDates();};
  service?.addEventListener('change',serviceChanged);
  additional?.addEventListener('change',serviceChanged);
  if(!scheduleFirst)doctor?.addEventListener('change',autoDates);
  date?.addEventListener('change',()=>container.querySelectorAll('[data-available-date]').forEach(tile=>tile.classList.toggle('active',tile.dataset.availableDate===date.value)));
  container.querySelectorAll('[name="provider_preference_mode"]').forEach(input=>input.addEventListener('change',()=>{syncProviderFields(container);autoDates();autoRefresh();}));
  if(container.hasAttribute('data-returning-booking')){
    let contextTimer=0;
    const autoContext=()=>{window.clearTimeout(contextTimer);if(slotProviderType(container)==='inhouse'){container.dataset.fixedDoctor='';container.dataset.bookingBlocked='0';container.querySelector('[data-continuity-status]')?.replaceChildren(document.createTextNode('This service is handled by the in-house clinic team and does not require continuity-dentist assignment.'));container.dispatchEvent(new CustomEvent('returning-context-change'));return;}contextTimer=window.setTimeout(()=>refreshReturningContext(container),600);};
    container.closest('form')?.querySelectorAll('[data-returning-identity]').forEach(field=>{field.addEventListener('input',autoContext);field.addEventListener('change',autoContext);});
    container.addEventListener('returning-context-change',()=>{autoDates();autoRefresh();});
    autoContext();
  }
  updateServiceBundleSummary(container);
  syncProviderFields(container);
  if(service?.value)autoDates();
  if(date?.value)autoRefresh();
});

document.querySelectorAll('[data-bypass-toggle]').forEach(toggle=>{
  const target=document.querySelector(toggle.dataset.bypassToggle);
  const sync=()=>{if(!target)return;target.hidden=!toggle.checked;target.querySelectorAll('textarea,input,select').forEach(field=>field.required=toggle.checked);};
  toggle.addEventListener('change',sync);sync();
});

document.querySelectorAll('[data-nav-group]').forEach(group=>{
  const key=`dental-nav-${group.dataset.navGroup}`;
  const saved=localStorage.getItem(key);
  if(saved==='closed')group.removeAttribute('open');
  if(saved==='open')group.setAttribute('open','');
  group.addEventListener('toggle',()=>localStorage.setItem(key,group.open?'open':'closed'));
});

document.querySelectorAll('[data-tooth]').forEach(button=>button.addEventListener('click',()=>{
  const input=document.querySelector('[data-tooth-input]');
  if(input)input.value=button.dataset.tooth;
  document.querySelectorAll('[data-tooth]').forEach(item=>item.classList.remove('selected'));
  button.classList.add('selected');
  document.querySelector('[data-tooth-label]')?.replaceChildren(document.createTextNode(`Tooth ${button.dataset.tooth}`));
}));

document.querySelectorAll('[data-confirm]').forEach(button=>button.addEventListener('click',event=>{
  if(!window.confirm(button.dataset.confirm||'Continue with this action?'))event.preventDefault();
}));

// Unified clinical encounter tabs, templates, checklist, and draft autosave.
document.querySelectorAll('[data-encounter-tabs]').forEach(tabBar=>{
  const form=tabBar.closest('form');
  const buttons=[...tabBar.querySelectorAll('[data-tab-target]')];
  const panels=[...form.querySelectorAll('[data-tab-panel]')];
  buttons.forEach(button=>button.addEventListener('click',()=>{
    buttons.forEach(item=>item.classList.toggle('active',item===button));
    panels.forEach(panel=>panel.hidden=panel.dataset.tabPanel!==button.dataset.tabTarget);
  }));
});

function decodeTemplate(value){
  try{
    const bytes=Uint8Array.from(atob(value),c=>c.charCodeAt(0));
    return new TextDecoder().decode(bytes);
  }catch(error){return '';}
}
document.querySelectorAll('[data-template-select]').forEach(select=>select.addEventListener('change',()=>{
  if(!select.value)return;
  const form=select.closest('form');
  const target=form?.querySelector(`[name="${select.dataset.templateTarget}"]`);
  if(target&&!target.value.trim())target.value=decodeTemplate(select.value);
  else if(target&&window.confirm('Replace the current text with this template?'))target.value=decodeTemplate(select.value);
  target?.dispatchEvent(new Event('input',{bubbles:true}));
  select.value='';
}));
document.querySelectorAll('[data-favorite-procedure]').forEach(select=>select.addEventListener('change',()=>{
  const target=select.closest('form')?.querySelector('[name="procedure_done"]');
  if(target&&select.value){target.value=target.value.trim()?`${target.value.trim()}\n${select.value}`:select.value;target.dispatchEvent(new Event('input',{bubbles:true}));}
  select.value='';
}));

document.querySelectorAll('[data-clinical-autosave]').forEach(form=>{
  const status=form.querySelector('[data-autosave-status]');
  const fields=['chief_complaint','examination','diagnosis','treatment_plan','procedure_done','soap_note','prescription','follow_up_date'];
  const completionMap={chief_complaint:'chief_complaint',examination:'examination',diagnosis:'diagnosis',procedure:'procedure_done',soap_note:'soap_note'};
  let timer=null;let dirty=false;let saving=false;
  const refreshChecklist=()=>{
    Object.entries(completionMap).forEach(([check,name])=>{
      const item=document.querySelector(`[data-check="${check}"]`);const field=form.querySelector(`[name="${name}"]`);if(!item||!field)return;
      const complete=field.value.trim()!=='';item.classList.toggle('complete',complete);item.classList.toggle('missing',!complete);item.querySelector('span').textContent=complete?'✓':'!';
    });
  };
  const save=async()=>{
    if(!dirty||saving||!form.dataset.autosaveUrl)return;
    saving=true;if(status){status.textContent='Saving…';status.className='autosave-status saving';}
    try{const body=new FormData(form);const response=await fetch(form.dataset.autosaveUrl,{method:'POST',body,headers:{Accept:'application/json'}});const data=await response.json();if(!response.ok||!data.ok)throw new Error(data.message||'Autosave failed.');dirty=false;if(status){status.textContent=`Saved ${data.saved_at}`;status.className='autosave-status saved';}}
    catch(error){if(status){status.textContent=error.message||'Autosave failed';status.className='autosave-status error';}}
    finally{saving=false;}
  };
  fields.forEach(name=>form.querySelector(`[name="${name}"]`)?.addEventListener('input',()=>{dirty=true;refreshChecklist();clearTimeout(timer);timer=setTimeout(save,2500);}));
  setInterval(save,20000);refreshChecklist();
});


// Visit-specific clinical workflow. The four primary phases keep the Dentist
// oriented while the secondary step rail still exposes the detailed charting
// sections needed for a complete encounter.
document.querySelectorAll('[data-clinical-section-tabs]').forEach(tabBar=>{
  const buttons=[...tabBar.querySelectorAll('[data-clinical-target]')];
  const panels=[...document.querySelectorAll('[data-clinical-panel]')];
  const phaseBar=document.querySelector('[data-clinical-phase-tabs]');
  const phaseButtons=[...(phaseBar?.querySelectorAll('[data-clinical-phase-target]')||[])];
  let activeTarget='';

  const phaseForTarget=target=>buttons.find(button=>button.dataset.clinicalTarget===target)?.dataset.clinicalPhase||'review';
  const setPhase=phase=>{
    phaseButtons.forEach(button=>button.classList.toggle('active',button.dataset.clinicalPhaseTarget===phase));
    buttons.forEach(button=>{button.hidden=button.dataset.clinicalPhase!==phase;});
    phaseBar?.querySelector(`[data-clinical-phase-target="${phase}"]`)?.scrollIntoView({block:'nearest',inline:'nearest'});
  };
  const activate=(target,updateHash=true)=>{
    const valid=buttons.some(button=>button.dataset.clinicalTarget===target);
    if(!valid)target=buttons[0]?.dataset.clinicalTarget||'intake';
    activeTarget=target;
    const phase=phaseForTarget(target);
    setPhase(phase);
    buttons.forEach(button=>button.classList.toggle('active',button.dataset.clinicalTarget===target));
    panels.forEach(panel=>{panel.hidden=panel.dataset.clinicalPanel!==target;});
    if(updateHash&&history.replaceState)history.replaceState(null,'',`#${target}`);
    tabBar.querySelector(`[data-clinical-target="${target}"]`)?.scrollIntoView({block:'nearest',inline:'nearest'});
  };
  const bestTargetForPhase=phase=>{
    const phaseSteps=buttons.filter(button=>button.dataset.clinicalPhase===phase);
    const missing=phaseSteps.find(button=>button.classList.contains('stage-missing'));
    const current=phaseSteps.find(button=>button.dataset.clinicalTarget===activeTarget);
    return (missing||current||phaseSteps[0])?.dataset.clinicalTarget||'intake';
  };
  buttons.forEach(button=>button.addEventListener('click',()=>activate(button.dataset.clinicalTarget||'intake')));
  phaseButtons.forEach(button=>button.addEventListener('click',()=>activate(bestTargetForPhase(button.dataset.clinicalPhaseTarget||'review'))));
  const requested=location.hash.replace('#','');
  const initial=buttons.some(button=>button.dataset.clinicalTarget===requested)?requested:(buttons[0]?.dataset.clinicalTarget||'intake');
  activate(initial,false);
});

// Keyboard-first clinical entry. Enter advances through single-line fields,
// Ctrl/Cmd+S saves the draft, and Alt+1..4 opens Review, Assess, Treat, or Close.
const clinicalFocusableFields = (form) => [...form.querySelectorAll('input,select,textarea,button')].filter((field) => {
  if (field.disabled || field.hidden || field.type === 'hidden') return false;
  if (field.closest('[hidden]')) return false;
  if (field.offsetParent === null && !field.closest('dialog[open]')) return false;
  return true;
});

document.querySelectorAll('form[data-keyboard-entry],dialog form[method="post"]').forEach((form) => {
  form.addEventListener('keydown', (event) => {
    if (event.defaultPrevented || event.isComposing) return;
    const target = event.target;
    if (!(target instanceof HTMLElement)) return;
    const tag = target.tagName.toLowerCase();
    const type = (target.getAttribute('type') || '').toLowerCase();
    if (event.key !== 'Enter') return;
    if (target.matches('[data-allow-enter],button,[type="submit"],[type="button"],[type="checkbox"],[type="radio"],[type="file"]')) return;
    if (tag === 'textarea' && !event.ctrlKey && !event.metaKey) return;
    if (event.altKey) return;
    const fields = clinicalFocusableFields(form).filter((field) => !field.matches('button,[type="submit"],[type="button"],[type="checkbox"],[type="radio"],[type="file"]'));
    const index = fields.indexOf(target);
    if (index < 0) return;
    event.preventDefault();
    const direction = event.shiftKey ? -1 : 1;
    let next = fields[index + direction];
    if (!event.shiftKey && form.matches('[data-required-first-entry]')) {
      const missingRequired = fields.find(field => field !== target && (field.required || field.matches('[data-required-first]')) && String(field.value || '').trim() === '');
      if (missingRequired) next = missingRequired;
    }
    if (next) {
      next.focus();
      if (typeof next.select === 'function' && next.matches('input:not([type="date"]):not([type="time"]):not([type="number"])')) next.select();
    }
  });
});

document.addEventListener('keydown', (event) => {
  if (event.isComposing) return;
  const recordForm = document.querySelector('[data-clinical-record-form]');
  if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's' && recordForm) {
    const save = recordForm.querySelector('[data-save-draft]');
    if (save && !save.disabled) {
      event.preventDefault();
      save.click();
    }
    return;
  }
  if (event.altKey && /^[1-4]$/.test(event.key)) {
    const phaseBar = document.querySelector('[data-clinical-phase-tabs]');
    const button = phaseBar?.querySelectorAll('[data-clinical-phase-target]')?.[Number(event.key) - 1];
    if (button) {
      event.preventDefault();
      button.click();
      window.setTimeout(() => {
        const activeStep=document.querySelector('[data-clinical-section-tabs] [data-clinical-target].active');
        const panel=activeStep?document.querySelector(`[data-clinical-panel="${activeStep.dataset.clinicalTarget}"]`):null;
        clinicalFocusableFields(panel || document.body)[0]?.focus();
      }, 30);
    }
  }
});


// DentalCare 0.6.0: move the clinician to the first incomplete workflow item
// instead of leaving them to hunt through the full treatment screen.
const dentalClinicalCheckTarget = (check) => ({
  intake: 'intake',
  alerts: 'intake',
  assessment: 'examination',
  charting: 'charting',
  diagnosis: 'diagnosis',
  disposition: 'treatment',
  consent: 'treatment',
  prescription: 'prescription',
  soap: 'notes',
  instructions: 'notes',
  followup: 'followup',
  billing: 'completion'
}[check] || 'intake');
const dentalGoToNextClinicalRequired = () => {
  if (typeof window.DentalClinicalFocusNext === 'function') {
    window.DentalClinicalFocusNext();
    return;
  }
  const item = document.querySelector('[data-clinical-checklist] [data-check-item].missing') || document.querySelector('[data-check-item].missing');
  const target = dentalClinicalCheckTarget(item?.dataset?.checkItem || 'intake');
  const tab = document.querySelector(`[data-clinical-section-tabs] [data-clinical-target="${target}"]`);
  if (tab) tab.click();
  window.setTimeout(() => {
    const panel = document.querySelector(`[data-clinical-panel="${target}"]`);
    const required = panel?.querySelector('[required]:not(:disabled),[data-required-first]:not(:disabled),textarea:not(:disabled),input:not(:disabled),select:not(:disabled)');
    const destination = required || panel || item;
    if (destination) dentalScrollTargetIntoView(destination, { force: false, behavior: 'smooth' });
    if (destination instanceof HTMLElement && destination.matches('input,select,textarea,button')) destination.focus({ preventScroll: true });
  }, 40);
};
document.addEventListener('click', event => {
  const next = event.target.closest?.('[data-next-clinical-required]');
  if (!next) return;
  event.preventDefault();
  dentalGoToNextClinicalRequired();
});
document.addEventListener('keydown', event => {
  if (event.altKey && event.key.toLowerCase() === 'n' && document.querySelector('[data-clinical-workspace]')) {
    event.preventDefault();
    dentalGoToNextClinicalRequired();
  }
});

// Keep the performed procedure and cashier handoff synchronized with the
// accepted treatment-plan item selected by the dentist.
document.querySelectorAll('[data-treatment-item-select]').forEach((select) => {
  const procedure = document.querySelector('[data-procedure-name]');
  const tooth = document.querySelector('[data-procedure-tooth]');
  const amount = document.querySelector('[data-billable-amount]');
  const prepare = document.querySelector('[data-prepare-invoice]');
  let lastAutoProcedure = '';
  let lastAutoTooth = '';
  let lastAutoAmount = '';
  const sync = () => {
    const option = select.selectedOptions?.[0];
    if (!option || !option.value) return;
    const nextProcedure = option.dataset.procedure || '';
    const nextTooth = option.dataset.tooth || '';
    const nextAmount = option.dataset.estimate || '';
    if (procedure && (!procedure.value.trim() || procedure.value === lastAutoProcedure)) procedure.value = nextProcedure;
    if (tooth && (!tooth.value.trim() || tooth.value === lastAutoTooth)) tooth.value = nextTooth;
    if (amount && (!amount.value || amount.value === lastAutoAmount)) amount.value = nextAmount;
    if (prepare && Number(nextAmount) > 0) prepare.checked = true;
    lastAutoProcedure = nextProcedure;
    lastAutoTooth = nextTooth;
    lastAutoAmount = nextAmount;
  };
  select.addEventListener('change', sync);
});

// Automatic GET filters: selects and dates submit immediately; search fields use a short debounce.
document.querySelectorAll('form[data-auto-submit]').forEach(form=>{
  let timer=0;
  const submit=()=>{
    window.clearTimeout(timer);
    if(typeof form.requestSubmit==='function')form.requestSubmit();
    else form.submit();
  };
  form.querySelectorAll('select[data-auto-submit-control],select:not([data-no-auto-submit]),input[type="date"][data-auto-submit-control],input[type="date"]:not([data-no-auto-submit])').forEach(control=>{
    control.addEventListener('change',submit);
  });
  form.querySelectorAll('[data-auto-search]').forEach(input=>{
    input.addEventListener('input',()=>{
      const value=input.value.trim();
      const minimum=Number(input.dataset.minLength||0);
      if(value!==''&&value.length<minimum)return;
      window.clearTimeout(timer);
      timer=window.setTimeout(submit,260);
    });
  });
});



// Patient lists filter immediately on the current page while a debounced
// database query refreshes all matching records. Focus and caret survive reload.
document.querySelectorAll('[data-live-patient-filter]').forEach(input=>{
  const rows=[...document.querySelectorAll('[data-patient-row]')];
  const empty=document.querySelector('[data-patient-live-empty]');
  const storageKey=`dental-patient-search:${location.pathname}`;
  const apply=()=>{
    const term=input.value.trim().toLocaleLowerCase();
    let shown=0;
    rows.forEach(row=>{
      const visible=term===''||String(row.dataset.searchText||row.textContent||'').toLocaleLowerCase().includes(term);
      row.hidden=!visible;if(visible)shown++;
    });
    if(empty)empty.hidden=shown>0||rows.length===0;
    sessionStorage.setItem(storageKey,JSON.stringify({value:input.value,focus:document.activeElement===input,caret:input.selectionStart||input.value.length}));
  };
  input.addEventListener('input',apply);
  input.addEventListener('search',apply);
  apply();
  try{
    const saved=JSON.parse(sessionStorage.getItem(storageKey)||'null');
    if(saved?.focus&&saved.value===input.value){requestAnimationFrame(()=>{input.focus();const caret=Math.min(Number(saved.caret)||input.value.length,input.value.length);input.setSelectionRange(caret,caret);});}
  }catch(_){ }
});

// Search inside long native selects without requiring a separate Filter button.
document.querySelectorAll('[data-select-search]').forEach(input=>{
  const select=document.getElementById(input.dataset.selectSearch||'');
  if(!select)return;
  const options=[...select.options].map(option=>({option,label:(option.textContent||'').toLowerCase()}));
  const filter=()=>{
    const term=input.value.trim().toLowerCase();
    let matches=0;
    options.forEach(({option,label},index)=>{
      const visible=index===0||term===''||label.includes(term);
      option.hidden=!visible;
      if(visible&&index>0)matches++;
    });
    input.setCustomValidity(term!==''&&matches===0?'No matching option was found.':'');
  };
  input.addEventListener('input',filter);
  input.addEventListener('search',filter);
});

// Clicking a weekly availability tile prepares the add-availability form for that day.
document.querySelectorAll('[data-availability-day-preset]').forEach(tile=>tile.addEventListener('click',event=>{
  if(event.target.closest('form,a,button'))return;
  const form=document.querySelector('[data-availability-form]');
  const day=form?.querySelector('[data-availability-day]');
  if(!form||!day)return;
  day.value=tile.dataset.availabilityDayPreset;
  day.dispatchEvent(new Event('change',{bubbles:true}));
  dentalScrollTargetIntoView(form, { force: false, behavior: 'smooth' });
  form.querySelector('[data-availability-start]')?.focus({preventScroll:true});
}));

// v0.5.7.3 unified doctor schedule builder.
document.querySelectorAll('[data-doctor-schedule]').forEach(calendar=>{
  if(calendar.dataset.mode!=='builder')return;
  const dialog=document.getElementById('working-hours-dialog');
  const form=dialog?.querySelector('[data-working-hours-form]');
  if(!dialog||!form)return;
  const dayField=form.querySelector('[data-builder-day]');
  const startField=form.querySelector('[data-builder-start]');
  const endField=form.querySelector('[data-builder-end]');
  let selection=null;

  const slotsForDay=day=>[...day.querySelectorAll('.doctor-calendar-slot:not(:disabled)')];
  const clearSelection=()=>document.querySelectorAll('.doctor-calendar-slot.is-selecting').forEach(slot=>slot.classList.remove('is-selecting'));
  const paintSelection=()=>{
    clearSelection();
    if(!selection)return;
    const slots=slotsForDay(selection.day);
    const first=Math.min(selection.startIndex,selection.endIndex);
    const last=Math.max(selection.startIndex,selection.endIndex);
    slots.slice(first,last+1).forEach(slot=>slot.classList.add('is-selecting'));
  };
  const openSelection=()=>{
    if(!selection)return;
    const slots=slotsForDay(selection.day);
    const first=Math.min(selection.startIndex,selection.endIndex);
    const last=Math.max(selection.startIndex,selection.endIndex);
    const firstSlot=slots[first];
    const lastSlot=slots[last];
    if(!firstSlot||!lastSlot){selection=null;clearSelection();return;}
    dayField.value=selection.day.dataset.scheduleDay||'';
    startField.value=firstSlot.dataset.slotStart||'';
    endField.value=lastSlot.dataset.slotEnd||'';
    form.querySelectorAll('input[name="copy_days[]"]').forEach(box=>{box.checked=false;box.disabled=box.hasAttribute('data-closed-day')||box.value===dayField.value;});
    dialog.showModal();
    selection=null;
    window.setTimeout(clearSelection,80);
  };

  calendar.querySelectorAll('.doctor-calendar-day').forEach(day=>{
    const slots=slotsForDay(day);
    slots.forEach((slot,index)=>{
      slot.addEventListener('pointerdown',event=>{
        if(event.button!==undefined&&event.button!==0)return;
        event.preventDefault();
        selection={day,startIndex:index,endIndex:index,pointerId:event.pointerId};
        paintSelection();
      });
      slot.addEventListener('pointerenter',()=>{
        if(!selection||selection.day!==day)return;
        selection.endIndex=index;
        paintSelection();
      });
      slot.addEventListener('pointerup',event=>{
        if(!selection||selection.day!==day)return;
        selection.endIndex=index;
        event.preventDefault();
        openSelection();
      });
      slot.addEventListener('keydown',event=>{
        if(event.key!=='Enter'&&event.key!==' ')return;
        event.preventDefault();
        selection={day,startIndex:index,endIndex:index};
        paintSelection();
        openSelection();
      });
    });
  });
  document.addEventListener('pointermove',event=>{
    if(!selection)return;
    const target=document.elementFromPoint(event.clientX,event.clientY)?.closest?.('.doctor-calendar-slot');
    if(!target||!selection.day.contains(target)||target.disabled)return;
    const slots=slotsForDay(selection.day);
    const index=slots.indexOf(target);
    if(index<0||index===selection.endIndex)return;
    selection.endIndex=index;
    paintSelection();
  });
  document.addEventListener('pointerup',event=>{
    if(!selection)return;
    const target=event.target instanceof Element?event.target.closest('.doctor-calendar-slot'):null;
    if(target&&selection.day.contains(target))return;
    openSelection();
  });
});

document.querySelectorAll('[data-exception-date]').forEach(button=>button.addEventListener('click',()=>{
  const dialog=document.getElementById('exception-dialog');
  const field=dialog?.querySelector('[data-exception-date]');
  if(!dialog||!field)return;
  field.value=button.dataset.exceptionDate||field.value;
  dialog.showModal();
}));

document.querySelectorAll('[data-schedule-exception-form]').forEach(form=>{
  const allDayField=form.querySelector('[data-all-day]');
  const sync=()=>form.querySelectorAll('[data-exception-part]').forEach(field=>{field.disabled=!!allDayField?.checked;field.required=!allDayField?.checked;});
  allDayField?.addEventListener('change',sync);
  sync();
});

document.querySelectorAll('[data-working-hours-form]').forEach(form=>{
  const day=form.querySelector('[data-builder-day]');
  const sync=()=>form.querySelectorAll('input[name="copy_days[]"]').forEach(box=>{
    const originallyClosed=box.hasAttribute('data-closed-day');
    box.disabled=originallyClosed||box.value===day?.value;
    if(box.disabled)box.checked=false;
  });
  day?.addEventListener('change',sync);
  sync();
});


// v0.5.7.8 scalable list controls.
// Long tables and card lists are paginated locally so records pages remain usable
// even when hundreds of matching rows are returned by the server.
function dentalPagerButton(label,title,action){
  const button=document.createElement('button');
  button.type='button';
  button.className='smart-list-page-btn';
  button.textContent=label;
  button.title=title;
  button.addEventListener('click',action);
  return button;
}
function dentalPagerControls({withSearch=false,onSearch,onPageSize,onFirst,onPrevious,onNext,onLast}){
  const controls=document.createElement('div');controls.className='smart-list-controls';
  const left=document.createElement('div');left.className='smart-list-controls-left';
  const summary=document.createElement('span');summary.className='smart-list-summary';summary.setAttribute('aria-live','polite');left.append(summary);
  let search=null;
  if(withSearch){
    const label=document.createElement('label');label.className='smart-list-search';label.append(document.createTextNode('Search list'));
    search=document.createElement('input');search.type='search';search.placeholder='Search loaded records';search.autocomplete='off';
    search.addEventListener('input',()=>onSearch(search.value));label.append(search);left.append(label);
  }
  const right=document.createElement('div');right.className='smart-list-controls-right';
  const sizeLabel=document.createElement('label');sizeLabel.className='smart-list-size';sizeLabel.append(document.createTextNode('Rows'));
  const size=document.createElement('select');[10,20,50,100].forEach(value=>{const option=document.createElement('option');option.value=String(value);option.textContent=String(value);size.append(option);});
  size.addEventListener('change',()=>onPageSize(Number(size.value)));sizeLabel.append(size);right.append(sizeLabel);
  const nav=document.createElement('div');nav.className='smart-list-nav';
  const first=dentalPagerButton('«','First page',onFirst);const previous=dentalPagerButton('‹','Previous page',onPrevious);
  const page=document.createElement('span');page.className='smart-list-page-label';
  const next=dentalPagerButton('›','Next page',onNext);const last=dentalPagerButton('»','Last page',onLast);
  nav.append(first,previous,page,next,last);right.append(nav);controls.append(left,right);
  return {controls,summary,search,size,page,first,previous,next,last};
}
function dentalPaginateCollection({items,label='records',pageSize=25,withSearch=false,getMatches,mountTop,mountBottom,scrollTarget=null,persistSize=true,emptyElement=null}){
  let page=1;
  let size=[10,20,50,100].includes(Number(pageSize))?Number(pageSize):25;
  let query='';
  let matches=[];
  const pageCount=()=>Math.max(1,Math.ceil(matches.length/size));
  let top;let bottom;
  const setPage=next=>{page=Math.min(Math.max(1,next),pageCount());render();};
  const setSize=next=>{size=[10,20,50,100].includes(Number(next))?Number(next):25;page=1;if(persistSize)localStorage.setItem('dentalcare-list-page-size',String(size));render();};
  top=dentalPagerControls({withSearch,onSearch:value=>{query=value.trim().toLowerCase();page=1;render();},onPageSize:setSize,onFirst:()=>setPage(1),onPrevious:()=>setPage(page-1),onNext:()=>setPage(page+1),onLast:()=>setPage(pageCount())});
  bottom=dentalPagerControls({onSearch:()=>{},onPageSize:setSize,onFirst:()=>setPage(1),onPrevious:()=>setPage(page-1),onNext:()=>setPage(page+1),onLast:()=>setPage(pageCount())});
  top.controls.classList.add('smart-list-controls-top');bottom.controls.classList.add('smart-list-controls-bottom');
  mountTop(top.controls);mountBottom(bottom.controls);
  const syncControl=control=>{
    const total=matches.length;const firstRow=total?(page-1)*size+1:0;const lastRow=Math.min(page*size,total);
    control.summary.textContent=`Showing ${firstRow}–${lastRow} of ${total} ${label}`;
    control.size.value=String(size);control.page.textContent=`Page ${page} of ${pageCount()}`;
    control.first.disabled=control.previous.disabled=page<=1;control.next.disabled=control.last.disabled=page>=pageCount();
  };
  function render({keepScroll=false}={}){
    matches=getMatches(query);
    page=Math.min(Math.max(1,page),pageCount());
    const visible=new Set(matches.slice((page-1)*size,page*size));
    items.forEach(item=>item.classList.toggle('smart-page-hidden',!visible.has(item)));
    if(emptyElement)emptyElement.hidden=matches.length!==0;
    syncControl(top);syncControl(bottom);
    if(!keepScroll&&scrollTarget)scrollTarget.scrollTop=0;
  }
  return {render,reset:()=>{page=1;render();},top,bottom};
}

(function initializeScalableTables(){
  const storedSize=Number(localStorage.getItem('dentalcare-list-page-size')||25);
  document.querySelectorAll('table').forEach(table=>{
    if(table.dataset.noPagination==='true'||table.closest('[data-no-pagination]'))return;
    const body=table.tBodies?.[0];if(!body)return;
    const rows=[...body.rows];
    const threshold=Number(table.dataset.paginationThreshold||25);
    if(rows.length<=threshold&&!table.hasAttribute('data-paginate'))return;
    const parent=table.parentElement;if(!parent||table.dataset.smartListReady==='1')return;
    table.dataset.smartListReady='1';table.classList.add('smart-list-table');parent.classList.add('contains-smart-list');
    const shell=document.createElement('div');shell.className='smart-list-container';parent.insertBefore(shell,table);
    const viewport=document.createElement('div');viewport.className='smart-list-viewport';shell.append(viewport);viewport.append(table);
    const empty=document.createElement('div');empty.className='smart-list-empty';empty.textContent='No records match this list search.';empty.hidden=true;
    let pager;
    pager=dentalPaginateCollection({
      items:rows,label:'records',pageSize:storedSize,withSearch:true,scrollTarget:viewport,emptyElement:empty,
      getMatches:query=>rows.filter(row=>!row.dataset.externalHidden&&(query===''||(row.textContent||'').toLowerCase().includes(query))),
      mountTop:controls=>shell.insertBefore(controls,viewport),
      mountBottom:controls=>{viewport.after(empty,controls);}
    });
    pager.render({keepScroll:true});
  });
})();

(function initializeScalableCardLists(){
  const storedSize=Number(localStorage.getItem('dentalcare-list-page-size')||25);
  const selectors=['.module-card-grid','.timeline-list','.search-result-list','.appointment-day-list','.schedule-block-list'];
  document.querySelectorAll(selectors.join(',')).forEach(host=>{
    if(host.dataset.noPagination==='true'||host.dataset.smartListReady==='1')return;
    const items=[...host.children].filter(item=>!item.classList.contains('queue-empty')&&!item.classList.contains('empty-state'));
    const threshold=Number(host.dataset.paginationThreshold||25);
    if(items.length<=threshold)return;
    host.dataset.smartListReady='1';host.classList.add('smart-card-list');
    dentalPaginateCollection({
      items,label:host.dataset.pageLabel||'items',pageSize:Number(host.dataset.pageSize||storedSize),withSearch:true,
      getMatches:query=>items.filter(item=>query===''||(item.textContent||'').toLowerCase().includes(query)),
      mountTop:controls=>host.before(controls),mountBottom:controls=>host.after(controls)
    }).render({keepScroll:true});
  });
})();

// Expected-patient list on the clinic queue, with status filters and independent paging.
function dentalInitializeExpectedPanels(root=document){
  root.querySelectorAll('[data-expected-day]:not([data-expected-ready])').forEach(panel=>{
    panel.dataset.expectedReady='1';
    const search=panel.querySelector('[data-expected-search]');
    const filters=[...panel.querySelectorAll('[data-expected-filter]')];
    const list=panel.querySelector('[data-expected-list]');
    const empty=panel.querySelector('[data-expected-no-results]');
    let active='all';
    let pager=null;
    const rows=()=>[...panel.querySelectorAll('[data-expected-row]')];
    const matchingRows=()=>{
      const query=(search?.value||'').trim().toLowerCase();
      return rows().filter(row=>{
        const group=row.dataset.group||'';
        return (active==='all'||group===active)&&(query===''||(row.dataset.search||'').includes(query));
      });
    };
    const apply=()=>{
      const currentRows=rows();
      const matches=matchingRows();
      currentRows.forEach(row=>row.dataset.externalHidden=matches.includes(row)?'':'1');
      if(pager)pager.reset();
      else currentRows.forEach(row=>row.classList.toggle('smart-page-hidden',!matches.includes(row)));
      if(empty)empty.hidden=matches.length!==0||currentRows.length===0;
    };
    const currentRows=rows();
    if(list&&currentRows.length>Number(list.dataset.paginationThreshold||25)){
      pager=dentalPaginateCollection({
        items:currentRows,label:'expected patients',pageSize:Number(list.dataset.pageSize||25),withSearch:false,persistSize:false,
        getMatches:()=>matchingRows(),mountTop:controls=>list.before(controls),mountBottom:controls=>list.after(controls)
      });
      pager.top.controls.classList.add('expected-list-pager');pager.bottom.controls.classList.add('expected-list-pager');
    }
    filters.forEach(button=>button.addEventListener('click',()=>{
      active=button.dataset.expectedFilter||'all';
      filters.forEach(item=>item.classList.toggle('active',item===button));apply();
    }));
    search?.addEventListener('input',apply);
    apply();
  });
}
window.dentalInitializeExpectedPanels=dentalInitializeExpectedPanels;
dentalInitializeExpectedPanels();
document.addEventListener('dental:live-queue-updated',event=>dentalInitializeExpectedPanels(event.detail?.region||document));
document.addEventListener('click',event=>{
  const button=event.target.closest?.('[data-expected-checkin]');
  if(!button)return;
  const dialog=document.getElementById('appointment-checkin');
  const select=dialog?.querySelector('select[name="appointment_id"]');
  if(!dialog||!select)return;
  select.value=button.dataset.expectedCheckin||'';
  select.dispatchEvent(new Event('change',{bubbles:true}));
  dialog.showModal();
  dialog.querySelector('textarea[name="chief_complaint"]')?.focus();
});

// Visible return control for long pages.
(()=>{
  const button=document.createElement('button');button.type='button';button.className='page-jump-top';button.textContent='↑ Top';button.setAttribute('aria-label','Return to top of page');
  button.addEventListener('click',()=>window.scrollTo({top:0,behavior:'smooth'}));document.body.append(button);
  const sync=()=>button.classList.toggle('show',window.scrollY>600);window.addEventListener('scroll',sync,{passive:true});sync();
})();



// v0.5.8.2 remote patient picker. Large clinics search the database instead of
// downloading thousands of patient options into every appointment or queue form.
document.querySelectorAll('[data-patient-picker]').forEach(picker=>{
  const query=picker.querySelector('[data-patient-query]');
  const hidden=picker.querySelector('[data-patient-id]');
  const results=picker.querySelector('[data-patient-results]');
  const selected=picker.querySelector('[data-patient-selection]');
  const endpoint=picker.dataset.endpoint||'api/patient_search.php';
  if(!query||!hidden||!results)return;
  let timer=0,controller=null;
  const clearResults=()=>{results.replaceChildren();results.hidden=true;};
  const choose=item=>{
    hidden.value=String(item.id||'');
    hidden.dataset.primaryDentistId=String(item.primary_dentist_id||'');
    hidden.dataset.primaryDentistName=String(item.primary_dentist_name||'');
    query.value=String(item.label||item.name||'');
    query.setCustomValidity('');
    if(selected){selected.textContent=`Selected: ${item.label||item.name||''}`;selected.hidden=false;}
    clearResults();
    hidden.dispatchEvent(new Event('change',{bubbles:true}));
    picker.dispatchEvent(new CustomEvent('patient-selected',{bubbles:true,detail:item}));
  };
  const render=(items,message='')=>{
    results.replaceChildren();
    if(!items.length){const empty=document.createElement('div');empty.className='remote-picker-empty';empty.textContent=message||'No matching patient found.';results.append(empty);results.hidden=false;return;}
    items.forEach(item=>{const button=document.createElement('button');button.type='button';button.className='remote-picker-option';button.innerHTML=`<strong></strong><small></small>`;button.querySelector('strong').textContent=item.label||item.name||'';button.querySelector('small').textContent=[item.match_context||'',item.birth_date?`Born ${item.birth_date}`:'',item.primary_dentist_name?`Continuity: ${item.primary_dentist_name}`:''].filter(Boolean).join(' · ');button.addEventListener('click',()=>choose(item));results.append(button);});
    results.hidden=false;
  };
  const search=async()=>{
    const term=query.value.trim();
    hidden.value='';hidden.dataset.primaryDentistId='';hidden.dataset.primaryDentistName='';
    if(selected){selected.textContent='';selected.hidden=true;}
    if(term.length<2){clearResults();return;}
    controller?.abort();controller=new AbortController();
    results.hidden=false;results.textContent='Searching patients…';
    try{
      const url=new URL(endpoint,window.location.href);url.searchParams.set('q',term);
      const response=await fetch(url,{headers:{Accept:'application/json'},credentials:'same-origin',signal:controller.signal});
      const raw=await response.text();let data=null;
      try{data=raw?JSON.parse(raw):null;}catch(parseError){
        if(response.redirected||response.status===401||response.url.includes('login.php'))throw new Error('Your session has expired. Sign in again, then reopen this page.');
        throw new Error(`Patient search returned an invalid server response${response.status?` (HTTP ${response.status})`:''}. Refresh the page or ask an administrator to check the patient-search route.`);
      }
      if(!response.ok||!data||!data.ok)throw new Error(data?.message||data?.error||`Patient search failed (HTTP ${response.status}).`);
      render(data.results||[],data.message||'');
    }catch(error){if(error.name==='AbortError')return;render([],error.message||'Patient search failed.');}
  };
  query.addEventListener('input',()=>{
    if(hidden.value){
      hidden.value='';hidden.dataset.primaryDentistId='';hidden.dataset.primaryDentistName='';
      if(selected){selected.textContent='';selected.hidden=true;}
      picker.dispatchEvent(new CustomEvent('patient-cleared',{bubbles:true}));
    }
    window.clearTimeout(timer);timer=window.setTimeout(search,250);
  });
  query.addEventListener('focus',()=>{if(query.value.trim().length>=2&&!hidden.value)search();});
  query.addEventListener('keydown',event=>{if(event.key==='Escape')clearResults();});
  document.addEventListener('click',event=>{if(!picker.contains(event.target))clearResults();});
  picker.closest('form')?.addEventListener('submit',event=>{if(hidden.value||picker.dataset.optional==='true')return;event.preventDefault();query.setCustomValidity('Search and select a patient from the results.');query.reportValidity();query.focus();});
});



// Existing-patient walk-in details. Selecting a patient fills a read-only
// identity/coverage summary and disables the new-patient registration fields.
document.querySelectorAll('#walkin-form [data-patient-picker]').forEach((picker)=>{
  const dialog=picker.closest('#walkin-form');
  if(!dialog)return;
  const card=dialog.querySelector('[data-walkin-existing-details]');
  const newFields=dialog.querySelector('[data-new-walkin-fields]');
  const selectedNo=dialog.querySelector('[data-selected-patient-no]');
  const hidden=picker.querySelector('[data-patient-id]');
  const setText=(selector,value,fallback='—')=>{const el=dialog.querySelector(selector);if(el)el.textContent=String(value||'').trim()||fallback;};
  const setNewFieldsDisabled=(disabled)=>{
    newFields?.querySelectorAll('input,select,textarea').forEach((field)=>{
      field.disabled=disabled;
      if(disabled)field.setAttribute('aria-disabled','true');else field.removeAttribute('aria-disabled');
    });
    newFields?.classList.toggle('walkin-new-fields-disabled',disabled);
  };
  const clear=()=>{
    if(selectedNo)selectedNo.value='';
    if(card)card.hidden=true;
    setNewFieldsDisabled(false);
  };
  picker.addEventListener('patient-cleared',clear);
  picker.addEventListener('patient-selected',(event)=>{
    const item=event.detail||{};
    if(selectedNo)selectedNo.value=String(item.patient_no||'');
    setText('[data-existing-name]',item.name||item.label||'Selected patient','Selected patient');
    setText('[data-existing-number]',item.patient_no?`Patient no. ${item.patient_no}`:'');
    const birth=[item.birth_date||'',item.age!==null&&item.age!==undefined&&item.age!==''?`${item.age} years old`:'' ].filter(Boolean).join(' · ');
    setText('[data-existing-birth]',birth);
    setText('[data-existing-sex]',item.sex);
    setText('[data-existing-mobile]',item.mobile);
    setText('[data-existing-email]',item.email);
    setText('[data-existing-branch]',item.branch_name);
    setText('[data-existing-dentist]',item.primary_dentist_name);
    setText('[data-existing-guardian]',item.guardian_summary);
    setText('[data-existing-insurance]',item.insurance_summary);
    const balance=Number(item.outstanding_balance||0);
    setText('[data-existing-balance]',new Intl.NumberFormat('en-PH',{style:'currency',currency:'PHP'}).format(Number.isFinite(balance)?balance:0),'₱0.00');
    setText('[data-existing-emergency]',item.emergency_contact);
    setText('[data-existing-address]',item.address);
    setText('[data-existing-alerts]',item.medical_alerts,'None recorded');
    const profile=dialog.querySelector('[data-existing-profile]');
    if(profile)profile.href=`patients.php?action=view&id=${encodeURIComponent(String(item.id||''))}`;
    if(card)card.hidden=false;
    setNewFieldsDisabled(true);
  });
  if(hidden?.value){
    if(card)card.hidden=false;
    setNewFieldsDisabled(true);
  }else{
    clear();
  }
});

// v0.5.7.6 continuity-of-care form behavior.
document.querySelectorAll('form').forEach(form=>{
  const patient=form.querySelector('select[name="patient_id"],select[data-continuity-patient],input[data-patient-id]');
  const doctor=form.querySelector('[data-continuity-doctor],[data-slot-doctor]');
  const note=form.querySelector('[data-continuity-note]');
  const transfer=form.querySelector('[data-continuity-transfer]');
  const reasonWrap=form.querySelector('[data-continuity-reason-wrap]');
  const reason=form.querySelector('[data-continuity-reason]');

  const selectedPrimary=()=>{
    const option=patient?.selectedOptions?.[0];
    return {
      id:String(option?.dataset?.primaryDentistId||patient?.dataset?.primaryDentistId||''),
      name:String(option?.dataset?.primaryDentistName||patient?.dataset?.primaryDentistName||'')
    };
  };
  const syncTransfer=()=>{
    if(reasonWrap)reasonWrap.hidden=!transfer?.checked;
    if(reason)reason.required=!!transfer?.checked;
  };
  const syncContinuity=()=>{
    if(!patient||!doctor)return;
    const primary=selectedPrimary();
    if(primary.id){
      form.dataset.preferredDoctor=primary.id;
      const matching=[...doctor.options].find(option=>String(option.value)===primary.id);
      if(matching && !transfer?.checked && String(doctor.value)!==primary.id){
        doctor.value=primary.id;
        doctor.dispatchEvent(new Event('change',{bubbles:true}));
      }
      if(note){
        const changed=doctor.value && String(doctor.value)!==primary.id;
        note.innerHTML=changed
          ? `Continuity dentist: <strong>${primary.name||'the established dentist'}</strong>. Changing dentists requires an authorized transfer reason.`
          : `Continuity dentist: <strong>${primary.name||'the established dentist'}</strong>. Future appointments and follow-ups remain with this dentist.`;
        note.classList.toggle('warning',changed);
      }
    }else if(note){
      note.textContent='No continuity dentist is assigned yet. The dentist on the first confirmed appointment will become the patient’s established dentist.';
      note.classList.remove('warning');
    }
  };

  patient?.addEventListener('change',syncContinuity);
  doctor?.addEventListener('change',syncContinuity);
  transfer?.addEventListener('change',()=>{syncTransfer();syncContinuity();});
  syncTransfer();
  syncContinuity();
});

// v0.5.8.1 responsive weekly appointment calendar navigation.
const dentalInitializeWeekCarousels=(root=document)=>{
  root.querySelectorAll('[data-week-carousel]').forEach(carousel=>{
    if(carousel.dataset.weekCarouselReady==='1')return;
    carousel.dataset.weekCarouselReady='1';
    const viewport=carousel.querySelector('[data-week-viewport]');
    const grid=carousel.querySelector('[data-week-grid]');
    const days=[...carousel.querySelectorAll('[data-week-day]')];
    const position=carousel.querySelector('[data-week-position]');
    const previous=carousel.querySelector('[data-week-scroll="-1"]');
    const next=carousel.querySelector('[data-week-scroll="1"]');
    if(!viewport||!grid||!days.length)return;

    const metrics=()=>{
      const style=getComputedStyle(grid);
      const gap=parseFloat(style.columnGap||style.gap||'0')||0;
      const dayWidth=days[0].getBoundingClientRect().width||viewport.clientWidth;
      const step=Math.max(1,dayWidth+gap);
      const visible=Math.max(1,Math.min(days.length,Math.floor((viewport.clientWidth+gap)/step+.05)));
      return {gap,dayWidth,step,visible};
    };
    const update=()=>{
      const {step,visible}=metrics();
      const first=Math.max(0,Math.min(days.length-1,Math.round(viewport.scrollLeft/step)));
      const last=Math.min(days.length,first+visible);
      if(position)position.textContent=`Days ${first+1}–${last} of ${days.length}`;
      if(previous)previous.disabled=viewport.scrollLeft<=2;
      if(next)next.disabled=viewport.scrollLeft>=viewport.scrollWidth-viewport.clientWidth-2;
    };
    const move=direction=>{
      const {step}=metrics();
      viewport.scrollBy({left:Number(direction)*step,behavior:'smooth'});
    };
    previous?.addEventListener('click',()=>move(-1));
    next?.addEventListener('click',()=>move(1));
    viewport.addEventListener('keydown',event=>{
      if(event.key==='ArrowLeft'){event.preventDefault();move(-1);}
      if(event.key==='ArrowRight'){event.preventDefault();move(1);}
    });
    let frame=0;
    viewport.addEventListener('scroll',()=>{cancelAnimationFrame(frame);frame=requestAnimationFrame(update);},{passive:true});
    const observer=typeof ResizeObserver!=='undefined'?new ResizeObserver(update):null;
    observer?.observe(viewport);

    requestAnimationFrame(()=>{
      const todayIndex=days.findIndex(day=>day.classList.contains('today'));
      if(todayIndex>=0){
        const {step,visible}=metrics();
        const target=Math.max(0,Math.min(days.length-visible,todayIndex-Math.floor(visible/2)));
        viewport.scrollLeft=target*step;
      }
      update();
    });
  });
};
window.dentalInitializeWeekCarousels=dentalInitializeWeekCarousels;
dentalInitializeWeekCarousels();
document.addEventListener('dental:live-queue-updated',event=>dentalInitializeWeekCarousels(event.detail?.region||document));

// v0.5.9.8.11 cashier account search and queue filtering.
document.querySelectorAll('[data-cashier-patient-picker]').forEach(picker=>{
  picker.addEventListener('patient-selected',event=>{
    const patientId=Number(event.detail?.id||0);
    if(patientId<1)return;
    const form=picker.querySelector('[data-cashier-lock-form]');
    const field=form?.querySelector('[data-cashier-lock-patient]');
    if(form&&field){field.value=String(patientId);form.requestSubmit();return;}
    window.location.assign(`billing.php?patient_id=${encodeURIComponent(String(patientId))}`);
  });
});

document.querySelectorAll('[data-pos-queue-search]').forEach(input=>{
  const rail=input.closest('.pos-queue-rail');
  const tickets=()=>[...(rail?.querySelectorAll('[data-pos-queue-ticket]')||[])];
  const apply=()=>{
    const term=input.value.trim().toLowerCase();
    tickets().forEach(ticket=>{ticket.hidden=term!==''&&!String(ticket.dataset.search||'').includes(term);});
  };
  input.addEventListener('input',apply);
  input.addEventListener('keydown',event=>{
    if(event.key==='Enter'){
      const first=tickets().find(ticket=>!ticket.hidden)?.querySelector('button[type="submit"],a');
      if(first){event.preventDefault();first.click();}
    }
  });
  document.addEventListener('dental:live-queue-updated',event=>{if(rail?.contains(event.detail?.region))apply();});
});

// v0.5.9.3 Cashier POS register
const posRegister=document.querySelector('[data-pos-register]');
if(posRegister){
  const cart=posRegister.querySelector('[data-pos-cart]');
  const empty=posRegister.querySelector('[data-pos-empty]');
  const subtotalEl=posRegister.querySelector('[data-pos-subtotal]');
  const discountAmountEl=posRegister.querySelector('[data-pos-discount-amount]');
  const totalEl=posRegister.querySelector('[data-pos-total]');
  const balanceEl=posRegister.querySelector('[data-pos-balance]');
  const paidEl=posRegister.querySelector('[data-pos-paid]');
  const changeEl=posRegister.querySelector('[data-pos-change]');
  const afterPaymentEl=posRegister.querySelector('[data-pos-after-payment]');
  const discountType=posRegister.querySelector('[data-pos-discount-type]');
  const discountValue=posRegister.querySelector('[data-pos-discount-value]');
  const discountAuthorization=posRegister.querySelector('[data-pos-discount-authorization]');
  const method=posRegister.querySelector('[data-pos-method]');
  const tendered=posRegister.querySelector('[data-pos-tendered]');
  const reference=posRegister.querySelector('[data-pos-reference]');
  const saveOnly=posRegister.querySelector('[data-pos-save-only]');
  const saveOnlyInitiallyDisabled=Boolean(saveOnly?.disabled);
  const locked=posRegister.dataset.posChargesLocked==='1';
  const money=value=>`₱${Number(value||0).toLocaleString('en-PH',{minimumFractionDigits:2,maximumFractionDigits:2})}`;
  const number=value=>Number.parseFloat(String(value??'').replaceAll(',',''))||0;

  const buildHidden=(name,value)=>{const input=document.createElement('input');input.type='hidden';input.name=name;input.value=String(value||'');return input;};
  const addItem=(data={})=>{
    if(locked)return;
    const sourceType=String(data.sourceType||'manual_charge');
    const canonical=['clinical_procedure','service_catalog','retail_product','clinical_supply','dispensed_medicine','equipment_service','diagnostic_service'].includes(sourceType);
    const clinical=sourceType==='clinical_procedure'||Number(data.usageId||0)>0;
    if(data.procedureId&&cart?.querySelector(`input[name="item_procedure_id[]"][value="${CSS.escape(String(data.procedureId))}"]`))return;
    if(data.usageId&&cart?.querySelector(`input[name="item_clinical_usage_id[]"][value="${CSS.escape(String(data.usageId))}"]`))return;
    if(data.inventoryId&&cart?.querySelector(`input[name="item_inventory_id[]"][value="${CSS.escape(String(data.inventoryId))}"]`))return;
    const row=document.createElement('tr');row.dataset.posRow='';row.dataset.sourceType=sourceType;
    const itemCell=document.createElement('td');
    const description=document.createElement('input');description.name='item_description[]';description.required=true;description.value=String(data.description||'');description.placeholder='Billable item';description.readOnly=canonical;
    itemCell.append(description,buildHidden('item_code[]',data.code),buildHidden('item_service_id[]',data.serviceId),buildHidden('item_treatment_plan_item_id[]',data.planId),buildHidden('item_procedure_id[]',data.procedureId),buildHidden('item_clinical_usage_id[]',data.usageId),buildHidden('item_inventory_id[]',data.inventoryId));
    const quantityCell=document.createElement('td');const quantity=document.createElement('input');quantity.type='number';quantity.name='item_quantity[]';quantity.min='0.01';quantity.max='999';quantity.step='0.01';quantity.value=String(data.quantity||1);quantity.dataset.posQuantity='';quantity.readOnly=clinical;quantityCell.append(quantity);
    const priceCell=document.createElement('td');const price=document.createElement('input');price.type='number';price.name='item_unit_price[]';price.min='0';price.step='0.01';price.value=Number(data.price||0).toFixed(2);price.dataset.posPrice='';price.readOnly=canonical;priceCell.append(price);
    const totalCell=document.createElement('td');const lineTotal=document.createElement('strong');lineTotal.dataset.posLineTotal='';lineTotal.textContent=money((data.quantity||1)*(data.price||0));totalCell.append(lineTotal);
    const removeCell=document.createElement('td');if(!clinical){const remove=document.createElement('button');remove.type='button';remove.className='pos-remove-item';remove.dataset.posRemove='';remove.setAttribute('aria-label','Remove item');remove.textContent='×';removeCell.append(remove);}
    row.append(itemCell,quantityCell,priceCell,totalCell,removeCell);cart?.append(row);bindRow(row);refresh();if(!canonical){description.focus();description.select();}
  };
  const bindRow=row=>{
    row.querySelectorAll('[data-pos-quantity],[data-pos-price],input[name="item_description[]"]').forEach(input=>input.addEventListener('input',refresh));
    row.querySelector('[data-pos-remove]')?.addEventListener('click',()=>{row.remove();refresh();});
  };
  const refresh=()=>{
    let subtotal=0;
    const rows=[...(cart?.querySelectorAll('[data-pos-row]')||[])];
    rows.forEach(row=>{const qty=Math.max(0,number(row.querySelector('[data-pos-quantity]')?.value));const price=Math.max(0,number(row.querySelector('[data-pos-price]')?.value));const line=Math.round(qty*price*100)/100;subtotal+=line;const target=row.querySelector('[data-pos-line-total]');if(target)target.textContent=money(line);});
    subtotal=Math.round(subtotal*100)/100;
    let discount=0;const dtype=discountType?.value||'none';const dvalue=Math.max(0,number(discountValue?.value));
    if(dtype==='percent')discount=Math.round(subtotal*Math.min(100,dvalue))/100;
    if(dtype==='fixed')discount=Math.min(subtotal,dvalue);
    const total=Math.max(0,Math.round((subtotal-discount)*100)/100);const paid=Math.max(0,number(paidEl?.dataset.value));const balance=Math.max(0,Math.round((total-paid)*100)/100);
    if(tendered&&document.activeElement!==tendered&&tendered.value==='')tendered.value=balance>0?balance.toFixed(2):'';
    const tender=Math.max(0,number(tendered?.value));const isCash=(method?.value||'cash')==='cash';const change=isCash?Math.max(0,Math.round((tender-balance)*100)/100):0;const applied=Math.min(balance,tender);const remaining=Math.max(0,Math.round((balance-applied)*100)/100);
    if(subtotalEl)subtotalEl.textContent=money(subtotal);if(discountAmountEl)discountAmountEl.textContent=`− ${money(discount)}`;if(totalEl)totalEl.textContent=money(total);if(balanceEl)balanceEl.textContent=money(balance);if(changeEl)changeEl.textContent=money(change);if(afterPaymentEl)afterPaymentEl.textContent=`Remaining balance ${money(remaining)}`;if(empty)empty.hidden=rows.length>0;
    const hasRetail=rows.some(row=>row.dataset.sourceType==='retail_product');if(saveOnly){saveOnly.disabled=saveOnlyInitiallyDisabled||hasRetail;saveOnly.title=hasRetail?'Retail products require full payment so stock can be deducted after payment succeeds.':'';}
    if(reference){
      reference.required=!isCash;reference.minLength=isCash?0:4;
      const labels={card:'Card terminal approval/reference code',gcash:'GCash reference number',maya:'Maya reference number','e-wallet':'Other e-wallet reference code',bank_transfer:'Bank transfer reference code',cheque:'Cheque/reference number',mixed:'Mixed-payment reference/breakdown code',other:'External digital transaction reference code'};
      reference.placeholder=isCash?'Optional receipt reference':(labels[method?.value]||'Required external transaction reference');
    }
  };
  cart?.querySelectorAll('[data-pos-row]').forEach(bindRow);
  document.querySelectorAll('[data-pos-add-item]').forEach(button=>button.addEventListener('click',()=>addItem({description:button.dataset.description,price:number(button.dataset.price),code:button.dataset.code,serviceId:button.dataset.serviceId,planId:button.dataset.planId,procedureId:button.dataset.procedureId,usageId:button.dataset.clinicalUsageId,inventoryId:button.dataset.inventoryId,quantity:number(button.dataset.quantity||1),sourceType:button.dataset.sourceType})));
  posRegister.querySelector('[data-pos-add-custom]')?.addEventListener('click',()=>{const description=posRegister.querySelector('[data-pos-custom-description]');const price=posRegister.querySelector('[data-pos-custom-price]');if(!description?.value.trim()){description?.focus();return;}addItem({description:description.value.trim(),price:number(price?.value),sourceType:'manual_charge'});description.value='';if(price)price.value='';});
  discountAuthorization?.addEventListener('change',()=>{const option=discountAuthorization.selectedOptions[0];if(discountType)discountType.value=option?.dataset.type||'none';if(discountValue)discountValue.value=option?.dataset.value||'0';refresh();});
  [discountType,discountValue,method,tendered].forEach(control=>control?.addEventListener('input',refresh));
  posRegister.addEventListener('keydown',event=>{if(event.key==='Enter'&&event.target?.matches('[data-pos-custom-description],[data-pos-custom-price]')){event.preventDefault();posRegister.querySelector('[data-pos-add-custom]')?.click();}});
  posRegister.addEventListener('submit',event=>{
    if((cart?.querySelectorAll('[data-pos-row]').length||0)===0){event.preventDefault();empty?.removeAttribute('hidden');posRegister.querySelector('[data-pos-custom-description]')?.focus();return;}
    const submitter=event.submitter;
    const wantsPayment=submitter&&submitter.name==='checkout_mode'&&submitter.value==='pay';
    const wantsSaveOnly=submitter&&submitter.name==='checkout_mode'&&submitter.value==='save';
    const hasRetail=[...(cart?.querySelectorAll('[data-pos-row]')||[])].some(row=>row.dataset.sourceType==='retail_product');
    if(wantsSaveOnly&&hasRetail){event.preventDefault();saveOnly?.focus();return;}
    const methodValue=method?.value||'cash';
    const needsReference=methodValue!=='cash';
    if(wantsPayment&&needsReference&&reference&&reference.value.trim().length<4){
      event.preventDefault();
      reference.required=true;
      reference.focus();
      reference.reportValidity?.();
    }
  });
  refresh();
}

// v0.5.9.8.2 transient notices close automatically after the configured delay.
document.querySelectorAll('[data-auto-dismiss]').forEach(notice=>{
  const delay=Math.max(1000,Number(notice.dataset.autoDismiss||10000));
  window.setTimeout(()=>{
    notice.classList.add('is-dismissing');
    window.setTimeout(()=>notice.remove(),240);
  },delay);
});

document.querySelectorAll('[data-privilege-manager]').forEach(manager=>{
  const groups=[...manager.querySelectorAll('[data-privilege-group]')];
  const update=()=>{
    let total=0;
    groups.forEach(group=>{
      const boxes=[...group.querySelectorAll('input[name="privileges[]"]')];
      const count=boxes.filter(box=>box.checked).length;total+=count;
      const host=group.querySelector('[data-group-count]');if(host)host.textContent=String(count);
    });
    const totalHost=manager.querySelector('[data-privilege-total]');if(totalHost)totalHost.textContent=String(total);
  };
  groups.forEach(group=>{
    const boxes=[...group.querySelectorAll('input[name="privileges[]"]')];
    boxes.forEach(box=>box.addEventListener('change',update));
    group.querySelectorAll('[data-group-select]').forEach(button=>button.addEventListener('click',()=>{
      const checked=button.dataset.groupSelect==='all';
      boxes.forEach(box=>{if(!box.disabled)box.checked=checked;});update();
    }));
  });
  update();
});


// Consent decisions are keyboard-first and cannot be submitted with hidden signer gaps.
document.querySelectorAll('[data-consent-transaction]').forEach(form=>{
  const status=form.querySelector('[data-consent-status]');
  const signerType=form.querySelector('[data-consent-signer-type]');
  const patient=form.querySelector('#consent-patient-select');
  const guardian=form.querySelector('[data-consent-guardian]');
  const guardianField=form.querySelector('[data-guardian-field]');
  const signerName=form.querySelector('[data-consent-signer-name]');
  const relationship=form.querySelector('[data-consent-signer-relationship]');
  const identity=form.querySelector('[data-consent-identity]');
  const identityOther=form.querySelector('[data-consent-identity-other]');
  const identityOtherField=form.querySelector('[data-identity-other-field]');
  const signatureMethod=form.querySelector('[data-consent-signature-method]');
  const acknowledged=form.querySelector('[data-consent-acknowledged]');
  const questions=form.querySelector('[data-consent-questions]');
  const declineField=form.querySelector('[data-decline-field]');
  const decline=form.querySelector('[name="decline_reason"]');
  const signedRequired=[...form.querySelectorAll('[data-consent-signed-required]')];
  const notice=form.querySelector('[data-consent-required-notice]');
  const noticeCopy=form.querySelector('[data-consent-required-copy]');
  const submitStatus=form.querySelector('[data-consent-submit-status]');
  const returnMode=form.querySelector('[name="return"]')?.value||'';

  const selectedPatientName=()=>patient?.selectedOptions?.[0]?.dataset.patientName||'';
  if(signerName?.value.trim()&&signerName.value.trim()===selectedPatientName())signerName.dataset.autofilled='1';
  if(relationship?.value==='Self')relationship.dataset.autofilled='1';
  const fillSigner=()=>{
    const type=signerType?.value||'patient';
    if(type==='patient'){
      if(signerName&&(!signerName.value.trim()||signerName.dataset.autofilled==='1')){signerName.value=selectedPatientName();signerName.dataset.autofilled='1';}
      if(relationship&&(!relationship.value.trim()||relationship.dataset.autofilled==='1')){relationship.value='Self';relationship.dataset.autofilled='1';}
      if(returnMode==='clinical'&&identity&&(!identity.value||identity.dataset.autofilled==='1')){identity.value='Clinic patient record and date of birth confirmed';identity.dataset.autofilled='1';}
    }
    if(type==='guardian'){
      const option=guardian?.selectedOptions?.[0];
      if(option?.value){
        if(signerName){signerName.value=option.dataset.guardianName||'';signerName.dataset.autofilled='1';}
        if(relationship){relationship.value=option.dataset.guardianRelationship||'Guardian';relationship.dataset.autofilled='1';}
        if(identity&&(!identity.value||identity.dataset.autofilled==='1')){identity.value='Verified guardian record and in-person identity confirmation';identity.dataset.autofilled='1';}
      }
    }
  };
  const refresh=()=>{
    const decision=status?.value||'draft';
    const signed=decision==='signed';
    const declined=decision==='declined';
    const decisionFinal=signed||declined;
    const type=signerType?.value||'patient';
    if(guardianField)guardianField.hidden=type!=='guardian';
    if(guardian)guardian.required=decisionFinal&&type==='guardian';
    fillSigner();
    [signerName,identity].forEach(field=>{if(field)field.required=decisionFinal;});
    if(signatureMethod)signatureMethod.required=signed;
    if(acknowledged)acknowledged.required=signed;
    if(questions)questions.required=signed;
    signedRequired.forEach(field=>field.required=signed);
    if(declineField)declineField.hidden=!declined;
    if(decline)decline.required=declined;
    if(identityOtherField)identityOtherField.hidden=identity?.value!=='other';
    if(identityOther)identityOther.required=decisionFinal&&identity?.value==='other';
    if(notice)notice.classList.toggle('ready',!decisionFinal||Boolean(signerName?.value.trim()&&identity?.value));
    if(noticeCopy)noticeCopy.textContent=!decisionFinal?'Draft or requested consent does not require a signature yet.':signed?'Confirm signer identity, questions answered, acknowledgment, risks, alternatives, and refusal consequences.':'Confirm signer identity and record the refusal statement.';
    if(submitStatus)submitStatus.textContent=decisionFinal?'Signer verification is required before saving':'Draft may be saved without signature evidence';
  };
  [status,signerType,patient,guardian,signatureMethod,acknowledged,questions].forEach(field=>field?.addEventListener('change',refresh));
  identity?.addEventListener('change',()=>{identity.dataset.autofilled='0';refresh();});
  signerName?.addEventListener('input',()=>{signerName.dataset.autofilled='0';refresh();});
  relationship?.addEventListener('input',()=>{relationship.dataset.autofilled='0';refresh();});
  form.addEventListener('submit',event=>{
    refresh();
    const invalid=[...form.querySelectorAll(':invalid')].filter(dentalFieldIsVisible)[0];
    if(invalid){
      event.preventDefault();
      invalid.focus({preventScroll:true});
      dentalScrollTargetIntoView(invalid, { force: true, behavior: 'smooth' });
      invalid.reportValidity();
      if(submitStatus)submitStatus.textContent='Complete the highlighted required item';
    }
  });
  refresh();
  if(form.closest('dialog[open]'))requestAnimationFrame(()=>dentalFocusFirstRequired(form));
});

// v0.5.9.8.14 future-visit preview. Reception can see existing bookings
// before creating another appointment or a treatment series.
document.querySelectorAll('[data-patient-upcoming]').forEach(panel=>{
  const form=panel.closest('form');
  const picker=form?.querySelector('[data-patient-picker]');
  const hidden=form?.querySelector('[data-patient-id]');
  const endpoint=panel.dataset.endpoint||'api/patient_upcoming_appointments.php';
  let controller=null;
  const render=(appointments,message='')=>{
    panel.replaceChildren();
    const title=document.createElement('strong');title.textContent='Future visits for this patient';panel.append(title);
    if(!appointments.length){const empty=document.createElement('p');empty.className='muted';empty.textContent=message||'No future appointments are currently booked.';panel.append(empty);return;}
    const list=document.createElement('div');list.className='patient-upcoming-list';
    appointments.forEach(item=>{
      const row=document.createElement('article');row.className='patient-upcoming-item';
      const head=document.createElement('div');
      const when=document.createElement('strong');when.textContent=new Date(String(item.start_at||'').replace(' ','T')).toLocaleString('en-PH',{dateStyle:'medium',timeStyle:'short'});
      const status=document.createElement('span');status.className='badge';status.textContent=String(item.status||'').replaceAll('_',' ');
      head.append(when,status);
      const service=document.createElement('p');service.textContent=item.service_name||'Dental appointment';
      const meta=document.createElement('small');meta.textContent=[item.series_name||item.visit_label||'',item.provider||''].filter(Boolean).join(' · ');
      const link=document.createElement('a');link.href=item.edit_url||'#';link.textContent='Review / reschedule';
      row.append(head,service,meta,link);list.append(row);
    });
    panel.append(list);
  };
  const load=async patientId=>{
    if(!patientId){render([],'Select a patient to review existing future appointments before adding another.');return;}
    controller?.abort();controller=new AbortController();
    render([],'Loading future appointments…');
    try{
      const url=new URL(endpoint,window.location.href);url.searchParams.set('patient_id',String(patientId));
      const response=await fetch(url,{headers:{Accept:'application/json'},credentials:'same-origin',signal:controller.signal});
      const data=await response.json();
      if(!response.ok||!data.ok)throw new Error(data.message||'Future appointments could not be loaded.');
      render(data.appointments||[],data.message||'');
    }catch(error){if(error.name==='AbortError')return;render([],error.message||'Future appointments could not be loaded.');}
  };
  picker?.addEventListener('patient-selected',event=>load(Number(event.detail?.id||0)));
  picker?.addEventListener('patient-cleared',()=>load(0));
  if(Number(hidden?.value||0)>0)load(Number(hidden.value));else load(0);
});

// Clarify the difference between services inside one visit and repeated visits.
document.querySelectorAll('[data-series-count]').forEach(input=>{
  const fieldset=input.closest('.appointment-series-fields');
  const sync=()=>{
    const count=Math.max(1,Number(input.value||1));
    fieldset?.classList.toggle('series-active',count>1);
    const note=fieldset?.querySelector('small');
    if(note)note.textContent=count>1
      ? `${count} separate future appointments will be created. Each visit remains independently reschedulable and receives its own queue visit and billing record when attended.`
      : 'One appointment will be created. Additional selected services remain inside this same visit.';
  };
  input.addEventListener('input',sync);sync();
});

// v0.5.9.8.14 custom future visit-plan builder.
document.querySelectorAll('[data-appointment-plan]').forEach(form=>{
  const rows=form.querySelector('[data-plan-rows]');
  const template=form.querySelector('[data-plan-template]');
  const renumber=()=>[...(rows?.querySelectorAll('[data-plan-row]')||[])].forEach((row,index)=>{const number=row.querySelector('.plan-row-number');if(number)number.textContent=`Visit ${index+1}`;row.querySelector('[data-plan-remove]')?.toggleAttribute('disabled',(rows?.querySelectorAll('[data-plan-row]').length||0)<=2);});
  const bind=row=>row.querySelector('[data-plan-remove]')?.addEventListener('click',()=>{if((rows?.querySelectorAll('[data-plan-row]').length||0)<=2)return;row.remove();renumber();});
  rows?.querySelectorAll('[data-plan-row]').forEach(bind);
  form.querySelector('[data-plan-add]')?.addEventListener('click',()=>{
    if((rows?.querySelectorAll('[data-plan-row]').length||0)>=12)return;
    const fragment=template?.content.cloneNode(true);const row=fragment?.querySelector?.('[data-plan-row]');if(!row||!rows)return;rows.append(row);bind(row);renumber();row.querySelector('select')?.focus();
  });
  form.addEventListener('submit',event=>{if((rows?.querySelectorAll('[data-plan-row]').length||0)<2){event.preventDefault();window.alert('Add at least two future visits to create a visit plan.');}});
  renumber();
});

// v0.5.9.8.18 shared bulk-selection controls for scalable management tables.
document.querySelectorAll('[data-select-all]').forEach(master=>{
  const form=master.closest('form')||document;
  const target=master.dataset.selectAll||'input[data-row-select]';
  const rows=()=>Array.from(form.querySelectorAll(target)).filter(el=>!el.disabled);
  const refresh=()=>{
    const selected=rows().filter(el=>el.checked).length;
    const total=rows().length;
    master.checked=total>0&&selected===total;
    master.indeterminate=selected>0&&selected<total;
    form.querySelectorAll('[data-selected-count]').forEach(el=>el.textContent=String(selected));
    form.querySelectorAll('[data-requires-selection]').forEach(el=>el.disabled=selected===0);
    form.querySelectorAll('[data-bulk-bar]').forEach(el=>el.hidden=selected===0);
  };
  master.addEventListener('change',()=>{rows().forEach(el=>{el.checked=master.checked;});refresh();});
  rows().forEach(el=>el.addEventListener('change',refresh));
  refresh();
});

// v0.5.9.8.18 scalable operational lookup. Large inventory and sterilization
// catalogs are searched on demand instead of repeated in every table row.
document.querySelectorAll('[data-record-picker]').forEach(picker=>{
  const query=picker.querySelector('[data-record-query]');
  const hidden=picker.querySelector('[data-record-id]');
  const results=picker.querySelector('[data-record-results]');
  const selected=picker.querySelector('[data-record-selection]');
  const endpoint=picker.dataset.endpoint||'api/management_lookup.php';
  if(!query||!hidden||!results)return;
  let timer=0,controller=null;
  const clearResults=()=>{results.replaceChildren();results.hidden=true;};
  const choose=item=>{
    hidden.value=String(item.id||'');
    query.value=String(item.label||item.name||'');
    query.setCustomValidity('');
    if(selected){selected.textContent=`Selected: ${item.label||item.name||''}`;selected.hidden=false;}
    clearResults();
    hidden.dispatchEvent(new Event('change',{bubbles:true}));
    picker.dispatchEvent(new CustomEvent('record-selected',{bubbles:true,detail:item}));
  };
  const render=(items,message='')=>{
    results.replaceChildren();
    if(!items.length){const empty=document.createElement('div');empty.className='remote-picker-empty';empty.textContent=message||'No matching record found.';results.append(empty);results.hidden=false;return;}
    items.forEach(item=>{
      const button=document.createElement('button');button.type='button';button.className='remote-picker-option';button.innerHTML='<strong></strong><small></small>';
      button.querySelector('strong').textContent=item.label||item.name||'';
      button.querySelector('small').textContent=item.meta||'';
      button.addEventListener('click',()=>choose(item));results.append(button);
    });
    results.hidden=false;
  };
  const search=async()=>{
    const term=query.value.trim();hidden.value='';
    if(selected){selected.textContent='';selected.hidden=true;}
    if(term.length<2){clearResults();return;}
    controller?.abort();controller=new AbortController();results.hidden=false;results.textContent='Searching…';
    try{
      const url=new URL(endpoint,window.location.href);url.searchParams.set('q',term);
      const response=await fetch(url,{headers:{Accept:'application/json'},credentials:'same-origin',signal:controller.signal});
      const raw=await response.text();let data=null;
      try{data=raw?JSON.parse(raw):null;}catch(parseError){throw new Error(`Lookup returned an invalid response${response.status?` (HTTP ${response.status})`:''}.`);}
      if(!response.ok||!data||!data.ok)throw new Error(data?.message||`Lookup failed (HTTP ${response.status}).`);
      render(data.results||[],data.message||'');
    }catch(error){if(error.name==='AbortError')return;render([],error.message||'Lookup failed.');}
  };
  query.addEventListener('input',()=>{hidden.value='';if(selected){selected.textContent='';selected.hidden=true;}window.clearTimeout(timer);timer=window.setTimeout(search,250);});
  query.addEventListener('focus',()=>{if(query.value.trim().length>=2&&!hidden.value)search();});
  query.addEventListener('keydown',event=>{if(event.key==='Escape')clearResults();});
  document.addEventListener('click',event=>{if(!picker.contains(event.target))clearResults();});
  picker.closest('form')?.addEventListener('submit',event=>{if(hidden.value||picker.dataset.optional==='true')return;event.preventDefault();query.setCustomValidity('Search and select a record from the results.');query.reportValidity();query.focus();});
});

document.querySelectorAll('[data-multi-record-picker]').forEach(picker=>{
  const query=picker.querySelector('[data-multi-record-query]');
  const results=picker.querySelector('[data-multi-record-results]');
  const selection=picker.querySelector('[data-multi-record-selection]');
  const inputs=picker.querySelector('[data-multi-record-inputs]');
  const endpoint=picker.dataset.endpoint||'api/management_lookup.php';
  const inputName=picker.dataset.inputName||'record_ids[]';
  if(!query||!results||!selection||!inputs)return;
  const chosen=new Map();let timer=0,controller=null;
  const clearResults=()=>{results.replaceChildren();results.hidden=true;};
  const sync=()=>{
    selection.replaceChildren();inputs.replaceChildren();
    if(!chosen.size){const empty=document.createElement('span');empty.className='muted';empty.textContent='No records selected.';selection.append(empty);return;}
    chosen.forEach((item,id)=>{
      const chip=document.createElement('span');chip.className='remote-multi-chip';
      const text=document.createElement('span');text.textContent=item.label||item.name||String(id);
      const remove=document.createElement('button');remove.type='button';remove.textContent='×';remove.setAttribute('aria-label',`Remove ${text.textContent}`);remove.addEventListener('click',()=>{chosen.delete(id);sync();});
      chip.append(text,remove);selection.append(chip);
      const hidden=document.createElement('input');hidden.type='hidden';hidden.name=inputName;hidden.value=String(id);inputs.append(hidden);
    });
  };
  const choose=item=>{const id=String(item.id||'');if(!id)return;chosen.set(id,item);query.value='';query.setCustomValidity('');clearResults();sync();query.focus();};
  const render=(items,message='')=>{
    results.replaceChildren();
    const available=items.filter(item=>!chosen.has(String(item.id||'')));
    if(!available.length){const empty=document.createElement('div');empty.className='remote-picker-empty';empty.textContent=message||'No additional matching records.';results.append(empty);results.hidden=false;return;}
    available.forEach(item=>{const button=document.createElement('button');button.type='button';button.className='remote-picker-option';button.innerHTML='<strong></strong><small></small>';button.querySelector('strong').textContent=item.label||item.name||'';button.querySelector('small').textContent=item.meta||'';button.addEventListener('click',()=>choose(item));results.append(button);});results.hidden=false;
  };
  const search=async()=>{
    const term=query.value.trim();if(term.length<2){clearResults();return;}controller?.abort();controller=new AbortController();results.hidden=false;results.textContent='Searching…';
    try{const url=new URL(endpoint,window.location.href);url.searchParams.set('q',term);const response=await fetch(url,{headers:{Accept:'application/json'},credentials:'same-origin',signal:controller.signal});const raw=await response.text();let data=null;try{data=raw?JSON.parse(raw):null;}catch(e){throw new Error(`Lookup returned an invalid response (HTTP ${response.status}).`);}if(!response.ok||!data?.ok)throw new Error(data?.message||`Lookup failed (HTTP ${response.status}).`);render(data.results||[],data.message||'');}catch(error){if(error.name==='AbortError')return;render([],error.message||'Lookup failed.');}
  };
  query.addEventListener('input',()=>{window.clearTimeout(timer);timer=window.setTimeout(search,250);});query.addEventListener('focus',()=>{if(query.value.trim().length>=2)search();});query.addEventListener('keydown',event=>{if(event.key==='Escape')clearResults();});document.addEventListener('click',event=>{if(!picker.contains(event.target))clearResults();});
  picker.closest('form')?.addEventListener('submit',event=>{if(picker.dataset.required!=='true'||chosen.size)return;event.preventDefault();query.setCustomValidity('Search and add at least one record.');query.reportValidity();query.focus();});sync();
});

document.querySelectorAll('[data-critical-operations]').forEach(section=>{
  const groups=[...section.querySelectorAll('[data-critical-group]')];
  const setAll=open=>groups.forEach(group=>{group.open=open;});
  section.querySelector('[data-critical-expand-all]')?.addEventListener('click',()=>setAll(true));
  section.querySelector('[data-critical-collapse-all]')?.addEventListener('click',()=>setAll(false));
});


// DentalCare 0.5.9.8.32: appointment integrity, confirmation evidence, add/drop services, and audited dispositions.
document.querySelectorAll('form[data-protect-unsaved],dialog form[method="post"]').forEach(form=>{
  form.setAttribute('data-protect-unsaved','');
  form.dataset.dirty='0';
  const markDirty=event=>{
    if(event.target.matches('[type="hidden"],[data-ignore-dirty]'))return;
    form.dataset.dirty='1';
    form.closest('dialog')?.classList.add('has-unsaved-changes');
  };
  form.addEventListener('input',markDirty);
  form.addEventListener('change',markDirty);
  form.addEventListener('submit',()=>{form.dataset.dirty='0';form.closest('dialog')?.classList.remove('has-unsaved-changes');});
  form.addEventListener('reset',()=>{requestAnimationFrame(()=>{form.dataset.dirty='0';form.closest('dialog')?.classList.remove('has-unsaved-changes');});});
});

window.addEventListener('beforeunload',event=>{
  const dirty=[...document.querySelectorAll('form[data-protect-unsaved][data-dirty="1"]')].some(form=>dentalFieldIsVisible(form)||form.closest('dialog[open]'));
  if(!dirty)return;
  event.preventDefault();
  event.returnValue='';
});

document.addEventListener('keydown',event=>{
  if(!(event.ctrlKey||event.metaKey)||event.key.toLowerCase()!=='s')return;
  const form=event.target?.closest?.('form[data-keyboard-save],dialog form[method="post"]')||document.querySelector('dialog[open] form[method="post"]');
  if(!form)return;
  event.preventDefault();
  const submit=[...form.querySelectorAll('button[type="submit"],input[type="submit"],button:not([type])')].find(dentalFieldIsVisible);
  if(submit)form.requestSubmit(submit);else form.requestSubmit();
});

document.querySelectorAll('.table-wrap').forEach(wrapper=>{
  if(!wrapper.hasAttribute('tabindex'))wrapper.tabIndex=0;
  if(!wrapper.hasAttribute('role'))wrapper.setAttribute('role','region');
  if(!wrapper.hasAttribute('aria-label')){
    const heading=wrapper.closest('.card')?.querySelector('h2,h3')?.textContent?.trim();
    wrapper.setAttribute('aria-label',heading?`${heading} table`:'Scrollable table');
  }
});

const selectedWeekDay=document.querySelector('[data-week-day].selected-date');
if(selectedWeekDay){
  requestAnimationFrame(()=>selectedWeekDay.scrollIntoView({behavior:'smooth',block:'nearest',inline:'center'}));
}

document.querySelectorAll('form').forEach(form=>form.addEventListener('submit',event=>{
  const invalid=[...form.querySelectorAll(':invalid')].find(dentalFieldIsVisible);
  if(!invalid)return;
  event.preventDefault();
  invalid.setAttribute('aria-invalid','true');
  invalid.focus({preventScroll:true});
  dentalScrollTargetIntoView(invalid, { force: true, behavior: 'smooth' });
  invalid.reportValidity();
}));


// DentalCare 0.5.9.8.102: online-request approval should not ask for existing-patient identity proof when creating a new patient.
document.querySelectorAll('form.schedule-request-form').forEach(form=>{
  const wrap=form.querySelector('[data-identity-verified-wrap]');
  if(!wrap)return;
  const checkbox=wrap.querySelector('input[type="checkbox"]');
  const sync=()=>{
    const selected=form.querySelector('[name="patient_resolution"]:checked')?.value||'';
    const needsProof=selected==='linked'||selected==='existing';
    wrap.hidden=!needsProof;
    if(checkbox){checkbox.disabled=!needsProof;if(!needsProof)checkbox.checked=false;}
  };
  form.querySelectorAll('[name="patient_resolution"]').forEach(input=>input.addEventListener('change',sync));
  sync();
});

// DentalCare 0.5.9.8.32: explicit appointment confirmation evidence.
document.querySelectorAll('form[data-confirmation-form]').forEach(form=>{
  const status=form.querySelector('[data-confirmation-status]');
  const fields=form.querySelector('[data-confirmation-fields]');
  const submit=form.querySelector('[data-status-submit]');
  const sync=()=>{
    const confirmed=status?.value==='confirmed';
    if(fields){fields.hidden=!confirmed;fields.querySelectorAll('select,textarea,input').forEach(field=>{field.required=confirmed;field.disabled=!confirmed;});}
    if(submit)submit.textContent=confirmed?(submit.dataset.confirmedLabel||'Create confirmed appointment'):(submit.dataset.pendingLabel||'Create pending appointment');
  };
  status?.addEventListener('change',sync);sync();
});

// Public urgent-request guidance.
document.querySelectorAll('[data-urgency-select]').forEach(select=>{
  const form=select.closest('form');
  const notice=form?.querySelector('[data-urgent-notice]');
  const reason=form?.querySelector('[name="reason"]');
  const sync=()=>{const urgent=select.value==='urgent';if(notice)notice.hidden=!urgent;if(reason){reason.minLength=urgent?20:0;reason.placeholder=urgent?'Describe the urgent concern, symptoms, when it started, and any swelling, bleeding, trauma, or severe pain.':reason.placeholder;}};
  select.addEventListener('change',sync);sync();
});

// Add/drop service controls while retaining the original multi-select for submission.
document.querySelectorAll('select[data-slot-additional-services]').forEach(select=>{
  if(select.dataset.addDropReady==='1')return;
  select.dataset.addDropReady='1';
  select.hidden=true;
  const builder=document.createElement('div');builder.className='service-add-drop-builder';
  const controls=document.createElement('div');controls.className='service-add-drop-controls';
  const picker=document.createElement('select');picker.setAttribute('aria-label','Additional service to add');
  const add=document.createElement('button');add.type='button';add.className='btn ghost compact';add.textContent='Add service';
  const chips=document.createElement('div');chips.className='service-add-drop-chips';chips.setAttribute('aria-live','polite');
  controls.append(picker,add);builder.append(controls,chips);select.insertAdjacentElement('afterend',builder);
  const slotPicker=select.closest('[data-slot-picker]');
  const primaryService=slotPicker?.querySelector('[data-slot-service]')||null;
  const removePrimaryDuplicate=()=>{
    if(!primaryService?.value)return false;
    let changed=false;
    [...select.options].forEach(option=>{if(option.selected&&option.value===primaryService.value){option.selected=false;changed=true;}});
    return changed;
  };
  const refresh=()=>{
    removePrimaryDuplicate();
    picker.replaceChildren(new Option('Choose an additional service',''));
    [...select.options].filter(option=>!option.selected&&option.value!==primaryService?.value).forEach(option=>picker.add(new Option(option.textContent,option.value)));
    chips.replaceChildren();
    const selected=[...select.selectedOptions];
    if(!selected.length){const empty=document.createElement('span');empty.className='muted';empty.textContent='No additional service added.';chips.append(empty);}
    selected.forEach(option=>{const chip=document.createElement('span');chip.className='service-add-drop-chip';const text=document.createElement('span');text.textContent=option.textContent;const remove=document.createElement('button');remove.type='button';remove.setAttribute('aria-label',`Remove ${option.textContent}`);remove.textContent='×';remove.addEventListener('click',()=>{option.selected=false;select.dispatchEvent(new Event('change',{bubbles:true}));refresh();});chip.append(text,remove);chips.append(chip);});
  };
  add.addEventListener('click',()=>{if(!picker.value||picker.value===primaryService?.value)return;const option=[...select.options].find(item=>item.value===picker.value);if(option){option.selected=true;select.dispatchEvent(new Event('change',{bubbles:true}));refresh();}});
  primaryService?.addEventListener('change',()=>{const changed=removePrimaryDuplicate();refresh();if(changed)select.dispatchEvent(new Event('change',{bubbles:true}));});
  refresh();
});

// Future visit-plan rows use validated available slots instead of free-form times.
document.querySelectorAll('[data-appointment-plan]').forEach(form=>{
  const branchId=form.dataset.branchId||'';
  const patientId=()=>form.querySelector('[data-patient-id]')?.value||'';
  const bindAvailability=row=>{
    if(row.dataset.slotReady==='1')return;row.dataset.slotReady='1';
    const service=row.querySelector('[data-plan-service]');const doctor=row.querySelector('[data-plan-doctor]');const date=row.querySelector('[data-plan-date]');const time=row.querySelector('[data-plan-time]');
    let controller=null;
    const refresh=async()=>{
      if(!service?.value||!date?.value||!time)return;
      controller?.abort();controller=new AbortController();
      time.disabled=true;time.replaceChildren(new Option('Checking available times…',''));
      try{
        const url=new URL('api/available_slots.php',window.location.href);url.searchParams.set('branch_id',branchId);url.searchParams.set('service_id',service.value);url.searchParams.set('date',date.value);if(doctor?.value)url.searchParams.set('doctor_id',doctor.value);if(patientId())url.searchParams.set('patient_id',patientId());
        const response=await fetch(url,{headers:{Accept:'application/json'},credentials:'same-origin',signal:controller.signal});const data=await response.json();if(!response.ok||!data.ok)throw new Error(data.message||'Available times could not be loaded.');
        time.replaceChildren(new Option(data.slots?.length?'Choose an available time':'No available time',''));
        (data.slots||[]).forEach(slot=>{if(doctor?.value&&Number(slot.doctor_id||0)>0&&String(slot.doctor_id)!==String(doctor.value))return;const option=new Option(slot.label||slot.time,slot.time);option.dataset.doctorId=String(slot.doctor_id||'');time.add(option);});
        time.disabled=time.options.length<=1;
      }catch(error){if(error.name==='AbortError')return;time.replaceChildren(new Option(error.message||'No available time',''));time.disabled=true;}
    };
    time?.addEventListener('change',()=>{
      const selectedDoctorId=String(time.selectedOptions?.[0]?.dataset.doctorId||'');
      if(!doctor||doctor.value||!selectedDoctorId)return;
      if([...doctor.options].some(option=>String(option.value)===selectedDoctorId))doctor.value=selectedDoctorId;
    });
    [service,doctor,date].filter(Boolean).forEach(field=>field.addEventListener('change',refresh));
    if(service?.value&&date?.value)refresh();
  };
  form.querySelectorAll('[data-plan-row]').forEach(bindAvailability);
  const rows=form.querySelector('[data-plan-rows]');
  if(rows)new MutationObserver(records=>records.forEach(record=>record.addedNodes.forEach(node=>{if(!(node instanceof Element))return;const row=node.matches('[data-plan-row]')?node:node.querySelector('[data-plan-row]');if(row)bindAvailability(row);}))).observe(rows,{childList:true});
});

document.querySelectorAll('[data-service-disposition-action]').forEach(select=>{
  const form=select.closest('form');const destination=form?.querySelector('[data-service-destination]');const destinationSelect=destination?.querySelector('select');
  const sync=()=>{const show=select.value==='move_existing';if(destination)destination.hidden=!show;if(destinationSelect){destinationSelect.required=show;destinationSelect.disabled=!show;}};
  select.addEventListener('change',sync);sync();
});


(() => {
  // v0.5.9.8.89: in-app browser fullscreen forcing removed.
  // True fullscreen/F11 cannot be triggered automatically by a website.
  // Use Chrome/Edge kiosk launch mode for clinic stations that must stay fullscreen.
  try {
    localStorage.removeItem('dentalcare-fullscreen-policy');
    sessionStorage.removeItem('dentalcare-post-login-fullscreen');
    sessionStorage.setItem('dentalcare-fullscreen-restore-dismissed', '1');
  } catch (e) {}
})();


(() => {
  const pagers = Array.from(document.querySelectorAll('[data-system-setup-paginate]'));
  if (!pagers.length) return;

  const pageSizes = [10, 20, 50];

  const buildPager = (container) => {
    if (container.dataset.systemSetupPagerReady === '1') return;
    const selector = container.dataset.systemSetupItemSelector || 'tbody > tr';
    const itemRoot = selector.includes('tbody') ? (container.querySelector('tbody') || container) : container;
    let items = Array.from(container.querySelectorAll(selector)).filter((item) => !item.closest('[data-no-pagination]'));
    if (items.length <= 10) return;

    container.dataset.systemSetupPagerReady = '1';
    let page = 1;
    let perPage = Number(container.dataset.systemSetupDefaultSize || 10);
    if (!pageSizes.includes(perPage)) perPage = 10;

    const controls = document.createElement('nav');
    controls.className = 'server-pagination system-setup-client-pagination';
    controls.setAttribute('aria-label', 'System setup section pages');
    controls.innerHTML = `
      <div class="server-pagination-summary" data-page-summary></div>
      <div class="server-pagination-controls">
        <button class="pagination-number" type="button" data-page-prev>‹</button>
        <span class="server-page-size"><label>Show <select data-page-size>${pageSizes.map((size) => `<option value="${size}"${size === perPage ? ' selected' : ''}>${size}</option>`).join('')}</select></label></span>
        <span data-page-numbers></span>
        <button class="pagination-number" type="button" data-page-next>›</button>
      </div>`;

    const head = container.querySelector('.card-head') || container.querySelector('h2') || container.firstElementChild;
    if (head && head.parentNode === container) head.insertAdjacentElement('afterend', controls);
    else container.insertBefore(controls, container.firstChild);

    const summary = controls.querySelector('[data-page-summary]');
    const sizeSelect = controls.querySelector('[data-page-size]');
    const numberWrap = controls.querySelector('[data-page-numbers]');
    const prev = controls.querySelector('[data-page-prev]');
    const next = controls.querySelector('[data-page-next]');

    const syncPermissionHeaders = () => {
      if (!container.classList.contains('permission-matrix')) return;
      Array.from(container.querySelectorAll('h3')).forEach((header) => { header.hidden = true; });
      Array.from(container.querySelectorAll('.permission-row')).forEach((row) => {
        if (row.hidden) return;
        let node = row.previousElementSibling;
        while (node && !node.matches('h3')) node = node.previousElementSibling;
        if (node) node.hidden = false;
      });
    };

    const render = () => {
      items = Array.from(container.querySelectorAll(selector)).filter((item) => !item.closest('[data-no-pagination]'));
      const total = items.length;
      const pages = Math.max(1, Math.ceil(total / perPage));
      page = Math.min(Math.max(1, page), pages);
      const start = (page - 1) * perPage;
      const end = Math.min(total, start + perPage);

      items.forEach((item, index) => { item.hidden = !(index >= start && index < end); });
      syncPermissionHeaders();

      if (summary) summary.innerHTML = `<strong>${total}</strong> items · showing ${start + 1}–${end} · page ${page} of ${pages}`;
      if (prev) prev.disabled = page <= 1;
      if (next) next.disabled = page >= pages;
      if (numberWrap) {
        const low = Math.max(1, page - 2);
        const high = Math.min(pages, page + 2);
        numberWrap.innerHTML = '';
        for (let i = low; i <= high; i += 1) {
          const button = document.createElement('button');
          button.type = 'button';
          button.className = `pagination-number${i === page ? ' active' : ''}`;
          button.textContent = String(i);
          button.setAttribute('aria-current', i === page ? 'page' : 'false');
          button.addEventListener('click', () => { page = i; render(); });
          numberWrap.appendChild(button);
        }
      }
      controls.hidden = total <= perPage && perPage === 50;
    };

    prev?.addEventListener('click', () => { page -= 1; render(); });
    next?.addEventListener('click', () => { page += 1; render(); });
    sizeSelect?.addEventListener('change', () => { perPage = Number(sizeSelect.value || 10); page = 1; render(); });
    render();
  };

  pagers.forEach(buildPager);
})();

(() => {
  // v0.6.1: client-side cashier focus reminder. Server-side route lock remains authoritative.
  const activeLock = document.querySelector('.cashier-transaction-lock');
  if (!activeLock) return;
  const isSafeElement = (element) => Boolean(element.closest('.cashier-transaction-lock, .pos-transaction-grid, form, dialog, [data-pos-register], [data-pos-queue-list]'));
  document.addEventListener('click', (event) => {
    const link = event.target instanceof Element ? event.target.closest('a[href]') : null;
    if (!link || isSafeElement(link)) return;
    const href = String(link.getAttribute('href') || '');
    if (!href || href.startsWith('#') || href.startsWith('javascript:')) return;
    event.preventDefault();
    const message = 'Complete or cancel the active cashier transaction before leaving this register.';
    const existing = document.querySelector('[data-cashier-client-lock-warning]');
    if (existing) { existing.remove(); }
    const warning = document.createElement('div');
    warning.className = 'alert warning';
    warning.setAttribute('data-cashier-client-lock-warning', '1');
    warning.innerHTML = `<strong>Focused transaction is active.</strong> ${message}`;
    activeLock.insertAdjacentElement('afterend', warning);
    dentalScrollTargetIntoView(warning, { force: true, behavior: 'smooth' });
  }, true);
})();

(() => {
  // DentalCare 0.6.2: keep the visible clinical coach synchronized with the enforced rail.
  const rail = document.querySelector('[data-v062-clinical-enforcement]');
  const checklist = document.querySelector('[data-clinical-checklist]');
  if (!rail && !checklist) return;
  const targetForCheck = check => dentalClinicalCheckTarget(check || 'intake');
  const updateClinicalCoach = () => {
    const items = [...document.querySelectorAll('[data-clinical-checklist] [data-check-item], [data-v062-clinical-enforcement] [data-target-check-item]')];
    const missing = items.find(item => item.classList.contains('missing'));
    const completeCount = items.filter(item => item.classList.contains('complete')).length;
    const total = items.length || 0;
    const label = document.querySelector('[data-next-required-label]');
    const copy = document.querySelector('[data-next-required-copy]');
    const chip = document.querySelector('[data-next-required-chip]');
    const count = document.querySelector('[data-completion-count]');
    if (count && total) count.textContent = `${completeCount} / ${total}`;
    if (missing) {
      const title = missing.querySelector('strong')?.textContent?.trim() || 'Next required item';
      const hint = missing.querySelector('small')?.textContent?.trim() || 'Complete this before release.';
      if (label) label.textContent = title;
      if (copy) copy.textContent = hint;
      if (chip) { chip.textContent = 'Action needed'; chip.classList.remove('success'); chip.classList.add('warning'); }
    } else {
      if (label) label.textContent = 'Ready to complete';
      if (copy) copy.textContent = 'All required clinical and cashier handoff checks are satisfied.';
      if (chip) { chip.textContent = 'Ready'; chip.classList.remove('warning'); chip.classList.add('success'); }
    }
  };
  rail?.addEventListener('click', event => {
    const item = event.target.closest('[data-target-check-item]');
    if (!item) return;
    const target = targetForCheck(item.dataset.targetCheckItem || item.dataset.v062Step || 'intake');
    const tab = document.querySelector(`[data-clinical-section-tabs] [data-clinical-target="${target}"]`);
    tab?.click();
    window.setTimeout(() => { const panel = document.querySelector(`[data-clinical-panel="${target}"]`); if (panel) dentalScrollTargetIntoView(panel, { force: false, behavior: 'smooth' }); }, 40);
  });
  document.addEventListener('change', event => {
    if (event.target.closest('[data-clinical-workspace]')) window.setTimeout(updateClinicalCoach, 40);
  });
  updateClinicalCoach();
})();

(() => {
  // DentalCare 0.6.2: make active POS lock visible to the whole shell and warn on browser exits too.
  const activeLock = document.querySelector('.cashier-transaction-lock');
  if (!activeLock) return;
  document.body.classList.add('transaction-navigation-locked');
  let cashierIntentionalSubmit = false;
  document.querySelectorAll('.pos-register-workspace form').forEach(form => {
    form.addEventListener('submit', () => { cashierIntentionalSubmit = true; });
  });
  window.addEventListener('beforeunload', event => {
    if (cashierIntentionalSubmit) return;
    event.preventDefault();
    event.returnValue = 'Complete or cancel the active cashier transaction before leaving.';
  });
})();

(() => {
  // DentalCare 0.7.4: reduce visual clutter by turning inline guidance text into hover/click help.
  const root = document.querySelector('[data-clinical-workspace]')?.closest('body') || document.body;
  const clinical = document.querySelector('[data-clinical-workspace]');
  if (!clinical || root.dataset.compactClinicalGuidesApplied === '1') return;
  root.dataset.compactClinicalGuidesApplied = '1';
  root.classList.add('clinical-compact-guides-active');

  const helpSelectors = [
    '.clinical-workspace-main .card-head p',
    '.clinical-workspace-main .section-heading p',
    '.clinical-workspace-main .clinical-command-strip small',
    '.clinical-workspace-main .check-label small',
    '.clinical-workspace-main label small',
    '.clinical-workspace-main .bp-measurement-guide p',
    '.clinical-workspace-main .bp-screening-panel small',
    '.clinical-workspace-main .previous-vitals-strip + p',
    '.clinical-workspace-main .muted:not(.keep-inline-help)',
    '.clinical-workspace-main .hint:not(.keep-inline-help)'
  ];

  const shouldSkip = (node) => {
    if (!(node instanceof HTMLElement)) return true;
    if (node.dataset.contextHelpified === '1') return true;
    if (node.closest('.context-help,.context-help-popover,.alert,.flash,.badge,.table-responsive,.status-actions,.clinical-treatment-cockpit')) return true;
    const text = (node.textContent || '').replace(/\s+/g, ' ').trim();
    return text.length < 12;
  };

  const makeHelp = (text, label = 'Help') => {
    const wrap = document.createElement('span');
    wrap.className = 'context-help auto-context-help';
    wrap.tabIndex = 0;
    const btn = document.createElement('button');
    btn.type = 'button';
    btn.className = 'context-help-trigger';
    btn.setAttribute('aria-expanded', 'false');
    btn.textContent = '?';
    btn.title = label;
    const pop = document.createElement('span');
    pop.className = 'context-help-popover';
    pop.setAttribute('role', 'tooltip');
    pop.textContent = text;
    wrap.append(btn, pop);
    return wrap;
  };

  document.querySelectorAll(helpSelectors.join(',')).forEach(node => {
    if (shouldSkip(node)) return;
    const text = (node.textContent || '').replace(/\s+/g, ' ').trim();
    node.dataset.contextHelpified = '1';
    node.classList.add('context-help-source');
    node.insertAdjacentElement('afterend', makeHelp(text));
  });

  document.addEventListener('click', event => {
    const trigger = event.target instanceof Element ? event.target.closest('[data-context-help-toggle], .context-help-trigger') : null;
    if (!trigger) return;
    const help = trigger.closest('.context-help, .clinical-flow-reference-popout');
    if (!help) return;
    event.preventDefault();
    const isOpen = help.classList.toggle('open');
    trigger.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
    document.querySelectorAll('.context-help.open, .clinical-flow-reference-popout.open').forEach(other => {
      if (other === help) return;
      other.classList.remove('open');
      other.querySelector('.context-help-trigger')?.setAttribute('aria-expanded', 'false');
    });
  }, true);

  document.addEventListener('click', event => {
    if (event.target instanceof Element && event.target.closest('.context-help, .clinical-flow-reference-popout')) return;
    document.querySelectorAll('.context-help.open, .clinical-flow-reference-popout.open').forEach(help => {
      help.classList.remove('open');
      help.querySelector('.context-help-trigger')?.setAttribute('aria-expanded', 'false');
    });
  });

  document.addEventListener('keydown', event => {
    if (event.key !== 'Escape') return;
    document.querySelectorAll('.context-help.open, .clinical-flow-reference-popout.open').forEach(help => {
      help.classList.remove('open');
      help.querySelector('.context-help-trigger')?.setAttribute('aria-expanded', 'false');
    });
  });
})();
