// DCOG-OS Client Logic — v1.0
// İstemci ve Daemon (port 5318) arasındaki HTTP entegrasyonu.

// Merkez beyin adresi. Öncelik:
//  1) açık override (?brain=URL veya kayıtlı localStorage) — test/özel dağıtım
//  2) beyin tarafından /app/ altında SAME-ORIGIN servis ediliyorsa → kendi origin'i
//     (yerel: http://127.0.0.1:5318  ·  tünel: https://<public-domain>) — internet üzerinden çalışır
//  3) ayrı statik sunucudan (serve.js dev) açıldıysa → yerel beyin
const DAEMON_URL = (() => {
  try {
    const q = new URLSearchParams(location.search).get('brain');
    if (q) { localStorage.setItem('dcog_brain_url', q); return q.replace(/\/+$/, ''); }
    const saved = localStorage.getItem('dcog_brain_url');
    if (saved) return saved.replace(/\/+$/, '');
  } catch (e) {}
  if (location.protocol.startsWith('http') && location.pathname.startsWith('/app')) {
    return location.origin; // same-origin (beyin kendi client'ını servis ediyor)
  }
  return 'http://127.0.0.1:5318';
})();
const SESSION_ID = 'default_client_session';

// State
let state = {
  agentName: 'Dudu',
  agentPersonality: '',
  agentEmoji: '',
  agentLanguage: 'tr',
  n8nInstalled: false,
  n8nRunning: false,
  n8nUrl: 'http://localhost:5678',
  ttsEnabled: true,
  speechRecognition: null,
  isListening: false,
  activeLogAgentId: null,
  logIntervalId: null,
  telemetryIntervalId: null,
};

// DOM Elements
const els = {
  overlay: document.getElementById('loading-overlay'),
  circle: document.getElementById('progress-circle'),
  percentText: document.getElementById('loading-percentage'),
  phaseText: document.getElementById('loading-phase'),
  progressBar: document.getElementById('loading-bar'),
  app: document.getElementById('app-container'),
  
  // Left panel
  agentAvatar: document.getElementById('agent-avatar-display'),
  agentNameDisplay: document.getElementById('agent-name-display'),
  identityName: document.getElementById('identity-name'),
  identityPersonality: document.getElementById('identity-personality'),
  identityEmoji: document.getElementById('identity-emoji'),
  identityLang: document.getElementById('identity-lang'),
  btnSaveIdentity: document.getElementById('btn-save-identity'),
  
  n8nStatusBadge: document.getElementById('n8n-status-badge'),
  n8nVersionRow: document.getElementById('n8n-version-row'),
  n8nVersionVal: document.getElementById('n8n-version-val'),
  btnN8nInstall: document.getElementById('btn-n8n-install'),
  btnN8nStart: document.getElementById('btn-n8n-start'),
  btnN8nStop: document.getElementById('btn-n8n-stop'),
  linkN8nPanel: document.getElementById('link-n8n-panel'),
  
  telPeers: document.getElementById('tel-peers'),
  telSleep: document.getElementById('tel-sleep'),
  telLocalMem: document.getElementById('tel-local-mem'),
  telSharedMem: document.getElementById('tel-shared-mem'),
  // G1: model & VRAM status
  telVram: document.getElementById('tel-vram'),
  telModelStatus: document.getElementById('tel-model-status'),
  telWeightsCount: document.getElementById('tel-weights-count'),
  modelDownloadBanner: document.getElementById('model-download-banner'),
  modelVramLockBanner: document.getElementById('model-vram-lock-banner'),
  btnModelDownload: document.getElementById('btn-model-download'),
  modelQuantSelect: document.getElementById('model-quant-select'),
  quantDesc: document.getElementById('quant-desc'),
  // H8: upgrade banner
  modelUpgradeBanner: document.getElementById('model-upgrade-banner'),
  upgradeBannerMsg: document.getElementById('upgrade-banner-msg'),
  btnModelUpgrade: document.getElementById('btn-model-upgrade'),
  
  // Center panel
  chatAgentName: document.getElementById('chat-agent-name'),
  chatAgentPersonality: document.getElementById('chat-agent-personality'),
  chatMessages: document.getElementById('chat-messages'),
  chatForm: document.getElementById('chat-form'),
  chatInput: document.getElementById('chat-input'),
  btnVoiceInput: document.getElementById('btn-voice-input'),
  btnToggleTts: document.getElementById('btn-toggle-tts'),
  ttsIconOn: document.getElementById('tts-icon-on'),
  ttsIconOff: document.getElementById('tts-icon-off'),
  btnClearHistory: document.getElementById('btn-clear-history'),
  
  // Right panel
  agentTaskName: document.getElementById('agent-task-name'),
  agentTaskType: document.getElementById('agent-task-type'),
  agentTaskGoal: document.getElementById('agent-task-goal'),
  btnLaunchAgent: document.getElementById('btn-launch-agent'),
  autonomousAgentsList: document.getElementById('autonomous-agents-list'),
  
  activeLogAgentName: document.getElementById('active-log-agent-name'),
  consoleLogs: document.getElementById('console-logs'),
  btnClearConsole: document.getElementById('btn-clear-console'),
};

// ────────────────────────────────────────────────────────────────
// INITIALIZATION & LOADING SCREEN FLOW
// ────────────────────────────────────────────────────────────────

// ── MERKEZ BEYİN KAPISI ──────────────────────────────────────────────
// Bu istemci YALNIZCA ana merkez beyin (sahibin 2-USB'li node'u) açıkken çalışır.
// Beyin kilitliyse (USB çıkık) veya ulaşılamıyorsa → istemci kilitlenir.
// Her şey transitif olarak sahibin 2 USB'sine bağlı: client çalışır ⟺ beyin açık ⟺ USB takılı.
let _appStarted = false;
function startApp() {
  if (_appStarted) return; _appStarted = true;
  startLoadingSimulation();
  initAppEvents();
  loadModelSelector();
  loadFileBrowser();
  loadDesktopBrowser();
  loadModelCenter();
}
function ensureBrainOverlay() {
  let o = document.getElementById('brain-lock-overlay');
  if (!o) {
    o = document.createElement('div'); o.id = 'brain-lock-overlay';
    o.style.cssText = 'position:fixed; inset:0; z-index:99999; background:#07090f; color:#e6edf3; display:none; align-items:center; justify-content:center; text-align:center; font-family:system-ui,-apple-system,sans-serif;';
    o.innerHTML = '<div style="max-width:540px; padding:40px;"><div style="font-size:58px;">🧠🔒</div>' +
      '<h2 style="font-weight:600; letter-spacing:.4px;">CENTRAL BRAIN OFFLINE</h2>' +
      '<p style="color:#9aa7b5; line-height:1.6;">This client only works while the <b>central brain</b> is online.<br>' +
      'The central brain is currently offline (security keys removed) or unreachable.<br>This screen closes automatically once it is back online.</p>' +
      '<p style="margin-top:16px; font-size:12px; color:#5b6b7d;" id="brain-lock-detail">connecting…</p></div>';
    document.body.appendChild(o);
  }
  return o;
}
async function checkBrain() {
  const o = ensureBrainOverlay();
  try {
    const r = await fetch(`${DAEMON_URL}/api/lock_status`, { cache: 'no-store' });
    const j = await r.json();
    if (j && j.locked) {
      o.style.display = 'flex';
      const d = document.getElementById('brain-lock-detail');
      if (d) d.textContent = 'Central brain LOCKED — security keys (USB) removed.';
    } else {
      o.style.display = 'none';
      startApp();
    }
  } catch (e) {
    o.style.display = 'flex';
    const d = document.getElementById('brain-lock-detail');
    if (d) d.textContent = 'Central brain unreachable (' + DAEMON_URL + ').';
  }
}
document.addEventListener('DOMContentLoaded', () => {
  ensureBrainOverlay();
  checkBrain();
  setInterval(checkBrain, 5000);
});

// Model Merkezi (son-kullanıcı paneli) — isteyen indirir. Daemon kendi başına indirmez.
async function loadModelCenter(retry = 0) {
  const box = document.getElementById('model-center-list');
  const note = document.getElementById('model-center-note');
  if (!box) return;
  try {
    const items = await apiRequest('/api/models/catalog');
    box.innerHTML = '';
    let lastBranch = '';
    (items || []).forEach(m => {
      if (m.branch !== lastBranch) {
        const h = document.createElement('div');
        h.style.cssText = 'color:var(--text-muted,#8c9ba5); margin-top:4px; text-transform:uppercase; font-size:10px;';
        h.textContent = m.branch;
        box.appendChild(h); lastBranch = m.branch;
      }
      const row = document.createElement('div');
      row.style.cssText = 'display:flex; justify-content:space-between; align-items:center; gap:8px;';
      const lbl = document.createElement('span');
      lbl.textContent = `${m.label} (${m.size_gb.toFixed(1)}GB)`;
      const act = document.createElement('span');
      if (m.installed) { act.textContent = '✓ kurulu'; act.style.color = '#22c55e'; }
      else if (m.engine === 'external') { act.textContent = '🔗 harici'; act.style.color = '#f59e0b'; act.title = m.note; }
      else {
        const btn = document.createElement('button');
        btn.textContent = '⬇ download';
        btn.style.cssText = 'background:rgba(0,229,255,0.12); border:1px solid rgba(0,229,255,0.3); color:#00e5ff; border-radius:5px; font-size:11px; padding:3px 8px; cursor:pointer;';
        btn.onclick = async () => {
          if (!confirm(`${m.label} (~${m.size_gb.toFixed(1)}GB) indirilsin mi?\nLisans: ${m.license}`)) return;
          btn.textContent = '…'; btn.disabled = true;
          try {
            const j = await apiRequest('/api/models/install', 'POST', { id: m.id });
            if (note) note.textContent = j.status || j.error || '';
            setTimeout(() => loadModelCenter(), 8000);
          } catch (e) { if (note) note.textContent = 'Could not start download.'; btn.disabled = false; btn.textContent = '⬇ download'; }
        };
        act.appendChild(btn);
      }
      row.appendChild(lbl); row.appendChild(act); box.appendChild(row);
    });
  } catch (e) {
    if (retry < 5) setTimeout(() => loadModelCenter(retry + 1), 2500);
  }
}

// Son-kullanıcı paneli dosya erişimi — /api/workspace + /api/file (daemon 5318).
async function loadFileBrowser(retry = 0) {
  const sel = document.getElementById('file-browser-select');
  const view = document.getElementById('file-content-view');
  if (!sel) return;
  try {
    const files = await apiRequest('/api/workspace');
    sel.innerHTML = '<option value="">Select file…</option>';
    (files || []).forEach(f => {
      const o = document.createElement('option');
      o.value = f; o.textContent = f;
      sel.appendChild(o);
    });
    sel.onchange = async () => {
      if (!sel.value) { view.textContent = ''; return; }
      view.textContent = 'Loading…';
      try {
        const content = await apiRequest(`/api/file?path=${encodeURIComponent(sel.value)}`);
        view.textContent = (typeof content === 'string' ? content : JSON.stringify(content, null, 2)).slice(0, 20000);
      } catch (e) { view.textContent = 'Could not read file: ' + e.message; }
    };
  } catch (e) {
    if (retry < 5) setTimeout(() => loadFileBrowser(retry + 1), 2500);
  }
}

// Masaüstü dosya tarayıcı (NB2-3) — /api/desktop + /api/desktop/file (sandbox: ~/Desktop).
let _desktopSub = '';
async function loadDesktopBrowser() {
  const sel = document.getElementById('desktop-browser-select');
  const view = document.getElementById('desktop-content-view');
  if (!sel) return;
  try {
    const data = await apiRequest(`/api/desktop?sub=${encodeURIComponent(_desktopSub)}`);
    const entries = (data && data.entries) || [];
    sel.innerHTML = '';
    const head = document.createElement('option');
    head.value = '';
    head.textContent = _desktopSub ? `📂 ${_desktopSub}` : 'Select from Desktop…';
    sel.appendChild(head);
    if (_desktopSub) {
      const up = document.createElement('option');
      up.value = '..UP'; up.textContent = '⬆️ ..'; sel.appendChild(up);
    }
    entries.forEach(e => {
      const o = document.createElement('option');
      o.value = e;
      o.textContent = e.startsWith('d:') ? '📁 ' + e.slice(2) : '📄 ' + e.slice(2);
      sel.appendChild(o);
    });
    sel.onchange = async () => {
      const v = sel.value;
      if (!v) return;
      if (v === '..UP') { _desktopSub = _desktopSub.split('/').slice(0, -1).join('/'); loadDesktopBrowser(); return; }
      const name = v.slice(2);
      const rel = _desktopSub ? `${_desktopSub}/${name}` : name;
      if (v.startsWith('d:')) { _desktopSub = rel; loadDesktopBrowser(); return; }
      view.textContent = 'Loading…';
      try {
        const content = await apiRequest(`/api/desktop/file?path=${encodeURIComponent(rel)}`);
        view.textContent = (typeof content === 'string' ? content : JSON.stringify(content, null, 2)).slice(0, 20000);
      } catch (e) { view.textContent = 'Could not read file: ' + e.message; }
    };
  } catch (e) { /* daemon not ready */ }
}

// Son-kullanıcı panelinde aktif zekâ (model) seçici — daemon (5318) ile.
async function loadModelSelector(retry = 0) {
  const sel = document.getElementById('llm-model-select');
  const note = document.getElementById('llm-model-note');
  if (!sel) return;
  try {
    const [models, cur] = await Promise.all([
      apiRequest('/api/llm/models'),
      apiRequest('/api/llm/model'),
    ]);
    sel.innerHTML = '<option value="">Automatic (per task)</option>';
    (models || []).forEach(m => {
      const o = document.createElement('option');
      o.value = m; o.textContent = m;
      sel.appendChild(o);
    });
    sel.value = cur.preferred || '';
    let txt = cur.preferred ? `Manual: ${cur.preferred}` : `Automatic · heavy: ${cur.effective || '—'}`;
    if (cur.last_path && cur.last_model) {
      txt += ` · Last: ${cur.last_path === 'ağır' ? '🧠' : '⚡'} ${cur.last_model}`;
    }
    note.textContent = txt;
    sel.onchange = async () => {
      try {
        const r = await apiRequest('/api/llm/model', 'POST', { name: sel.value });
        note.textContent = r.status || r.error || '—';
        setTimeout(() => loadModelSelector(), 400);
      } catch (e) { note.textContent = 'Could not set model.'; }
    };
  } catch (e) {
    // Daemon henüz hazır değilse birkaç kez yeniden dene.
    if (retry < 5) setTimeout(() => loadModelSelector(retry + 1), 2500);
    else if (note) note.textContent = 'No brain connection.';
  }
}

// Helper: API Request
async function apiRequest(path, method = 'GET', body = null) {
  const url = `${DAEMON_URL}${path}`;
  const options = {
    method,
    headers: {
      'Content-Type': 'application/json',
    },
  };
  if (body) {
    options.body = JSON.stringify(body);
  }
  
  try {
    const response = await fetch(url, options);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    // Check if response is text or json
    const contentType = response.headers.get('content-type');
    if (contentType && contentType.includes('application/json')) {
      return await response.json();
    } else {
      return await response.text();
    }
  } catch (e) {
    console.error(`API Request failed for ${path}:`, e);
    throw e;
  }
}

// Progress Ring calculation
function setProgress(percent) {
  const radius = 52;
  const circumference = 2 * Math.PI * radius;
  const offset = circumference - (percent / 100) * circumference;
  els.circle.style.strokeDashoffset = offset;
  els.percentText.textContent = `${Math.round(percent)}%`;
  els.progressBar.style.width = `${percent}%`;
}

// Smooth progress animation helper
async function _animateTo(currentRef, target, text) {
  els.phaseText.textContent = text;
  while (currentRef.value < target) {
    currentRef.value = Math.min(currentRef.value + 2, target);
    setProgress(currentRef.value);
    await new Promise(r => setTimeout(r, 25));
  }
}

// Boot loader: real phase-gated progress (no random, no ZKP mock)
async function startLoadingSimulation() {
  const prog = { value: 0 };
  setProgress(0);

  // Phase 0 — wait for daemon (blocking poll)
  els.phaseText.textContent = 'Waiting for Central Intelligence (Daemon) connection...';
  while (true) {
    try {
      await apiRequest('/api/agent/identity');
      break;
    } catch (_) {
      els.phaseText.textContent = 'Starting daemon... launching DCOG-OS background service.';
      await new Promise(r => setTimeout(r, 2000));
    }
  }

  // Phase 1 — security kernel init
  await _animateTo(prog, 20, 'Starting DCOG-OS Security Core...');

  // Phase 2 — sovereign crypto stack (Argon2id, XChaCha20-Poly1305, Ed25519⊕ML-DSA-87)
  await _animateTo(prog, 40, 'Sovereign Cryptography Layer Ready (Argon2id · XChaCha20-Poly1305 · Ed25519⊕ML-DSA-87)...');

  // Phase 3 — local memory DB (real ping)
  await apiRequest('/api/memory/list_local').catch(() => []);
  await _animateTo(prog, 60, 'Loading Local SQLite Memory Database...');

  // Phase 4 — P2P network layer (real ping)
  await apiRequest('/api/telemetry').catch(() => ({}));
  await _animateTo(prog, 80, 'Establishing P2P Distributed Network (libp2p Kademlia + GossipSub)...');

  // Phase 5 — agent identity + n8n service check (real ping)
  await apiRequest('/api/n8n/status').catch(() => ({}));
  await _animateTo(prog, 95, 'Preparing Agent Identity and Service Layer...');

  // Phase 6 — complete
  await _animateTo(prog, 100, 'Core System Ready. Sovereign Agent Active!');

  // Fetch initial backend state
  try {
    await fetchInitialData();
  } catch (err) {
    console.error("Initial data load failed:", err);
  }

  // Transition loading screen to main app
  setTimeout(() => {
    els.overlay.style.opacity = '0';
    els.overlay.style.visibility = 'hidden';
    els.app.classList.remove('hidden');
    speakGreeting();
  }, 800);
}

// ────────────────────────────────────────────────────────────────
// FETCH DATA FROM DAEMON
// ────────────────────────────────────────────────────────────────

async function fetchInitialData() {
  // 1. Get Agent Identity
  const identity = await apiRequest('/api/agent/identity');
  updateIdentityUI(identity);
  
  // 2. Get n8n Status
  const n8nStatus = await apiRequest('/api/n8n/status');
  updateN8nUI(n8nStatus);
  
  // 3. Get Autonomous Agent Types
  const agentTypes = await apiRequest('/api/agents/types');
  populateAgentTypes(agentTypes);
  
  // 4. Get Chat History
  await loadChatHistory();
  
  // 5. Get Autonomous Agents List
  await loadAutonomousAgents();
  
  // 6. Start telemetry polling interval (every 5 seconds)
  state.telemetryIntervalId = setInterval(fetchTelemetryData, 5000);
  // Initial run
  await fetchTelemetryData();
}

// H8 — Upgrade banner helpers
function _showUpgradeBanner(localV, latestV, localQ) {
  if (!els.modelUpgradeBanner) return;
  if (els.upgradeBannerMsg) {
    els.upgradeBannerMsg.textContent =
      `⬆️ New version available: ${localV} → ${latestV} (${localQ})`;
  }
  els.modelUpgradeBanner.classList.remove('hidden');
  if (els.btnModelUpgrade) {
    els.btnModelUpgrade.disabled = false;
    els.btnModelUpgrade.textContent = `⬆️ Update (${localQ})`;
  }
}
function _hideUpgradeBanner() {
  if (els.modelUpgradeBanner) els.modelUpgradeBanner.classList.add('hidden');
}

async function fetchTelemetryData() {
  try {
    const telemetry = await apiRequest('/api/telemetry');
    
    // Peer count
    const peers = telemetry.peers || [];
    els.telPeers.textContent = peers.length;
    
    // Global Sleep
    if (telemetry.global_sleep) {
      els.telSleep.textContent = "Offline (Sleep)";
      els.telSleep.className = "telemetry-value status-offline";
    } else {
      els.telSleep.textContent = "Aktif";
      els.telSleep.className = "telemetry-value status-online";
    }
    
    // Get local and shared memory records count
    const localList = await apiRequest('/api/memory/list_local');
    els.telLocalMem.textContent = localList.length;
    
    const sharedList = await apiRequest('/api/memory/shared/list');
    els.telSharedMem.textContent = sharedList.length;

    // G1/H8: VRAM and model status + upgrade check
    const allocatedVram = telemetry.allocated_vram_gb || 0;
    els.telVram.textContent = `${allocatedVram} GB`;
    const weights = telemetry.weights || [];
    els.telWeightsCount.textContent = weights.length;
    const modelReady = weights.length > 0;
    if (modelReady) {
      // H8: Model hazırsa versiyon/upgrade kontrolü yap
      try {
        const versionInfo = await apiRequest('/api/model/version');
        if (versionInfo.upgrade_available) {
          const localV = versionInfo.local_version || '?';
          const latestV = versionInfo.latest_version;
          const localQ = versionInfo.local_quant || 'Q4_K_M';
          els.telModelStatus.textContent = `⬆️ Update available (${localQ})`;
          els.telModelStatus.className = 'telemetry-value';
          els.telModelStatus.title = `Mevcut: ${localV} → Yeni: ${latestV}`;
          // Upgrade banner'ı göster
          _showUpgradeBanner(localV, latestV, localQ);
        } else {
          const localQ = versionInfo.local_quant || '';
          els.telModelStatus.textContent = `✅ Ready${localQ ? ' (' + localQ + ')' : ''}`;
          els.telModelStatus.className = 'telemetry-value status-online';
          _hideUpgradeBanner();
        }
      } catch (_) {
        els.telModelStatus.textContent = '✅ Ready';
        els.telModelStatus.className = 'telemetry-value status-online';
      }
      els.modelDownloadBanner.classList.add('hidden');
      els.modelVramLockBanner.classList.remove('hidden');
    } else {
      els.telModelStatus.textContent = '❌ Not Installed';
      els.telModelStatus.className = 'telemetry-value status-offline';
      els.modelDownloadBanner.classList.remove('hidden');
      els.modelVramLockBanner.classList.add('hidden');
      _hideUpgradeBanner();
    }

    // Keep local memory syncing
    const identity = await apiRequest('/api/agent/identity');
    if (identity.name !== state.agentName) {
      updateIdentityUI(identity);
    }
    
    // n8n status update
    const n8nStatus = await apiRequest('/api/n8n/status');
    updateN8nUI(n8nStatus);
    
    // Refresh autonomous agents
    await loadAutonomousAgents();
    
  } catch (e) {
    console.error("Telemetry fetch failed:", e);
  }
}

// ────────────────────────────────────────────────────────────────
// COMPONENT UPDATES
// ────────────────────────────────────────────────────────────────

function updateIdentityUI(identity) {
  state.agentName = identity.name || 'Dudu';
  state.agentPersonality = identity.personality || '';
  state.agentEmoji = identity.avatar_emoji || '';
  state.agentLanguage = identity.language || 'tr';
  
  // UI Display
  els.agentAvatar.textContent = state.agentEmoji;
  els.agentNameDisplay.textContent = state.agentName;
  els.chatAgentName.textContent = state.agentName;
  els.chatAgentPersonality.textContent = state.agentPersonality;
  
  // Form Inputs
  els.identityName.value = state.agentName;
  els.identityPersonality.value = state.agentPersonality;
  els.identityEmoji.value = state.agentEmoji;
  els.identityLang.value = state.agentLanguage;
}

function updateN8nUI(status) {
  state.n8nInstalled = status.installed;
  state.n8nRunning = status.running;
  state.n8nUrl = status.url || 'http://localhost:5678';
  
  if (state.n8nRunning) {
    els.n8nStatusBadge.textContent = 'Running';
    els.n8nStatusBadge.className = 'status-badge status-online';
    
    els.btnN8nInstall.classList.add('hidden');
    els.btnN8nStart.classList.add('hidden');
    els.btnN8nStop.classList.remove('hidden');
    els.linkN8nPanel.classList.remove('hidden');
    els.linkN8nPanel.href = state.n8nUrl;
  } else if (state.n8nInstalled) {
    els.n8nStatusBadge.textContent = 'Durduruldu';
    els.n8nStatusBadge.className = 'status-badge status-offline';
    
    els.btnN8nInstall.classList.add('hidden');
    els.btnN8nStart.classList.remove('hidden');
    els.btnN8nStop.classList.add('hidden');
    els.linkN8nPanel.classList.add('hidden');
  } else {
    els.n8nStatusBadge.textContent = 'Not Installed';
    els.n8nStatusBadge.className = 'status-badge status-offline';
    
    els.btnN8nInstall.classList.remove('hidden');
    els.btnN8nStart.classList.add('hidden');
    els.btnN8nStop.classList.add('hidden');
    els.linkN8nPanel.classList.add('hidden');
  }
  
  if (status.version) {
    els.n8nVersionRow.classList.remove('hidden');
    els.n8nVersionVal.textContent = status.version;
  } else {
    els.n8nVersionRow.classList.add('hidden');
  }
}

function populateAgentTypes(types) {
  els.agentTaskType.innerHTML = '';
  types.forEach(type => {
    const option = document.createElement('option');
    option.value = type;
    // Capitalize first letter
    option.textContent = type.charAt(0).toUpperCase() + type.slice(1);
    els.agentTaskType.appendChild(option);
  });
}

// ────────────────────────────────────────────────────────────────
// CHAT FUNCTIONALITY
// ────────────────────────────────────────────────────────────────

async function speakGreeting() {
  try {
    const greeting = await apiRequest('/api/agent/greeting');
    appendMessage('agent', greeting);
    if (state.ttsEnabled) {
      await speakText(greeting);
    }
  } catch (e) {
    console.error("Failed to fetch greeting:", e);
    appendMessage('agent', `Hello! I'm ${state.agentName}. How can I help you today?`);
  }
}

async function loadChatHistory() {
  try {
    const history = await apiRequest(`/api/memory/conversation/history?session_id=${SESSION_ID}&limit=50`);
    els.chatMessages.innerHTML = '';
    
    if (history.length === 0) {
      return;
    }
    
    history.reverse().forEach(msg => {
      appendMessage(msg.role, msg.content, false);
    });
    
    scrollToBottom();
  } catch (e) {
    console.error("Failed to load chat history:", e);
  }
}

function appendMessage(role, content, scroll = true) {
  const wrapper = document.createElement('div');
  wrapper.className = `message-wrapper ${role === 'user' ? 'user' : 'agent'}`;
  
  const sender = document.createElement('span');
  sender.className = 'message-sender';
  sender.textContent = role === 'user' ? 'Siz' : state.agentName;
  
  const bubble = document.createElement('div');
  bubble.className = 'message-bubble';
  
  // Simple markdown format mapping (bold and codeblocks)
  bubble.innerHTML = formatMarkdown(content);
  
  wrapper.appendChild(sender);
  wrapper.appendChild(bubble);
  els.chatMessages.appendChild(wrapper);
  
  if (scroll) {
    scrollToBottom();
  }
}

function formatMarkdown(text) {
  // GÜVENLİK: önce TÜM metni escape et (ham HTML/script innerHTML'e GİREMEZ → XSS yok),
  // sonra markdown işaretlerini uygula. Markdown karakterleri (` * \n) escape'ten etkilenmez.
  let html = escapeHTML(text);

  // Code blocks (içerik zaten escape'li)
  html = html.replace(/```([\s\S]*?)```/g, (match, code) => `<pre><code>${code.trim()}</code></pre>`);
  // Inline code
  html = html.replace(/`([^`\n]+)`/g, (match, code) => `<code>${code}</code>`);
  // Bold
  html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
  // Paragraphs
  html = html.split('\n\n').map(p => `<p>${p.replace(/\n/g, '<br>')}</p>`).join('');

  return html;
}

function escapeHTML(str) {
  return str.replace(/[&<>'"]/g, 
    tag => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#39;', '"': '&quot;' }[tag] || tag)
  );
}

function scrollToBottom() {
  els.chatMessages.scrollTop = els.chatMessages.scrollHeight;
}

async function handleSendMessage(isVoice = false) {
  const text = els.chatInput.value.trim();
  if (!text) return;
  
  els.chatInput.value = '';
  appendMessage('user', text);
  
  // Save user message to database
  try {
    await apiRequest('/api/memory/conversation/save', 'POST', {
      role: 'user',
      content: text,
      session_id: SESSION_ID
    });
  } catch (e) {
    console.error("Failed to save user conversation:", e);
  }
  
  // Append loading placeholder for AI
  const typingWrapper = document.createElement('div');
  typingWrapper.className = 'message-wrapper agent typing-placeholder';
  typingWrapper.innerHTML = `
    <span class="message-sender">${state.agentName}</span>
    <div class="message-bubble" style="font-style:italic; color:var(--text-muted)">Thinking...</div>
  `;
  els.chatMessages.appendChild(typingWrapper);
  scrollToBottom();
  
  try {
    // Send to backend via search API
    const reply = await apiRequest('/api/search', 'POST', { prompt: text, session_id: SESSION_ID });
    
    // Remove typing placeholder
    typingWrapper.remove();
    
    // Append response
    appendMessage('agent', reply);
    
    // Save reply to database
    await apiRequest('/api/memory/conversation/save', 'POST', {
      role: 'assistant',
      content: reply,
      session_id: SESSION_ID
    });
    
    // Sesli komut → sesli yanıt; yazılı komut → yalnızca yazılı yanıt.
    // (ttsEnabled master-mute olarak kalır: kapalıysa seste bile konuşmaz.)
    if (isVoice && state.ttsEnabled) {
      await speakText(reply);
    }
  } catch (e) {
    typingWrapper.remove();
    appendMessage('agent', 'Error: Could not connect to central intelligence. Please make sure the daemon service is running.');
  }
}

// TTS Audio play
let currentAudio = null;
async function speakText(text) {
  // Stop currently playing audio if any
  if (currentAudio) {
    currentAudio.pause();
    currentAudio = null;
  }
  
  try {
    // Call speak_text API
    const base64Audio = await apiRequest('/api/speak_text', 'POST', { text });
    if (!base64Audio) return;
    
    // Create audio source
    const audioSrc = `data:audio/wav;base64,${base64Audio}`;
    currentAudio = new Audio(audioSrc);
    currentAudio.play();
  } catch (e) {
    console.error("Speech synthesis failed:", e);
  }
}

// ────────────────────────────────────────────────────────────────
// AUTONOMOUS AGENTS ORCHESTRATION
// ────────────────────────────────────────────────────────────────

async function loadAutonomousAgents() {
  try {
    const agents = await apiRequest('/api/agents/list');
    els.autonomousAgentsList.innerHTML = '';
    
    if (agents.length === 0) {
      els.autonomousAgentsList.innerHTML = '<div class="empty-state">No active autonomous tasks.</div>';
      return;
    }
    
    agents.forEach(agent => {
      const card = document.createElement('div');
      card.className = 'agent-card';
      card.dataset.id = agent.agent_id;
      
      // Status mapping
      let statusClass = 'running';
      let statusText = 'Running';
      if (agent.status === 'completed') {
        statusClass = 'completed';
        statusText = 'Completed';
      } else if (agent.status === 'failed') {
        statusClass = 'failed';
        statusText = 'Failed';
      } else if (agent.status === 'paused') {
        statusClass = 'failed';
        statusText = 'Paused';
      }
      
      card.innerHTML = `
        <div class="agent-card-header">
          <span class="agent-card-name">${escapeHTML(agent.name)}</span>
          <span class="agent-card-type">${escapeHTML(agent.agent_type)}</span>
        </div>
        <div class="agent-card-goal" title="${escapeHTML(agent.goal)}">${escapeHTML(agent.goal)}</div>
        <div class="agent-card-footer">
          <span class="agent-card-status">
            <span class="agent-card-status-dot ${statusClass}"></span>
            <span>${statusText}</span>
          </span>
          <div style="display:flex; gap:6px;">
            <button class="btn-text btn-view-logs" data-id="${agent.agent_id}" data-name="${escapeHTML(agent.name)}">Log</button>
            <button class="btn-text btn-delete-agent" data-id="${agent.agent_id}" style="color:var(--status-offline)">Sil</button>
          </div>
        </div>
      `;
      els.autonomousAgentsList.appendChild(card);
    });
    
    // Attach card actions
    document.querySelectorAll('.btn-view-logs').forEach(btn => {
      btn.addEventListener('click', (e) => {
        const id = e.target.dataset.id;
        const name = e.target.dataset.name;
        startLogPolling(id, name);
      });
    });
    
    document.querySelectorAll('.btn-delete-agent').forEach(btn => {
      btn.addEventListener('click', async (e) => {
        const id = e.target.dataset.id;
        if (confirm('Are you sure you want to delete this autonomous agent?')) {
          try {
            await apiRequest('/api/agents/delete', 'POST', { agent_id: id });
            if (state.activeLogAgentId === id) {
              stopLogPolling();
            }
            await loadAutonomousAgents();
          } catch (err) {
            alert('Ajan silinemedi: ' + err.message);
          }
        }
      });
    });
    
  } catch (e) {
    console.error("Failed to load autonomous agents:", e);
  }
}

async function handleLaunchAgent() {
  const name = els.agentTaskName.value.trim();
  const type = els.agentTaskType.value;
  const goal = els.agentTaskGoal.value.trim();
  
  if (!name || !goal) {
    alert('Agent name and goal cannot be empty.');
    return;
  }
  
  try {
    els.btnLaunchAgent.disabled = true;
    els.btnLaunchAgent.querySelector('span').textContent = 'Starting...';
    
    await apiRequest('/api/agents/create', 'POST', { name, agent_type: type, goal });
    
    els.agentTaskName.value = '';
    els.agentTaskGoal.value = '';
    
    await loadAutonomousAgents();
  } catch (e) {
    alert('Could not start agent: make sure the local n8n service is installed and running.');
  } finally {
    els.btnLaunchAgent.disabled = false;
    els.btnLaunchAgent.querySelector('span').textContent = 'Start Agent';
  }
}

function startLogPolling(agentId, agentName) {
  stopLogPolling();
  
  state.activeLogAgentId = agentId;
  els.activeLogAgentName.textContent = `Selected Agent: ${agentName}`;
  els.consoleLogs.innerHTML = '<div class="log-line system">> Console connection established. Fetching logs...</div>';
  
  pollLogs();
  state.logIntervalId = setInterval(pollLogs, 2500);
}

function stopLogPolling() {
  if (state.logIntervalId) {
    clearInterval(state.logIntervalId);
    state.logIntervalId = null;
  }
  state.activeLogAgentId = null;
  els.activeLogAgentName.textContent = 'Selected Agent: None';
  els.consoleLogs.innerHTML = '<div class="console-placeholder">> Click the log button of a running autonomous agent above to view console output.</div>';
}

async function pollLogs() {
  if (!state.activeLogAgentId) return;
  
  try {
    const logs = await apiRequest(`/api/agents/logs?agent_id=${state.activeLogAgentId}`);
    els.consoleLogs.innerHTML = '';
    
    if (logs.length === 0) {
      els.consoleLogs.innerHTML = '<div class="console-placeholder">> No console records for this agent yet.</div>';
      return;
    }
    
    logs.forEach(line => {
      const row = document.createElement('div');
      row.className = 'log-line';
      
      if (line.toLowerCase().includes('error') || line.toLowerCase().includes('fail')) {
        row.classList.add('error');
      } else if (line.startsWith('>')) {
        row.classList.add('system');
      }
      
      row.textContent = line;
      els.consoleLogs.appendChild(row);
    });
    
    // Auto scroll console
    els.consoleLogs.scrollTop = els.consoleLogs.scrollHeight;
  } catch (e) {
    console.error("Failed to poll agent logs:", e);
  }
}

// ────────────────────────────────────────────────────────────────
// EVENT LISTENERS & INTERACTIONS
// ────────────────────────────────────────────────────────────────

function initAppEvents() {
  // 1. Save Identity
  els.btnSaveIdentity.addEventListener('click', async () => {
    const name = els.identityName.value.trim();
    const personality = els.identityPersonality.value.trim();
    const emoji = els.identityEmoji.value.trim();
    const lang = els.identityLang.value;
    
    if (!name) {
      alert('Agent name cannot be empty!');
      return;
    }
    
    try {
      els.btnSaveIdentity.disabled = true;
      els.btnSaveIdentity.querySelector('span').textContent = 'Kaydediliyor...';
      
      const identity = await apiRequest('/api/agent/update_identity', 'POST', {
        name,
        personality,
        language: lang,
        avatar_emoji: emoji
      });
      
      updateIdentityUI(identity);
      alert('Agent identity saved successfully and written to local memory!');
    } catch (e) {
      alert('Hata: ' + e.message);
    } finally {
      els.btnSaveIdentity.disabled = false;
      els.btnSaveIdentity.querySelector('span').textContent = 'Update Identity';
    }
  });
  
  // 2. n8n Actions
  els.btnN8nInstall.addEventListener('click', async () => {
    try {
      els.btnN8nInstall.disabled = true;
      els.btnN8nInstall.textContent = 'Kuruluyor...';
      await apiRequest('/api/n8n/install', 'POST');
      alert('n8n installation started in the background! Status will update when it finishes (this may take a few minutes).');
    } catch (e) {
      alert('Could not start installation: ' + e.message);
      els.btnN8nInstall.disabled = false;
      els.btnN8nInstall.textContent = 'n8n Kur';
    }
  });
  
  els.btnN8nStart.addEventListener('click', async () => {
    try {
      await apiRequest('/api/n8n/start', 'POST');
      alert('Starting n8n service...');
    } catch (e) {
      alert('Could not start n8n: ' + e.message);
    }
  });
  
  els.btnN8nStop.addEventListener('click', async () => {
    try {
      await apiRequest('/api/n8n/stop', 'POST');
      alert('n8n servisi durduruluyor...');
    } catch (e) {
      alert('Could not stop n8n: ' + e.message);
    }
  });
  
  // 3. Chat Controls
  els.chatForm.addEventListener('submit', (e) => {
    e.preventDefault();
    handleSendMessage();
  });
  
  els.btnClearHistory.addEventListener('click', async () => {
    if (confirm('Are you sure you want to completely clear your chat history? This deletes all chat logs in local memoryecektir.')) {
      try {
        await apiRequest('/api/memory/conversation/delete_session', 'POST', { session_id: SESSION_ID });
        els.chatMessages.innerHTML = '';
        appendMessage('agent', `Chat history successfully deleted from local memory. I'm **${state.agentName}**, ready to serve you.`);
      } catch (e) {
        alert('Could not delete history: ' + e.message);
      }
    }
  });
  
  els.btnToggleTts.addEventListener('click', () => {
    state.ttsEnabled = !state.ttsEnabled;
    if (state.ttsEnabled) {
      els.ttsIconOn.classList.remove('hidden');
      els.ttsIconOff.classList.add('hidden');
      els.btnToggleTts.title = "Mute Voice Reply";
    } else {
      els.ttsIconOn.classList.add('hidden');
      els.ttsIconOff.classList.remove('hidden');
      els.btnToggleTts.title = "Enable Voice Reply";
      if (currentAudio) {
        currentAudio.pause();
        currentAudio = null;
      }
    }
  });
  
  // 3b. G1/H7: Model Download Button — quantization-aware
  if (els.btnModelDownload) {
    // Quant değişince açıklamayı güncelle
    const QUANT_DESCS = {
      'Q4_0':  'Smallest size, fastest inference. Slightly lower quality.',
      'Q4_K_M': 'Best quality/size balance. Default.',
      'Q5_K_M': 'Higher accuracy, slightly more VRAM.',
      'Q8_0':  'Neredeyse tam hassasiyet. En az 3 GB VRAM gerektirir.',
    };
    if (els.modelQuantSelect) {
      els.modelQuantSelect.addEventListener('change', () => {
        const q = els.modelQuantSelect.value;
        if (els.quantDesc) els.quantDesc.textContent = QUANT_DESCS[q] || '';
      });
    }

    els.btnModelDownload.addEventListener('click', async () => {
      const quant = els.modelQuantSelect ? els.modelQuantSelect.value : 'Q4_K_M';
      const label = els.modelQuantSelect
        ? els.modelQuantSelect.options[els.modelQuantSelect.selectedIndex].text
        : 'Q4_K_M';

      const confirmed = confirm(
        `Model download will start.\n` +
        `• Selected quantization: ${label}\n` +
        `• Required for inference.\n\n` +
        `Do you want to continue?`
      );
      if (!confirmed) return;

      els.btnModelDownload.disabled = true;
      els.btnModelDownload.textContent = `⏳ Downloading (${quant})...`;
      try {
        await apiRequest('/api/allocate_local_weight', 'POST', { expertId: 1, quantization: quant });
        if (els.telModelStatus) {
          els.telModelStatus.textContent = `⬇️ Downloading (${quant})...`;
          els.telModelStatus.className = 'telemetry-value';
        }
      } catch (e) {
        alert('Model download error: ' + e.message);
        els.btnModelDownload.disabled = false;
        els.btnModelDownload.textContent = '⬇️ Download Model';
      }
    });
  }

  // 3c. H8: Model Upgrade Button
  if (els.btnModelUpgrade) {
    els.btnModelUpgrade.addEventListener('click', async () => {
      const confirmed = confirm(
        'Model will be updated. The current GGUF file will be deleted and re-downloaded.\n' +
        'The operation runs in the background.\n\nDo you want to continue?'
      );
      if (!confirmed) return;
      els.btnModelUpgrade.disabled = true;
      els.btnModelUpgrade.textContent = '⏳ Updating...';
      try {
        await apiRequest('/api/model/upgrade', 'POST', {});
        if (els.telModelStatus) {
          els.telModelStatus.textContent = '⬇️ Downloading update...';
          els.telModelStatus.className = 'telemetry-value';
        }
        _hideUpgradeBanner();
      } catch (e) {
        alert('Update error: ' + e.message);
        els.btnModelUpgrade.disabled = false;
        els.btnModelUpgrade.textContent = '⬆️ Update (mevcut quant ile)';
      }
    });
  }

  // 4. Voice Input (Speech to Text)
  initSpeechRecognition();
  els.btnVoiceInput.addEventListener('click', toggleSpeechRecognition);

  // 5. Launch Agent
  els.btnLaunchAgent.addEventListener('click', handleLaunchAgent);
  
  // 6. Clear Console
  els.btnClearConsole.addEventListener('click', () => {
    els.consoleLogs.innerHTML = '';
  });
  
  // 7. Client Tab Switcher & NotebookLM Init
  const tabChat = document.getElementById('tab-client-chat');
  const tabNotebook = document.getElementById('tab-client-notebook');
  const chatView = document.getElementById('client-chat-view');
  const notebookView = document.getElementById('client-notebook-view');
  
  if (tabChat && tabNotebook && chatView && notebookView) {
    tabChat.addEventListener('click', () => {
      tabChat.classList.add('active');
      tabNotebook.classList.remove('active');
      chatView.classList.remove('hidden');
      notebookView.classList.add('hidden');
    });
    
    tabNotebook.addEventListener('click', async () => {
      tabNotebook.classList.add('active');
      tabChat.classList.remove('active');
      notebookView.classList.remove('hidden');
      chatView.classList.add('hidden');
      await refreshClientNotebookList();
    });
  }
  initClientNotebookLMEvents();
}

// Fallback mic recording variables
let mediaStream = null;
let audioContext = null;
let audioProcessorNode = null;
let voiceAnalysisInterval = null;
let leftChannelPCM = [];
let isRecordingFallback = false;

function bufferToWav(buffer, sampleRate) {
 let numOfChan = 1,
   length = buffer.length * 2 + 44,
   bufferArr = new ArrayBuffer(length),
   view = new DataView(bufferArr),
   pos = 0;

 function setUint16(data) {
  view.setUint16(pos, data, true);
  pos += 2;
 }

 function setUint32(data) {
  view.setUint32(pos, data, true);
  pos += 4;
 }

 // write WAVE header
 setUint32(0x46464952);             // "RIFF"
 setUint32(length - 8);             // file length - 8
 setUint32(0x45564157);             // "WAVE"
 setUint32(0x20746d66);             // "fmt " chunk
 setUint32(16);                 // chunk length
 setUint16(1);                 // sample format (raw PCM)
 setUint16(numOfChan);             // channel count
 setUint32(sampleRate);             // sample rate
 setUint32(sampleRate * 2);           // byte rate
 setUint16(2);                 // block align
 setUint16(16);                 // bits per sample
 setUint32(0x61746164);             // "data" chunk identifier
 setUint32(buffer.length * 2);         // chunk length

 // write PCM audio samples
 for (let i = 0; i < buffer.length; i++) {
  let sample = Math.max(-1, Math.min(1, buffer[i]));
  sample = sample < 0 ? sample * 0x8000 : sample * 0x7FFF;
  view.setInt16(pos, sample, true);
  pos += 2;
 }

 return new Blob([bufferArr], { type: 'audio/wav' });
}

async function startMicFallbackRecording() {
  leftChannelPCM = [];
  isRecordingFallback = true;
  els.btnVoiceInput.classList.add('listening');
  els.chatInput.placeholder = 'Listening... Please speak.';
  
  try {
    mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
    const localContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 16000 });
    audioContext = localContext;
    
    const source = localContext.createMediaStreamSource(mediaStream);
    const processor = localContext.createScriptProcessor(4096, 1, 1);
    audioProcessorNode = processor;
    
    const analyser = localContext.createAnalyser();
    analyser.fftSize = 512;
    source.connect(analyser);
    
    const bufferLength = analyser.frequencyBinCount;
    const dataArray = new Float32Array(bufferLength);
    
    processor.onaudioprocess = function(e) {
      if (!isRecordingFallback) return;
      const left = e.inputBuffer.getChannelData(0);
      leftChannelPCM.push(new Float32Array(left));
    };
    
    source.connect(processor);
    processor.connect(localContext.destination);
    
    let hasSpoken = false;
    let silenceDetectedAt = null;
    const recordStart = Date.now();
    
    voiceAnalysisInterval = setInterval(async () => {
      if (!isRecordingFallback) {
        clearInterval(voiceAnalysisInterval);
        voiceAnalysisInterval = null;
        return;
      }
      
      // 6 seconds maximum recording window
      if (Date.now() - recordStart > 6000) {
        clearInterval(voiceAnalysisInterval);
        voiceAnalysisInterval = null;
        await stopAndTranscribeWavFallback();
        return;
      }
      
      analyser.getFloatTimeDomainData(dataArray);
      let sum = 0;
      for (let i = 0; i < bufferLength; i++) sum += dataArray[i] * dataArray[i];
      const rms = Math.sqrt(sum / bufferLength);
      
      if (rms > 0.004) {
        hasSpoken = true;
        silenceDetectedAt = null;
      } else if (hasSpoken) {
        if (!silenceDetectedAt) {
          silenceDetectedAt = Date.now();
        } else if (Date.now() - silenceDetectedAt > 1300) {
          clearInterval(voiceAnalysisInterval);
          voiceAnalysisInterval = null;
          await stopAndTranscribeWavFallback();
        }
      }
    }, 100);
  } catch (err) {
    isRecordingFallback = false;
    els.btnVoiceInput.classList.remove('listening');
    els.chatInput.placeholder = 'Ask your agent something...';
    console.error("Local microphone fallback failed:", err);
  }
}

async function stopAndTranscribeWavFallback() {
  isRecordingFallback = false;
  els.btnVoiceInput.classList.remove('listening');
  els.chatInput.placeholder = 'Transcribing audio...';
  
  if (audioProcessorNode) {
    try { audioProcessorNode.disconnect(); } catch(e) {}
    audioProcessorNode = null;
  }
  
  if (mediaStream) {
    mediaStream.getTracks().forEach(t => t.stop());
    mediaStream = null;
  }
  
  if (audioContext) {
    try { await audioContext.close(); } catch(e) {}
    audioContext = null;
  }
  
  if (leftChannelPCM.length > 0) {
    let totalLength = leftChannelPCM.reduce((acc, val) => acc + val.length, 0);
    let flatBuffer = new Float32Array(totalLength);
    let offset = 0;
    for (let i = 0; i < leftChannelPCM.length; i++) {
      flatBuffer.set(leftChannelPCM[i], offset);
      offset += leftChannelPCM[i].length;
    }
    
    const wavBlob = bufferToWav(flatBuffer, 16000);
    leftChannelPCM = [];
    
    try {
      const arrayBuffer = await wavBlob.arrayBuffer();
      const uint8Array = new Uint8Array(arrayBuffer);
      
      // Post to daemon transcribe API
      const responseText = await apiRequest('/api/transcribe', 'POST', {
        audioData: Array.from(uint8Array)
      });
      
      if (responseText && responseText.trim().length > 0) {
        els.chatInput.value = responseText;
        await handleSendMessage(true); // voice input -> voice reply
      } else {
        els.chatInput.placeholder = 'Could not understand audio. Try again.';
      }
    } catch (e) {
      console.error("Transcription API failed:", e);
      els.chatInput.placeholder = 'Error: Could not transcribe audio.';
    }
  } else {
    els.chatInput.placeholder = 'Ask your agent something...';
  }
}

function initSpeechRecognition() {
  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
  if (!SpeechRecognition) {
    console.warn("Speech recognition (Web Speech API) is not supported in this browser. Fallback mic recording active.");
    return;
  }
  
  state.speechRecognition = new SpeechRecognition();
  state.speechRecognition.continuous = false;
  state.speechRecognition.interimResults = false;
  
  // Set language depending on state
  state.speechRecognition.lang = state.agentLanguage === 'tr' ? 'tr-TR' : 
                 state.agentLanguage === 'de' ? 'de-DE' : 'en-US';
                 
  state.speechRecognition.onstart = () => {
    state.isListening = true;
    els.btnVoiceInput.classList.add('listening');
    els.chatInput.placeholder = 'Listening... Please speak.';
  };
  
  state.speechRecognition.onresult = (event) => {
    const transcript = event.results[0][0].transcript;
    els.chatInput.value = transcript;
  };
  
  state.speechRecognition.onerror = (event) => {
    console.error("Speech recognition error:", event.error);
    stopSpeechRecognition();
  };
  
  state.speechRecognition.onend = () => {
    stopSpeechRecognition();
  };
}

function toggleSpeechRecognition() {
  if (!state.speechRecognition) return;
  
  if (state.isListening) {
    state.speechRecognition.stop();
  } else {
    // Dynamic language update
    state.speechRecognition.lang = state.agentLanguage === 'tr' ? 'tr-TR' : 
                   state.agentLanguage === 'de' ? 'de-DE' : 'en-US';
    state.speechRecognition.start();
  }
}

function stopSpeechRecognition() {
  state.isListening = false;
  els.btnVoiceInput.classList.remove('listening');
  els.chatInput.placeholder = 'Ask your agent something...';
}

// ────────────────────────────────────────────────────────────────
// CLIENT NOTEBOOKLM COGNITIVE WORKSPACE HELPERS
// ────────────────────────────────────────────────────────────────

window.activeClientNotebookPath = null;
window.activeClientNotebookData = null;

async function refreshClientNotebookList() {
  const select = document.getElementById('nl-client-notebook-select');
  if (!select) return;
  
  try {
    const files = await apiRequest('/api/workspace');
    const ipynbFiles = files.filter(f => f.endsWith('.ipynb'));
    
    select.innerHTML = '<option value="">No Notebook Selected</option>';
    ipynbFiles.forEach(file => {
      const opt = document.createElement('option');
      opt.value = file;
      opt.textContent = file;
      if (window.activeClientNotebookPath === file) {
        opt.selected = true;
      }
      select.appendChild(opt);
    });
  } catch (e) {
    console.error("Failed to refresh client notebook list:", e);
  }
}

async function loadSelectedClientNotebook(path) {
  const cellsList = document.getElementById('nl-client-cells-list');
  if (!cellsList) return;
  
  if (!path) {
    window.activeClientNotebookPath = null;
    window.activeClientNotebookData = null;
    cellsList.innerHTML = `
      <div style="text-align: center; color: #666; font-size: 11px; font-family: var(--font-mono); padding-top: 50px;">
        Select a notebook to continue, or create a new one.
      </div>`;
    return;
  }
  
  window.activeClientNotebookPath = path;
  try {
    const content = await apiRequest(`/api/file?path=${encodeURIComponent(path)}`);
    let data;
    try {
      data = JSON.parse(content);
    } catch (e) {
      data = { cells: [], metadata: {} };
    }
    
    window.activeClientNotebookData = data;
    renderClientNotebookCells();
  } catch (err) {
    console.error("Failed to load client notebook:", err);
  }
}

async function saveActiveClientNotebook() {
  if (!window.activeClientNotebookPath || !window.activeClientNotebookData) return;
  try {
    const content = JSON.stringify(window.activeClientNotebookData, null, 2);
    await apiRequest(`/api/file?path=${encodeURIComponent(window.activeClientNotebookPath)}`, 'POST', content);
  } catch (err) {
    console.error("Failed to save client notebook:", err);
  }
}

function renderClientNotebookCells() {
  const container = document.getElementById('nl-client-cells-list');
  if (!container) return;
  
  if (!window.activeClientNotebookData || !window.activeClientNotebookData.cells || window.activeClientNotebookData.cells.length === 0) {
    container.innerHTML = `
      <div style="text-align: center; color: #666; font-size: 11px; font-family: var(--font-mono); padding-top: 50px;">
        This notebook has no cells yet. Add a Markdown or Code cell above.
      </div>`;
    return;
  }
  
  container.innerHTML = '';
  window.activeClientNotebookData.cells.forEach((cell, idx) => {
    const cellEl = document.createElement('div');
    cellEl.className = 'client-cell-item';
    cellEl.style.background = cell.cell_type === 'code' ? 'rgba(0, 229, 255, 0.03)' : 'rgba(255, 159, 0, 0.03)';
    cellEl.style.border = cell.cell_type === 'code' ? '1px solid rgba(0, 229, 255, 0.15)' : '1px solid rgba(255, 159, 0, 0.15)';
    cellEl.style.borderRadius = '6px';
    cellEl.style.padding = '10px';
    cellEl.style.display = 'flex';
    cellEl.style.flexDirection = 'column';
    cellEl.style.gap = '6px';
    
    const header = document.createElement('div');
    header.style.display = 'flex';
    header.style.justifyContent = 'space-between';
    header.style.fontSize = '9px';
    header.style.fontFamily = 'var(--font-mono)';
    header.style.color = cell.cell_type === 'code' ? '#00e5ff' : '#ff9f00';
    header.style.borderBottom = '1px solid rgba(255,255,255,0.05)';
    header.style.paddingBottom = '4px';
    
    const leftText = document.createElement('span');
    leftText.textContent = `[${idx + 1}] ${cell.cell_type.toUpperCase()} CELL`;
    header.appendChild(leftText);
    
    const toolbar = document.createElement('div');
    toolbar.style.display = 'flex';
    toolbar.style.gap = '10px';
    
    if (cell.cell_type === 'code') {
      const runBtn = document.createElement('button');
      runBtn.textContent = '▶ Run';
      runBtn.style.background = 'none';
      runBtn.style.border = 'none';
      runBtn.style.color = '#00ff88';
      runBtn.style.cursor = 'pointer';
      runBtn.style.fontSize = '9px';
      runBtn.style.fontFamily = 'var(--font-mono)';
      runBtn.addEventListener('click', () => runClientNotebookCodeCell(idx));
      toolbar.appendChild(runBtn);
    }
    
    const delBtn = document.createElement('button');
    delBtn.textContent = '❌ Sil';
    delBtn.style.background = 'none';
    delBtn.style.border = 'none';
    delBtn.style.color = '#ff4444';
    delBtn.style.cursor = 'pointer';
    delBtn.style.fontSize = '9px';
    delBtn.style.fontFamily = 'var(--font-mono)';
    delBtn.addEventListener('click', () => deleteClientNotebookCell(idx));
    toolbar.appendChild(delBtn);
    
    header.appendChild(toolbar);
    cellEl.appendChild(header);
    
    const textarea = document.createElement('textarea');
    textarea.value = Array.isArray(cell.source) ? cell.source.join('') : (cell.source || '');
    textarea.style.background = 'rgba(0,0,0,0.3)';
    textarea.style.border = '1px solid rgba(255,255,255,0.08)';
    textarea.style.color = '#fff';
    textarea.style.fontFamily = 'var(--font-mono)';
    textarea.style.fontSize = '11px';
    textarea.style.padding = '6px';
    textarea.style.borderRadius = '4px';
    textarea.style.resize = 'vertical';
    textarea.style.minHeight = '50px';
    textarea.style.width = '100%';
    textarea.style.boxSizing = 'border-box';
    textarea.style.outline = 'none';
    
    textarea.addEventListener('input', () => {
      cell.source = [textarea.value];
      saveActiveClientNotebook();
    });
    
    cellEl.appendChild(textarea);
    
    if (cell.cell_type === 'code') {
      const outputArea = document.createElement('div');
      outputArea.id = `nl-client-cell-output-${idx}`;
      outputArea.style.background = 'rgba(0,0,0,0.5)';
      outputArea.style.border = '1px solid rgba(255,255,255,0.05)';
      outputArea.style.borderRadius = '4px';
      outputArea.style.padding = '6px';
      outputArea.style.fontFamily = 'var(--font-mono)';
      outputArea.style.fontSize = '10px';
      outputArea.style.color = '#88ff88';
      outputArea.style.minHeight = '15px';
      outputArea.style.whiteSpace = 'pre-wrap';
      outputArea.style.display = 'none';
      
      if (cell.outputs && cell.outputs.length > 0) {
        outputArea.style.display = 'block';
        let text = '';
        cell.outputs.forEach(out => {
          if (out.text) {
            text += Array.isArray(out.text) ? out.text.join('') : out.text;
          } else if (out.data && out.data['text/plain']) {
            text += Array.isArray(out.data['text/plain']) ? out.data['text/plain'].join('') : out.data['text/plain'];
          }
        });
        outputArea.textContent = text || 'Completed successfully (no output).';
      }
      
      cellEl.appendChild(outputArea);
    }
    
    container.appendChild(cellEl);
  });
}

async function runClientNotebookCodeCell(idx) {
  if (!window.activeClientNotebookData || !window.activeClientNotebookData.cells) return;
  const cell = window.activeClientNotebookData.cells[idx];
  const code = Array.isArray(cell.source) ? cell.source.join('') : (cell.source || '');
  
  const outputArea = document.getElementById(`nl-client-cell-output-${idx}`);
  if (outputArea) {
    outputArea.style.display = 'block';
    outputArea.textContent = 'Running...';
    outputArea.style.color = '#ff9f00';
  }
  
  try {
    const result = await apiRequest('/api/run_wasi_sandbox', 'POST', { wasmCode: code });
    cell.outputs = [{
      output_type: "stream",
      name: "stdout",
      text: [result]
    }];
    
    if (outputArea) {
      outputArea.textContent = result;
      outputArea.style.color = '#88ff88';
    }
    saveActiveClientNotebook();
  } catch (err) {
    cell.outputs = [{
      output_type: "error",
      ename: "RuntimeError",
      evalue: String(err),
      traceback: [String(err)]
    }];
    if (outputArea) {
      outputArea.textContent = `[Hata] ${err}`;
      outputArea.style.color = '#ff4444';
    }
    saveActiveClientNotebook();
  }
}

async function deleteClientNotebookCell(idx) {
  if (!window.activeClientNotebookData || !window.activeClientNotebookData.cells) return;
  window.activeClientNotebookData.cells.splice(idx, 1);
  await saveActiveClientNotebook();
  renderClientNotebookCells();
}

async function addClientNotebookCell(type) {
  if (!window.activeClientNotebookData) {
    alert('Please open a notebook first.');
    return;
  }
  const newCell = {
    cell_type: type,
    metadata: {},
    source: type === 'code' ? ['// Rust/C or WASM code here\n'] : ['### New Markdown Cell\nWrite your content here.'],
    outputs: []
  };
  window.activeClientNotebookData.cells.push(newCell);
  await saveActiveClientNotebook();
  renderClientNotebookCells();
}

async function generateClientStudyGuide() {
  if (!window.activeClientNotebookData || !window.activeClientNotebookData.cells) {
    alert('Please select a valid notebook first.');
    return;
  }
  
  appendClientNotebookMessage('system', 'Generating study guide...');
  let notebookContext = "";
  window.activeClientNotebookData.cells.forEach((cell, idx) => {
    notebookContext += `[Cell #${idx+1} - ${cell.cell_type}]\n${Array.isArray(cell.source) ? cell.source.join('') : cell.source}\n\n`;
  });
  
  const query = "Please produce a detailed, professional and structured Study Guide based on the contents of this notebook. Include the main topics, explanations of key concepts, and 3 study questions to reinforce learning. End with a noble Dudu signature (). Reply in the SAME language as the notebook content.";
  
  try {
    const result = await apiRequest('/api/synthesis', 'POST', {
      context: notebookContext,
      query: query,
      history: [],
      workspace: "",
      active_file: window.activeClientNotebookPath || ""
    });
    appendClientNotebookMessage('assistant', result);
  } catch (err) {
    appendClientNotebookMessage('system', `An error occurred: ${err}`);
  }
}

async function generateClientAudioOverview() {
  if (!window.activeClientNotebookData || !window.activeClientNotebookData.cells) {
    alert('Please select a valid notebook first.');
    return;
  }
  
  appendClientNotebookMessage('system', 'Preparing and narrating audio podcast script...');
  let notebookContext = "";
  window.activeClientNotebookData.cells.forEach((cell, idx) => {
    notebookContext += `[Cell #${idx+1} - ${cell.cell_type}]\n${Array.isArray(cell.source) ? cell.source.join('') : cell.source}\n\n`;
  });
  
  const query = "You are the NotebookLM audio engine. Produce a very fluent, short and fun podcast conversation script summarizing this notebook, between two hosts (Dudu and Co-host). Keep it short (max 150 words). Use plain text directly readable as dialogue (e.g. 'Dudu: ... Co-host: ...'). Write the script in the SAME language as the notebook content.";
  
  try {
    const result = await apiRequest('/api/synthesis', 'POST', {
      context: notebookContext,
      query: query,
      history: [],
      workspace: "",
      active_file: window.activeClientNotebookPath || ""
    });
    
    appendClientNotebookMessage('assistant', `🎙️ **Audio Podcast Summary:**\n\n${result}`);
    if (state.ttsEnabled) {
      const plainText = result.replace(/[*#`_-]/g, '').trim();
      await speakText(plainText);
    }
  } catch (err) {
    appendClientNotebookMessage('system', `An error occurred: ${err}`);
  }
}

async function handleClientNotebookChatSubmit() {
  const input = document.getElementById('nl-client-chat-input');
  if (!input) return;
  const text = input.value.trim();
  if (!text) return;
  
  if (!window.activeClientNotebookData || !window.activeClientNotebookData.cells) {
    alert('Please select a valid notebook first.');
    return;
  }
  
  input.value = '';
  appendClientNotebookMessage('user', text);
  
  let notebookContext = "";
  window.activeClientNotebookData.cells.forEach((cell, idx) => {
    notebookContext += `[Cell #${idx+1} - ${cell.cell_type}]\n${Array.isArray(cell.source) ? cell.source.join('') : cell.source}\n\n`;
  });
  
  const chatMessages = document.getElementById('nl-client-chat-messages');
  const history = [];
  if (chatMessages) {
    const messageEls = Array.from(chatMessages.querySelectorAll('.nl-msg')).slice(-6);
    messageEls.forEach(el => {
      const isUser = el.classList.contains('nl-msg-user');
      history.push(`${isUser ? 'user' : 'assistant'}: ${el.textContent}`);
    });
  }
  
  try {
    const result = await apiRequest('/api/synthesis', 'POST', {
      context: notebookContext,
      query: text,
      history: history,
      workspace: "",
      active_file: window.activeClientNotebookPath || ""
    });
    appendClientNotebookMessage('assistant', result);
    if (state.ttsEnabled) {
      await speakText(result);
    }
  } catch (err) {
    appendClientNotebookMessage('system', `An error occurred: ${err}`);
  }
}

function appendClientNotebookMessage(role, content) {
  const container = document.getElementById('nl-client-chat-messages');
  if (!container) return;
  
  if (container.querySelector('div[style*="color: #888"]')) {
    container.innerHTML = '';
  }
  
  const msgEl = document.createElement('div');
  msgEl.className = `nl-msg nl-msg-${role}`;
  msgEl.style.padding = '6px 10px';
  msgEl.style.borderRadius = '6px';
  msgEl.style.marginBottom = '6px';
  msgEl.style.lineHeight = '1.4';
  msgEl.style.wordBreak = 'break-word';
  
  if (role === 'user') {
    msgEl.style.background = 'rgba(255, 159, 0, 0.1)';
    msgEl.style.border = '1px solid rgba(255, 159, 0, 0.2)';
    msgEl.style.color = '#fff';
    msgEl.style.alignSelf = 'flex-end';
    msgEl.style.marginLeft = '15px';
    msgEl.innerHTML = `<strong>Siz:</strong> ${content}`;
  } else if (role === 'assistant') {
    msgEl.style.background = 'rgba(255,255,255,0.05)';
    msgEl.style.border = '1px solid rgba(255,255,255,0.1)';
    msgEl.style.color = '#ff9f00';
    msgEl.style.alignSelf = 'flex-start';
    msgEl.style.marginRight = '15px';
    
    let formatted = content;
    if (formatted.includes('<think>')) {
      const parts = formatted.split('</think>');
      const thinkText = parts[0].replace('<think>', '').trim();
      const restText = parts.slice(1).join('').trim();
      formatted = `<details style="margin-bottom: 6px; color: #888; font-size: 10px;"><summary style="cursor: pointer; outline: none; user-select: none;">Thought Process</summary><div style="padding: 4px; border-left: 2px solid #555; background: rgba(0,0,0,0.2); margin-top: 2px; white-space: pre-wrap;">${thinkText}</div></details>${restText}`;
    }
    msgEl.innerHTML = `<strong>Dudu:</strong> ${formatted}`;
  } else {
    msgEl.style.color = '#888';
    msgEl.style.fontStyle = 'italic';
    msgEl.style.textAlign = 'center';
    msgEl.textContent = content;
  }
  
  container.appendChild(msgEl);
  container.scrollTop = container.scrollHeight;
}

function initClientNotebookLMEvents() {
  const newNbBtn = document.getElementById('nl-client-btn-new-nb');
  const select = document.getElementById('nl-client-notebook-select');
  const addMdBtn = document.getElementById('nl-client-btn-add-md');
  const addCodeBtn = document.getElementById('nl-client-btn-add-code');
  const studyGuideBtn = document.getElementById('nl-client-btn-study-guide');
  const podcastBtn = document.getElementById('nl-client-btn-podcast');
  const chatForm = document.getElementById('nl-client-chat-form');
  
  if (newNbBtn) {
    newNbBtn.addEventListener('click', async () => {
      let name = prompt('Create New Notebook\nFile name (must end with .ipynb):');
      if (!name) return;
      if (!name.endsWith('.ipynb')) name += '.ipynb';
      
      const defaultContent = {
        cells: [
          {
            cell_type: "markdown",
            metadata: {},
            source: ["# " + name.replace('.ipynb', '') + "\nWelcome to the NotebookLM workspace."]
          }
        ],
        metadata: {},
        nbformat: 4,
        nbformat_minor: 2
      };
      
      try {
        await apiRequest(`/api/file?path=${encodeURIComponent(name)}`, 'POST', defaultContent);
        window.activeClientNotebookPath = name;
        await refreshClientNotebookList();
        await loadSelectedClientNotebook(name);
      } catch (err) {
        console.error("Failed to create client notebook:", err);
      }
    });
  }
  
  if (select) {
    select.addEventListener('change', (e) => {
      loadSelectedClientNotebook(e.target.value);
    });
  }
  
  if (addMdBtn) {
    addMdBtn.addEventListener('click', () => addClientNotebookCell('markdown'));
  }
  if (addCodeBtn) {
    addCodeBtn.addEventListener('click', () => addClientNotebookCell('code'));
  }
  if (studyGuideBtn) {
    studyGuideBtn.addEventListener('click', () => generateClientStudyGuide());
  }
  if (podcastBtn) {
    podcastBtn.addEventListener('click', () => generateClientAudioOverview());
  }
  if (chatForm) {
    chatForm.addEventListener('submit', (e) => {
      e.preventDefault();
      handleClientNotebookChatSubmit();
    });
  }
}

// Cleanup all intervals on page unload to prevent leaks
window.addEventListener('beforeunload', () => {
  if (state.telemetryIntervalId) { clearInterval(state.telemetryIntervalId); state.telemetryIntervalId = null; }
  if (state.logIntervalId) { clearInterval(state.logIntervalId); state.logIntervalId = null; }
});

