// ============================================================
// APP.JS — Core utilities, tabs, model selector, helpers
// ============================================================

// ── Client-side error logging ──────────────────────────────
function sendError(data) {
  try {
    navigator.sendBeacon('/api/logs/client-error', new Blob(
      [JSON.stringify(data)], { type: 'application/json' }
    ));
  } catch(e) {}
}

window.onerror = function(msg, src, line, col, err) {
  // Ignore errors from browser extensions
  if (src && src.indexOf('moz-extension') !== -1) return;
  if (src && src.indexOf('chrome-extension') !== -1) return;
  sendError({ type: 'uncaught', message: String(msg), source: src, line: line, col: col, stack: err && err.stack });
};

window.addEventListener('unhandledrejection', function(e) {
  var msg = e.reason ? (e.reason.message || String(e.reason)) : 'Unhandled promise rejection';
  sendError({ type: 'unhandledrejection', message: msg, stack: e.reason && e.reason.stack });
});

document.addEventListener('DOMContentLoaded', function() {

  // --- COMPONENT LOADER (lazy-load tab HTML from /components/) ---
  function getComponentVersion() {
    try {
      var script = document.currentScript || document.querySelector('script[src^="/js/app.js"]');
      var version = script ? new URL(script.src, window.location.href).searchParams.get('v') : '';
      return version || 'dev';
    } catch(e) { return 'dev'; }
  }
  var COMPONENT_VERSION = getComponentVersion();
  window.PEDSCRIBE_COMPONENT_VERSION = COMPONENT_VERSION;
  var _componentCache = {};
  var _componentLoading = {};

  function loadComponent(tabEl) {
    var component = tabEl.getAttribute('data-component');
    if (!component || tabEl.dataset.loaded) return Promise.resolve();
    if (_componentCache[component]) {
      tabEl.innerHTML = _componentCache[component];
      tabEl.dataset.loaded = '1';
      return Promise.resolve();
    }
    if (_componentLoading[component]) return _componentLoading[component];

    _componentLoading[component] = fetch('/components/' + component + '.html?v=' + COMPONENT_VERSION)
      .then(function(r) { if (!r.ok) throw new Error(r.status); return r.text(); })
      .then(function(html) {
        _componentCache[component] = html;
        tabEl.innerHTML = html;
        tabEl.dataset.loaded = '1';
        // Re-attach per-tab model selectors
        tabEl.querySelectorAll('.tab-model-select').forEach(function(sel) {
          if (typeof window._buildModelOptions === 'function') window._buildModelOptions(sel);
        });
        delete _componentLoading[component];
      })
      .catch(function(err) {
        console.warn('[Component] Failed to load ' + component + ':', err);
        tabEl.innerHTML = '<div style="padding:40px;text-align:center;color:var(--g400);">Failed to load. Please refresh.</div>';
        delete _componentLoading[component];
      });
    return _componentLoading[component];
  }

  // Preload the first tab immediately
  var firstTab = document.querySelector('.tab-content[data-component]');
  if (firstTab) loadComponent(firstTab);

  // --- TAB NAVIGATION ---
  function activateTab(tabName) {
    var btn = document.querySelector('.tab-btn[data-tab="' + tabName + '"]');
    if (!btn || btn.classList.contains('hidden')) return false;
    document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); });
    document.querySelectorAll('.tab-content').forEach(function(c) { c.classList.remove('active'); });
    btn.classList.add('active');
    var tabEl = document.getElementById(tabName + '-tab');
    if (tabEl) {
      tabEl.classList.add('active');
      // Lazy-load component HTML, then fire tabChanged after DOM is ready
      loadComponent(tabEl).then(function() {
        document.dispatchEvent(new CustomEvent('tabChanged', { detail: { tab: tabName } }));
      });
    } else {
      document.dispatchEvent(new CustomEvent('tabChanged', { detail: { tab: tabName } }));
    }
    // Persist last tab in localStorage (restored after login in auth.js)
    try { localStorage.setItem('ped_last_tab', tabName); } catch(e) {}
    // Close sidebar on mobile after tab click
    var sidebar = document.getElementById('sidebar');
    if (sidebar && window.innerWidth <= 768) {
      sidebar.classList.remove('open');
    }
    return true;
  }

  // Expose activateTab globally so auth.js can call it after login
  window.activateTab = activateTab;

  document.querySelectorAll('.tab-btn').forEach(function(btn) {
    btn.addEventListener('click', function() {
      activateTab(btn.getAttribute('data-tab'));
      var subtab = btn.getAttribute('data-subtab');
      if (subtab && typeof window.wvSwitchSubtab === 'function') {
        setTimeout(function() { window.wvSwitchSubtab(subtab); }, 0);
      }
    });
  });

  // --- SIDEBAR TOGGLE ---
  var sidebar = document.getElementById('sidebar');
  var sidebarOverlay = document.getElementById('sidebar-overlay');
  document.addEventListener('click', function(e) {
    if (e.target.closest('#btn-menu-toggle')) {
      if (sidebar) sidebar.classList.toggle('open');
    }
    if (e.target.closest('#btn-sidebar-close') || e.target.closest('#sidebar-overlay')) {
      if (sidebar) sidebar.classList.remove('open');
    }
  });
  if (sidebarOverlay) {
    sidebarOverlay.addEventListener('click', function() {
      if (sidebar) sidebar.classList.remove('open');
    });
  }

  // --- DESKTOP SIDEBAR COLLAPSE ---
  var sidebarPinBtn = document.getElementById('btn-sidebar-pin');
  var sidebarExpandBtn = document.getElementById('btn-sidebar-expand');
  // Restore collapse state
  try {
    if (localStorage.getItem('ped_sidebar_collapsed') === '1') {
      if (sidebar) sidebar.classList.add('collapsed');
      if (sidebarExpandBtn) sidebarExpandBtn.style.display = 'flex';
      if (sidebarPinBtn) sidebarPinBtn.style.display = 'none';
    }
  } catch(e) {}
  if (sidebarPinBtn) {
    sidebarPinBtn.addEventListener('click', function() {
      if (sidebar) sidebar.classList.add('collapsed');
      if (sidebarExpandBtn) sidebarExpandBtn.style.display = 'flex';
      if (sidebarPinBtn) sidebarPinBtn.style.display = 'none';
      try { localStorage.setItem('ped_sidebar_collapsed', '1'); } catch(e) {}
    });
  }
  if (sidebarExpandBtn) {
    sidebarExpandBtn.addEventListener('click', function() {
      if (sidebar) sidebar.classList.remove('collapsed');
      if (sidebarExpandBtn) sidebarExpandBtn.style.display = 'none';
      if (sidebarPinBtn) sidebarPinBtn.style.display = 'flex';
      try { localStorage.setItem('ped_sidebar_collapsed', '0'); } catch(e) {}
    });
  }

  // Set footer year
  var yearEl = document.getElementById('app-year');
  if (yearEl) yearEl.textContent = new Date().getFullYear();

  // Load settings data when settings tab is activated
  document.addEventListener('tabChanged', function(e) {
    if (e.detail && e.detail.tab === 'settings') {
      if (typeof load2FAStatus === 'function') load2FAStatus();
      if (typeof loadSessions === 'function') loadSessions();
      if (typeof loadNextcloudStatus === 'function') loadNextcloudStatus();
      if (typeof loadMemories === 'function') loadMemories();
      if (typeof loadSavedEncountersList === 'function') loadSavedEncountersList();
      if (typeof renderAudioBackups === 'function') renderAudioBackups();
      if (typeof loadDocuments === 'function') loadDocuments();
    }
    if (e.detail && e.detail.tab === 'faq') {
      // Wire FAQ accordion after component loads
      setTimeout(function() {
        document.querySelectorAll('.faq-question').forEach(function(btn) {
          if (btn._faqWired) return;
          btn._faqWired = true;
          btn.addEventListener('click', function() {
            var item = btn.parentElement;
            var isOpen = item.classList.contains('open');
            var section = item.closest('.faq-section');
            if (section) section.querySelectorAll('.faq-item.open').forEach(function(i) { i.classList.remove('open'); });
            if (!isOpen) item.classList.add('open');
          });
        });
      }, 100);
    }
  });

  // --- MODEL SELECTORS ---
  window._currentModels = [];
  window._currentProvider = 'openrouter';
  window._defaultModelId = '';

  fetch('/api/models')
    .then(function(r) { return r.json(); })
    .then(function(data) {
      window._currentModels = data.models || [];
      window._currentProvider = data.provider || 'openrouter';

      window._buildModelOptions = function buildModelOptions(selectEl) {
        selectEl.innerHTML = '';
        window._currentModels.forEach(function(m) {
          var opt = document.createElement('option');
          opt.value = m.id;
          opt.textContent = m.name;
          selectEl.appendChild(opt);
        });
        if (window._defaultModelId && !Array.prototype.some.call(selectEl.options, function(opt) { return opt.value === window._defaultModelId; })) {
          var saved = document.createElement('option');
          saved.value = window._defaultModelId;
          saved.textContent = window._defaultModelId + ' (saved default)';
          selectEl.appendChild(saved);
        }
        if (window._defaultModelId) selectEl.value = window._defaultModelId;
      }

      // Determine default model (admin override or first model)
      var defaultModelId = data.defaultModel || (window._currentModels.length > 0 ? window._currentModels[0].id : '');
      window._defaultModelId = defaultModelId;

      // Populate all per-tab model selectors already in DOM
      document.querySelectorAll('.tab-model-select').forEach(function(sel) {
        window._buildModelOptions(sel);
      });
    })
    .catch(function(err) { console.warn('Models load failed:', err); });

  console.log('✅ App.js DOM ready');

}); // end DOMContentLoaded

// ============================================================
// GLOBAL FUNCTIONS (must be outside DOMContentLoaded)
// ============================================================

// ── Set formatted text on output/contenteditable elements ──
// Converts \n to <br> so line breaks survive in contenteditable divs
// and are preserved when copying with innerText
function setOutputText(el, text) {
  if (!el) return;
  if (typeof el === 'string') el = document.getElementById(el);
  if (!el) return;
  // Escape HTML, then convert newlines to <br>
  var safe = String(text || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  el.innerHTML = safe.replace(/\n/g, '<br>');
}

// ── Data-action event delegation (replaces inline onclick handlers) ──
// Handles data-action="copy|speak|nc-export" on any element,
// allowing removal of 'unsafe-inline' from the Content Security Policy.
document.addEventListener('click', function(e) {
  var btn = e.target.closest('[data-action]');
  if (!btn) return;
  var action = btn.getAttribute('data-action');
  var targetId = btn.getAttribute('data-target');
  if (!action) return;

  if (action === 'copy' && targetId) {
    if (typeof copyText === 'function') copyText(targetId);
    // Log PHI copy event (fire-and-forget, best-effort)
    try {
      var token = window.AUTH_TOKEN || localStorage.getItem('ped_scribe_token') || '';
      if (token) {
        fetch('/api/logs/client-event', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
          body: JSON.stringify({ action: 'copy_to_clipboard', target: targetId })
        }).catch(function() {});
      }
    } catch(e) {}
    return;
  }
  if (action === 'speak' && targetId) {
    if (typeof speakText === 'function') speakText(targetId);
    return;
  }
  if (action === 'nc-export' && targetId) {
    var label = btn.getAttribute('data-label') || 'export';
    if (typeof exportToNextcloud === 'function') exportToNextcloud(targetId, label);
    return;
  }
});

// ── Announcement banner ────────────────────────────────────
function loadAnnouncement() {
  fetch('/api/admin/config/announcement', { headers: getAuthHeaders() })
    .then(function(r) { if (!r.ok) throw new Error('not ok'); return r.json(); })
    .then(function(data) {
      if (!data.success) return;
      var banner = document.getElementById('announcement-banner');
      var text   = document.getElementById('announcement-text');
      var icon   = document.getElementById('announcement-icon');
      if (!banner || !text) return;
      if (data.enabled && data.text && data.text.trim()) {
        text.textContent = data.text;
        // type → icon + class
        var icons = { info: 'fa-info-circle', warning: 'fa-triangle-exclamation', error: 'fa-circle-xmark', success: 'fa-circle-check' };
        var type = data.type || 'info';
        if (icon) { icon.className = 'fas ' + (icons[type] || icons.info); }
        banner.className = 'announcement-banner ann-' + type;
      } else {
        banner.className = 'announcement-banner hidden';
      }
    })
    .catch(function() {}); // silently ignore if endpoint not yet available
}

// Close button — dismiss for this page view only (reappears on refresh/re-login)
var closeBtn = document.getElementById('announcement-close');
if (closeBtn) {
  closeBtn.addEventListener('click', function() {
    var banner = document.getElementById('announcement-banner');
    if (banner) banner.classList.add('hidden');
  });
}

function getSelectedModel() {
  // Prefer the active tab's own model selector if present
  var activeTab = document.querySelector('.tab-content.active');
  if (activeTab) {
    var tabSel = activeTab.querySelector('.tab-model-select');
    if (tabSel && tabSel.value) return tabSel.value;
  }
  return undefined;
}

function showLoading(text) {
  var overlay = document.getElementById('loading-overlay');
  var textEl = document.getElementById('loading-text');
  if (textEl) textEl.textContent = text || 'Processing...';
  if (overlay) { overlay.style.display = 'flex'; overlay.className = overlay.className.replace('hidden', '').trim(); }
}

function hideLoading() {
  var overlay = document.getElementById('loading-overlay');
  if (overlay) overlay.style.display = 'none';
}

// Non-blocking busy bar — user can keep working while AI processes
function showBusy(text) {
  var bar = document.getElementById('busy-bar');
  var textEl = document.getElementById('busy-text');
  if (textEl) textEl.textContent = text || 'Processing...';
  if (bar) bar.classList.add('active');
}

function hideBusy() {
  var bar = document.getElementById('busy-bar');
  if (bar) bar.classList.remove('active');
}

// Reusable confirmation modal — replaces browser confirm() and prompt()
// Usage: showConfirm('Are you sure?', function() { doSomething(); });
// With input: showConfirm('Enter email:', function(value) { send(value); }, { input: true, placeholder: 'email@example.com', inputType: 'email' });
// With danger styling: showConfirm('Delete user?', function() { deleteUser(); }, { danger: true, confirmText: 'Delete' });
window.showConfirm = function(message, onConfirm, opts) {
  opts = opts || {};
  var modal = document.getElementById('confirm-modal');
  var text = document.getElementById('confirm-modal-text');
  var okBtn = document.getElementById('confirm-modal-ok');
  var cancelBtn = document.getElementById('confirm-modal-cancel');
  var inputWrap = document.getElementById('confirm-modal-input-wrap');
  var inputEl = document.getElementById('confirm-modal-input');
  if (!modal || !text || !okBtn) return;

  text.textContent = message;
  okBtn.textContent = opts.confirmText || 'Confirm';
  if (opts.danger) { okBtn.style.background = 'var(--red)'; okBtn.style.borderColor = 'var(--red)'; }
  else { okBtn.style.background = ''; okBtn.style.borderColor = ''; }

  if (opts.input) {
    inputWrap.style.display = 'block';
    inputEl.type = opts.inputType || 'text';
    inputEl.placeholder = opts.placeholder || '';
    inputEl.value = opts.defaultValue || '';
    setTimeout(function() { inputEl.focus(); }, 50);
  } else {
    inputWrap.style.display = 'none';
  }

  modal.classList.remove('hidden');

  function cleanup() {
    modal.classList.add('hidden');
    okBtn.onclick = null;
    cancelBtn.onclick = null;
    inputEl.value = '';
  }

  cancelBtn.onclick = function() { cleanup(); };
  okBtn.onclick = function() {
    if (opts.input) {
      var val = inputEl.value;
      if (opts.required && !val.trim()) { showToast(opts.requiredMsg || 'This field is required', 'error'); return; }
      cleanup();
      if (onConfirm) onConfirm(val);
    } else {
      cleanup();
      if (onConfirm) onConfirm();
    }
  };

  // Allow Enter key to confirm
  inputEl.onkeydown = function(e) { if (e.key === 'Enter') okBtn.click(); };
};

function showToast(message, type) {
  var container = document.getElementById('toast-container');
  if (!container) { console.log('Toast:', type, message); return; }
  var toast = document.createElement('div');
  toast.className = 'toast toast-' + (type || 'success');
  var icon = type === 'error' ? 'exclamation-circle' : type === 'info' ? 'info-circle' : 'check-circle';
  var iconEl = document.createElement('i');
  iconEl.className = 'fas fa-' + icon;
  toast.appendChild(iconEl);
  toast.appendChild(document.createTextNode(' ' + String(message == null ? '' : message)));
  container.appendChild(toast);
  setTimeout(function() { toast.remove(); }, 3500);
}

function copyText(elementId) {
  var el = document.getElementById(elementId);
  if (!el) return;
  var text = el.innerText || el.textContent || '';
  if (!text.trim()) { showToast('Nothing to copy', 'error'); return; }

  // Try modern clipboard API (requires HTTPS or localhost)
  if (navigator.clipboard && navigator.clipboard.writeText) {
    navigator.clipboard.writeText(text).then(function() {
      showToast('Copied!', 'success');
    }).catch(function(err) {
      // Fallback: select + execCommand
      try {
        var range = document.createRange();
        range.selectNodeContents(el);
        window.getSelection().removeAllRanges();
        window.getSelection().addRange(range);
        var ok = document.execCommand('copy');
        window.getSelection().removeAllRanges();
        showToast(ok ? 'Copied!' : 'Copy failed — please select text manually', ok ? 'success' : 'error');
      } catch (e) {
        showToast('Copy not supported in this browser', 'error');
      }
    });
  } else {
    // No clipboard API — use execCommand directly
    try {
      var range = document.createRange();
      range.selectNodeContents(el);
      window.getSelection().removeAllRanges();
      window.getSelection().addRange(range);
      var ok = document.execCommand('copy');
      window.getSelection().removeAllRanges();
      showToast(ok ? 'Copied!' : 'Copy failed — please select text manually', ok ? 'success' : 'error');
    } catch (e) {
      showToast('Copy not supported in this browser', 'error');
    }
  }
}

// Speak / Stop
var currentlyReadingId = null;
var currentAudio = null;

function speakText(elementId) {
  if (currentlyReadingId === elementId) { stopReading(); return; }
  stopReading();
  var el = document.getElementById(elementId);
  if (!el) return;
  var text = (el.innerText || el.textContent).trim();
  if (!text) { showToast('Nothing to read', 'error'); return; }

  currentlyReadingId = elementId;
  var btn = findReadButton(elementId);
  if (btn) { btn.classList.add('btn-reading'); btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Loading...'; }

  fetch('/api/text-to-speech', {
    method: 'POST',
    headers: window.getAuthHeaders ? window.getAuthHeaders() : { 'Content-Type': 'application/json' },
    body: JSON.stringify({ text: text })
  })
  .then(function(r) {
    if (!r.ok) throw new Error('TTS request failed (' + r.status + ')');
    var ttsProvider = r.headers.get('X-TTS-Provider') || 'server';
    return r.blob().then(function(blob) { return { blob: blob, provider: ttsProvider }; });
  })
  .then(function(result) {
    var url = URL.createObjectURL(result.blob);
    currentAudio = new Audio(url);
    currentAudio.onended = function() { URL.revokeObjectURL(url); stopReading(); };
    currentAudio.onerror = function() { URL.revokeObjectURL(url); stopReading(); showToast('Audio playback error', 'error'); };
    currentAudio.play();
    if (btn) { btn.classList.add('btn-reading'); btn.innerHTML = '<i class="fas fa-stop"></i> Stop'; }
    showToast('Reading aloud (' + result.provider + ')', 'info');
  })
  .catch(function(err) {
    stopReading();
    // Fallback to browser TTS if server TTS fails
    if ('speechSynthesis' in window) {
      var utter = new SpeechSynthesisUtterance(text);
      utter.rate = 0.9;
      utter.onend = function() { stopReading(); };
      currentlyReadingId = elementId;
      if (btn) { btn.classList.add('btn-reading'); btn.innerHTML = '<i class="fas fa-stop"></i> Stop'; }
      window.speechSynthesis.speak(utter);
      showToast('TTS unavailable — using browser voice', 'info');
    } else {
      showToast('Read aloud failed: ' + err.message, 'error');
    }
  });
}

function stopReading() {
  if ('speechSynthesis' in window) window.speechSynthesis.cancel();
  if (currentAudio) { currentAudio.pause(); currentAudio = null; }
  if (currentlyReadingId) {
    var btn = findReadButton(currentlyReadingId);
    if (btn) { btn.classList.remove('btn-reading'); btn.innerHTML = '<i class="fas fa-volume-high"></i> Read'; }
    currentlyReadingId = null;
  }
}

function findReadButton(elementId) {
  var el = document.getElementById(elementId);
  if (!el) return null;
  var card = el.closest('.output-card') || el.closest('.card');
  if (!card) return null;
  var buttons = card.querySelectorAll('button');
  for (var i = 0; i < buttons.length; i++) {
    var btn = buttons[i];
    // data-action="speak" buttons (current approach)
    if (btn.getAttribute('data-action') === 'speak' && btn.getAttribute('data-target') === elementId) return btn;
    // legacy onclick fallback
    var oc = btn.getAttribute('onclick') || '';
    if (oc.indexOf('speakText') !== -1 && oc.indexOf(elementId) !== -1) return btn;
  }
  return null;
}

// Timer
function createTimer(el) {
  var s = 0, iv = null;
  return {
    start: function() { s = 0; this.resume(); },
    resume: function() { this.update(); var self = this; if (!iv) iv = setInterval(function() { s++; self.update(); }, 1000); },
    stop: function() { if (iv) { clearInterval(iv); iv = null; } return s; },
    reset: function() { s = 0; this.update(); },
    update: function() { el.textContent = String(Math.floor(s / 60)).padStart(2, '0') + ':' + String(s % 60).padStart(2, '0'); }
  };
}

// Audio Recorder
function AudioRecorder() { this.mediaRecorder = null; this.chunks = []; this.stream = null; }
AudioRecorder.prototype.start = function() {
  var self = this; self.chunks = [];
  return navigator.mediaDevices.getUserMedia({ audio: { channelCount: 1, sampleRate: 16000, echoCancellation: true, noiseSuppression: true } })
  .then(function(stream) {
    self.stream = stream;
    var mime = MediaRecorder.isTypeSupported('audio/webm;codecs=opus') ? 'audio/webm;codecs=opus' : 'audio/webm';
    // 32kbps Opus is excellent for speech — small files, fast upload, great quality
    self.mediaRecorder = new MediaRecorder(stream, { mimeType: mime, audioBitsPerSecond: 32000 });
    self.mediaRecorder.ondataavailable = function(e) { if (e.data.size > 0) self.chunks.push(e.data); };
    self.mediaRecorder.start(1000);
  });
};
AudioRecorder.prototype.stop = function() {
  var self = this;
  return new Promise(function(resolve) {
    if (!self.mediaRecorder || self.mediaRecorder.state === 'inactive') { resolve(null); return; }
    self.mediaRecorder.onstop = function() {
      var blob = new Blob(self.chunks, { type: self.mediaRecorder.mimeType });
      if (self.stream) self.stream.getTracks().forEach(function(t) { t.stop(); });
      resolve(blob);
    };
    self.mediaRecorder.stop();
  });
};

// ── Native mobile helpers (Capacitor / Android bridge) ──
// These only activate when running inside the native app
window.nativeHaptic = function(style) {
  try {
    if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Haptics) {
      window.Capacitor.Plugins.Haptics.impact({ style: style || 'medium' });
    } else if (navigator.vibrate) {
      navigator.vibrate(style === 'heavy' ? 100 : 50);
    }
  } catch(e) {}
};

// Start/stop Android foreground service for background recording
window.nativeStartRecordingService = function() {
  try { if (window.NativeRecording) window.NativeRecording.startForegroundService(); } catch(e) {}
};
window.nativeStopRecordingService = function() {
  try { if (window.NativeRecording) window.NativeRecording.stopForegroundService(); } catch(e) {}
};

// Keep screen awake during recording.
//
// Prefer the NativeRecording bridge (addJavascriptInterface, so it is present
// on the remote origin the launcher navigates to). The Capacitor KeepAwake
// plugin is kept as a fallback but is NOT installed in this project — relying
// on it alone meant this function silently did nothing and the screen slept
// mid-recording, killing the MediaRecorder.
window.nativeKeepAwake = function(on) {
  try {
    if (window.NativeRecording && typeof window.NativeRecording.keepAwake === 'function') {
      window.NativeRecording.keepAwake(!!on);
      return;
    }
    if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.KeepAwake) {
      if (on) window.Capacitor.Plugins.KeepAwake.keepAwake();
      else window.Capacitor.Plugins.KeepAwake.allowSleep();
    }
  } catch(e) {}
};

// Detect if running in native app
window.isNativeApp = function() {
  return !!(window.Capacitor && window.Capacitor.isNativePlatform && window.Capacitor.isNativePlatform());
};

// Check if server-side transcription (Whisper/AWS) is available
window._transcribeAvailable = null; // null = not checked yet, true/false after check
function checkTranscribeStatus() {
  var token = window.AUTH_TOKEN || localStorage.getItem('ped_scribe_token') || '';
  if (!token) return;
  fetch('/api/transcribe/status', {
    headers: { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }
  })
  .then(function(r) { return r.json(); })
  .then(function(data) {
    window._transcribeAvailable = !!data.available;
    window._transcribeProvider = data.provider || 'none';
    if (!data.available) {
      console.log('[Transcribe] No server transcription configured — using browser speech recognition only');
    }
  })
  .catch(function() { window._transcribeAvailable = false; });
}

function transcribeAudio(blob) {
  return _serverTranscribe(blob);
}

function _serverTranscribe(blob) {
  // If no server transcription is configured, skip upload entirely
  if (window._transcribeAvailable === false) {
    return Promise.resolve({ success: false, noProvider: true, error: 'No transcription API configured — using live transcript' });
  }

  var startTime = Date.now();
  var formData = new FormData();
  formData.append('audio', blob, 'audio.webm');
  return fetch('/api/transcribe', {
    method: 'POST',
    headers: { 'Authorization': 'Bearer ' + (window.AUTH_TOKEN || localStorage.getItem('ped_scribe_token') || '') },
    body: formData
  }).then(function(r) { return r.json(); }).then(function(data) {
    var elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
    if (data.success && data.provider) {
      showToast('Transcribed via ' + data.provider + ' (' + elapsed + 's)', 'info');
    }
    if (!data.success && blob.size > 0 && typeof saveAudioBackup === 'function') {
      console.log('[AudioBackup] Transcription failed, saving backup...');
      saveAudioBackup(blob, 'failed-transcription').then(function(id) {
        console.log('[AudioBackup] Saved with id:', id);
        showToast('Audio backed up for retry', 'info');
      }).catch(function(e) { console.error('[AudioBackup] Save failed:', e); });
    }
    return data;
  }).catch(function(err) {
    if (blob.size > 0 && typeof saveAudioBackup === 'function') {
      console.log('[AudioBackup] Transcription error, saving backup...');
      saveAudioBackup(blob, 'failed-transcription').then(function(id) {
        console.log('[AudioBackup] Saved with id:', id);
        showToast('Audio backed up for retry', 'info');
      }).catch(function(e) { console.error('[AudioBackup] Save failed:', e); });
    }
    return { success: false, error: err.message };
  });
}

// Store original source material on an output element (call after generation)
function storeSourceContext(outputElementId, sourceText) {
  var el = document.getElementById(outputElementId);
  if (el && sourceText) el.dataset.sourceContext = sourceText;
}

// ── Billing code suggestions (called after note generation) ──
function suggestBillingCodes(outputElementId, noteText, noteType, patientAge, visitType) {
  // Find or create the billing codes container near the output element
  var outputEl = document.getElementById(outputElementId);
  if (!outputEl || !noteText) return;
  var card = outputEl.closest('.card, .output-card');
  if (!card) return;

  var containerId = outputElementId.replace('-text', '') + '-billing-codes';
  var container = document.getElementById(containerId);
  if (!container) {
    // Create container dynamically if not in HTML
    container = document.createElement('div');
    container.id = containerId;
    container.className = 'billing-codes-card';
    // Insert after the output text element
    outputEl.parentNode.insertBefore(container, outputEl.nextSibling);
  }

  container.className = 'billing-codes-card';
  container.innerHTML = '<div style="font-size:12px;color:var(--g500);"><i class="fas fa-spinner fa-spin"></i> Analyzing billing codes...</div>';

  fetch('/api/suggest-codes', {
    method: 'POST',
    headers: getAuthHeaders(),
    body: JSON.stringify({
      noteText: noteText,
      noteType: noteType || 'soap',
      patientAge: patientAge || '',
      visitType: visitType || 'outpatient'
    })
  })
  .then(function(r) { return r.json(); })
  .then(function(data) {
    if (!data.success || (!data.icd10.length && !data.cpt.length)) {
      container.classList.add('hidden');
      return;
    }

    var html = '<h4><i class="fas fa-file-invoice-dollar"></i> Suggested Billing Codes</h4>';

    if (data.icd10 && data.icd10.length > 0) {
      html += '<div class="billing-codes-section"><div class="billing-codes-label">ICD-10 Diagnoses</div><div>';
      data.icd10.forEach(function(c) {
        var name = c.name ? ' <span class="billing-code-name">' + escHtml(c.name) + '</span>' : '';
        html += '<span class="billing-code-chip icd" title="Click to copy" data-code="' + escHtml(c.code) + '">' + escHtml(c.code) + name + '</span>';
      });
      html += '</div></div>';
    }

    if (data.cpt && data.cpt.length > 0) {
      html += '<div class="billing-codes-section"><div class="billing-codes-label">CPT / E&M</div><div>';
      data.cpt.forEach(function(c) {
        var desc = c.desc ? ' <span class="billing-code-name">' + escHtml(c.desc) + '</span>' : '';
        html += '<span class="billing-code-chip cpt" title="Click to copy" data-code="' + escHtml(c.code) + '">' + escHtml(c.code) + desc + '</span>';
      });
      html += '</div></div>';
    }

    if (data.emLevel) {
      html += '<div class="billing-codes-section"><div class="billing-codes-label">E/M Assessment</div>';
      html += '<span class="billing-code-chip em">Level ' + escHtml(data.emLevel.level) + '</span>';
      html += '<span style="font-size:11px;color:var(--g500);margin-left:6px;">MDM: ' + escHtml(data.emLevel.complexity) + ' | ' + escHtml(data.emLevel.diagnosisCount) + ' dx | ' + escHtml(data.emLevel.rosCount) + ' ROS | ' + escHtml(data.emLevel.peCount) + ' PE</span>';
      html += '</div>';
    }

    html += '<p style="font-size:10px;color:var(--g400);margin:8px 0 0;">Suggestions only. Always verify codes against your institution\'s coding guidelines.</p>';

    container.innerHTML = html;

    // Wire click-to-copy on chips
    container.querySelectorAll('.billing-code-chip').forEach(function(chip) {
      chip.addEventListener('click', function() {
        var code = chip.dataset.code;
        if (code && navigator.clipboard) {
          navigator.clipboard.writeText(code);
          showToast('Copied: ' + code, 'info');
        }
      });
    });
  })
  .catch(function() {
    container.classList.add('hidden');
  });
}

function escHtml(s) { return String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }

// ── Don't-miss tooltip (called after sick visit / encounter HPI generates) ──
// Same insertion pattern as suggestBillingCodes — a side card next to the note
// output. Hard-capped at 5 items by the server prompt. Silent on empty / failure.
function suggestDontMiss(outputElementId, noteText, noteType, patientAge, chiefComplaint) {
  var outputEl = document.getElementById(outputElementId);
  if (!outputEl || !noteText) return;
  var card = outputEl.closest('.card, .output-card');
  if (!card) return;

  var containerId = outputElementId.replace('-text', '') + '-dont-miss';
  var container = document.getElementById(containerId);
  if (!container) {
    container = document.createElement('div');
    container.id = containerId;
    container.className = 'card dont-miss-card';
    container.style.cssText = 'margin-top:10px;border-left:3px solid #f59e0b;';
    outputEl.parentNode.insertBefore(container, outputEl.nextSibling);
  }

  container.innerHTML = '<div style="padding:10px 16px;font-size:12px;color:var(--g500);"><i class="fas fa-spinner fa-spin"></i> Reviewing note for high-yield items...</div>';

  fetch('/api/dont-miss', {
    method: 'POST',
    headers: getAuthHeaders(),
    body: JSON.stringify({
      noteText: noteText,
      noteType: noteType || '',
      patientAge: patientAge || '',
      chiefComplaint: chiefComplaint || ''
    })
  })
  .then(function(r) { return r.json(); })
  .then(function(data) {
    if (!data.success || !data.points || data.points.length === 0) {
      container.classList.add('hidden');
      return;
    }
    var html = '<div class="card-header"><h3><i class="fas fa-triangle-exclamation" style="color:#f59e0b;"></i> Don\'t Miss</h3>' +
               '<span style="font-size:11px;color:var(--g500);">High-yield items. Suggestions only.</span></div>' +
               '<div style="padding:8px 16px;">';
    data.points.forEach(function(p) {
      var why = p.why ? '<div style="font-size:11px;color:var(--g500);margin-top:2px;">' + escHtml(p.why) + '</div>' : '';
      html += '<div style="padding:6px 0;border-bottom:1px solid var(--g100);">' +
        '<div style="font-size:13px;color:var(--g800);"><i class="fas fa-circle-exclamation" style="color:#f59e0b;font-size:11px;margin-right:6px;"></i>' + escHtml(p.point) + '</div>' +
        why + '</div>';
    });
    html += '</div>';
    container.classList.remove('hidden');
    container.innerHTML = html;
  })
  .catch(function() {
    container.classList.add('hidden');
  });
}

// ── Patient education handout helper ────────────────────────
// Adds a reusable "Handout" action beside generated clinical notes. The actual
// handout is generated only when the physician clicks Generate in the panel.
function attachPatientEducation(outputElementId, opts) {
  var outputEl = document.getElementById(outputElementId);
  if (!outputEl) return;
  opts = opts || {};
  var card = outputEl.closest('.card, .output-card');
  if (!card) return;
  var actions = card.querySelector('.output-actions');
  var prefix = outputElementId.replace(/[^a-zA-Z0-9_-]/g, '');

  if (actions && !document.getElementById(prefix + '-patient-ed-btn')) {
    var btn = document.createElement('button');
    btn.id = prefix + '-patient-ed-btn';
    btn.className = 'btn-sm btn-ghost';
    btn.type = 'button';
    btn.innerHTML = '<i class="fas fa-person-breastfeeding"></i> Handout';
    btn.addEventListener('click', function() {
      var panel = ensurePatientEducationPanel(outputElementId, opts);
      panel.classList.remove('hidden');
      panel.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    });
    actions.appendChild(btn);
  }
}

function ensurePatientEducationPanel(outputElementId, opts) {
  var outputEl = document.getElementById(outputElementId);
  var prefix = outputElementId.replace(/[^a-zA-Z0-9_-]/g, '');
  var panelId = prefix + '-patient-ed';
  var panel = document.getElementById(panelId);
  if (panel) return panel;

  panel = document.createElement('div');
  panel.id = panelId;
  panel.className = 'card patient-ed-card hidden';
  panel.style.cssText = 'margin-top:10px;border-left:3px solid #0ea5e9;';
  panel.innerHTML =
    '<div class="card-header output-header">' +
      '<h3><i class="fas fa-person-breastfeeding" style="color:#0ea5e9;"></i> Patient Education Handout</h3>' +
      '<div class="output-actions">' +
        '<button class="btn-sm btn-primary" id="' + prefix + '-patient-ed-generate" type="button"><i class="fas fa-wand-magic-sparkles"></i> Generate</button>' +
      '</div>' +
    '</div>' +
    '<div style="padding:10px 16px;display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px;align-items:end;">' +
      '<div class="demo-field"><label>Parent language</label><select id="' + prefix + '-patient-ed-language">' +
        '<option>English</option><option>Spanish</option><option>French</option><option>Arabic</option><option>Haitian Creole</option><option>Chinese</option><option>Russian</option><option>Portuguese</option>' +
      '</select></div>' +
      '<div class="demo-field"><label>Diagnosis/context</label><input type="text" id="' + prefix + '-patient-ed-diagnosis" placeholder="Optional: diagnosis to emphasize"></div>' +
      '<div class="demo-field"><label>Medications</label><input type="text" id="' + prefix + '-patient-ed-meds" placeholder="Optional: meds/doses from plan"></div>' +
    '</div>' +
    '<div id="' + prefix + '-patient-ed-text" class="output-text" contenteditable="true" style="margin:0 16px 12px;min-height:120px;" data-placeholder="Generated parent handout appears here..."></div>' +
    '<div style="padding:0 16px 12px;display:flex;gap:8px;flex-wrap:wrap;align-items:center;">' +
      '<button class="btn-sm btn-primary" data-action="copy" data-target="' + prefix + '-patient-ed-text"><i class="fas fa-copy"></i> Copy</button>' +
      '<span style="font-size:11px;color:var(--g500);">Parent-facing draft. Verify before sharing.</span>' +
    '</div>';

  outputEl.parentNode.insertBefore(panel, outputEl.nextSibling);
  var gen = panel.querySelector('#' + prefix + '-patient-ed-generate');
  if (gen) gen.addEventListener('click', function() { generatePatientEducation(outputElementId, opts); });
  return panel;
}

function generatePatientEducation(outputElementId, opts) {
  var outputEl = document.getElementById(outputElementId);
  if (!outputEl) return;
  opts = opts || {};
  var prefix = outputElementId.replace(/[^a-zA-Z0-9_-]/g, '');
  var handoutEl = document.getElementById(prefix + '-patient-ed-text');
  var langEl = document.getElementById(prefix + '-patient-ed-language');
  var dxEl = document.getElementById(prefix + '-patient-ed-diagnosis');
  var medsEl = document.getElementById(prefix + '-patient-ed-meds');
  var noteText = (outputEl.innerText || outputEl.textContent || '').trim();
  if (!noteText) { showToast('No note for handout', 'error'); return; }

  if (handoutEl) handoutEl.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Generating parent handout...';
  fetch('/api/patient-education', {
    method: 'POST',
    headers: getAuthHeaders(),
    body: JSON.stringify({
      noteText: noteText,
      diagnosis: dxEl ? dxEl.value : '',
      medications: medsEl ? medsEl.value : '',
      patientAge: opts.patientAge || '',
      language: langEl ? langEl.value : 'English',
      readingLevel: '6th grade plain language',
      model: getSelectedModel()
    })
  })
  .then(function(r) { return r.json(); })
  .then(function(data) {
    if (!data.success) {
      if (handoutEl) handoutEl.textContent = '';
      showToast(data.error || 'Handout generation failed', 'error');
      return;
    }
    setOutputText(handoutEl, data.handout || '');
    showToast('Patient handout generated', 'success');
  })
  .catch(function(err) {
    if (handoutEl) handoutEl.textContent = '';
    showToast(err.message || 'Handout generation failed', 'error');
  });
}

function refineDocument(outputElementId, inputElementId) {
  var doc = document.getElementById(outputElementId);
  var input = document.getElementById(inputElementId);
  if (!doc || !input) return;
  var docText = doc.innerText.trim();
  var instructions = input.value.trim();
  if (!docText) { showToast('No document', 'error'); return; }
  if (!instructions) { showToast('Enter instructions', 'error'); return; }
  showBusy('Refining...');
  var body = { currentDocument: docText, instructions: instructions, model: getSelectedModel() };
  // Include original source material so AI can reference the full input
  if (doc.dataset.sourceContext) body.sourceContext = doc.dataset.sourceContext;
  fetch('/api/refine', {
    method: 'POST', headers: getAuthHeaders(),
    body: JSON.stringify(body)
  })
  .then(function(r) { return r.json(); })
  .then(function(data) {
    hideBusy();
    if (data.success) { setOutputText(doc, data.refined); input.value = ''; showToast('Refined!', 'success'); }
    else showToast(data.error || 'Failed', 'error');
  })
  .catch(function(err) { hideBusy(); showToast(err.message, 'error'); });
}

function shortenDocument(outputElementId) {
  var doc = document.getElementById(outputElementId);
  if (!doc) return;
  var text = doc.innerText.trim();
  if (!text) { showToast('No document', 'error'); return; }
  showBusy('Shortening...');
  fetch('/api/shorten', {
    method: 'POST', headers: getAuthHeaders(),
    body: JSON.stringify({ document: text, model: getSelectedModel() })
  })
  .then(function(r) { return r.json(); })
  .then(function(data) {
    hideBusy();
    if (data.success) { setOutputText(doc, data.shortened); showToast('Shortened!', 'success'); }
    else showToast(data.error || 'Failed', 'error');
  })
  .catch(function(err) { hideBusy(); showToast(err.message, 'error'); });
}

function exportToNextcloud(elementId, docType) {
  var el = document.getElementById(elementId);
  if (!el) return;
  var text = el.innerText.trim();
  if (!text) { showToast('Nothing to export', 'error'); return; }
  fetch('/api/nextcloud/export', {
    method: 'POST', headers: getAuthHeaders(),
    body: JSON.stringify({ content: text, filename: docType + '-' + Date.now(), type: docType })
  })
  .then(function(r) { return r.json(); })
  .then(function(data) {
    if (data.success) showToast(data.message, 'success');
    else showToast(data.error || 'Export failed', 'error');
  })
  .catch(function() { showToast('Nextcloud not connected', 'error'); });
}

function createSpeechRecognition() {
  if (window.WebSpeechRecognition && !window.WebSpeechRecognition.isEnabled()) return null;
  if (!(window.SpeechRecognition || window.webkitSpeechRecognition)) return null;
  var SR = window.SpeechRecognition || window.webkitSpeechRecognition;
  var rec = new SR();
  rec.continuous = true;
  rec.interimResults = true;
  rec.lang = 'en-US';
  rec.maxAlternatives = 1;
  return rec;
}

// Deduplicate speech recognition finals — Chrome can repeat text across restarts
function deduplicateFinal(newText, existingText) {
  if (!newText || !existingText) return newText;
  var trimmed = newText.trim();
  if (!trimmed) return '';
  // Check if the new text is already at the end of existing text
  if (existingText.trimEnd().endsWith(trimmed)) return '';
  // Check for partial overlap (last sentence repeated)
  var words = trimmed.split(/\s+/);
  if (words.length >= 3) {
    var tail = existingText.trimEnd().split(/\s+/).slice(-words.length).join(' ');
    if (tail === trimmed) return '';
    // Check if first half of new text overlaps with end of existing
    var half = Math.ceil(words.length / 2);
    var firstHalf = words.slice(0, half).join(' ');
    if (existingText.trimEnd().endsWith(firstHalf)) {
      return words.slice(half).join(' ') + ' ';
    }
  }
  return newText;
}

// PWA Service Worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js?v=' + encodeURIComponent(window.PEDSCRIBE_COMPONENT_VERSION || 'dev')).catch(function() {});
}

console.log('✅ App.js loaded');
