/**
 * electron/main.cjs — CJS entry point for the Electron desktop wrapper.
 *
 * Uses CommonJS so that require('electron') resolves to Electron's internal
 * module API (not the npm package that just exports the binary path).
 * The .cjs extension overrides "type": "module" in the root package.json.
 */

const { writeFileSync, mkdirSync, openSync } = require('fs');
const { join: pjoin } = require('path');

// Early boot log — write to a file since stdout may not be visible in packaged apps
const BOOT_LOG = pjoin(require('os').homedir(), 'ai-do-boot.log');
function bootLog(msg) {
  const line = `[${new Date().toISOString()}] ${msg}\n`;
  try { process.stderr.write(line); } catch {}
  try { writeFileSync(BOOT_LOG, line, { flag: 'a' }); } catch {}
}

bootLog('main.cjs loading...');
bootLog(`__dirname=${__dirname}`);
bootLog(`process.type=${process.type}`);
bootLog(`process.versions.electron=${process.versions.electron}`);

let electronModule;
try {
  electronModule = require('electron');
  bootLog(`require("electron") type=${typeof electronModule}, keys=${Object.keys(electronModule).slice(0,5)}`);
} catch (err) {
  bootLog(`require("electron") FAILED: ${err.message}`);
  process.exit(1);
}

const { app, BrowserWindow, Tray, Menu, dialog, globalShortcut, ipcMain, nativeImage, clipboard, shell } = electronModule;
bootLog(`app type=${typeof app}, isPackaged=${app?.isPackaged}`);

const { spawn } = require('child_process');
const { readFile, writeFile } = require('fs/promises');
const { existsSync } = require('fs');
const { join, resolve } = require('path');

const IS_PACKAGED = app.isPackaged;
const AGENT_ROOT = IS_PACKAGED
  ? resolve(join(__dirname, '..'))
  : resolve(join(__dirname, '..'));
const CONFIG_PATH = IS_PACKAGED
  ? join(app.getPath('userData'), 'config.json')
  : join(AGENT_ROOT, 'config.json');
const DEFAULT_HOTKEY = 'CommandOrControl+Shift+A';

let agentProcess = null;
let mainWindow = null;
let splashWindow = null;
let tray = null;
let isQuitting = false;
let currentHotkey = '';
let hotkeyPollTimer = null;

// Restart tracking — prevent infinite crash loops
let restartCount = 0;
let lastRestartTime = 0;
const MAX_RESTARTS = 5;
const RESTART_WINDOW_MS = 30000; // reset counter after 30s of stable running

function findNodeBin() {
  if (IS_PACKAGED) return process.execPath;
  return process.env.AGENT_NODE_BIN || process.env.NODE_EXE || 'node';
}

function relayChildStream(stream, prefix = '') {
  let buffer = '';
  stream.on('data', (chunk) => {
    buffer += chunk.toString('utf8');
    const lines = buffer.split(/\r?\n/);
    buffer = lines.pop() || '';
    for (const raw of lines) {
      const line = raw.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '');
      if (!line.trim()) continue;
      const msg = prefix ? `${prefix}${line}` : line;
      console.log(msg);
      bootLog(`child: ${msg}`);
    }
  });
  stream.on('end', () => {
    const tail = buffer.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '').trim();
    if (tail) {
      const msg = prefix ? `${prefix}${tail}` : tail;
      console.log(msg);
      bootLog(`child: ${msg}`);
    }
  });
}

function safeConfigDefaults() {
  return {
    port: 4000,
    authToken: null,
    // Packaged apps store workspaces in userData (survives updates);
    // dev mode uses the project tree
    workspaceRoot: IS_PACKAGED
      ? join(app.getPath('userData'), 'workspaces')
      : join(AGENT_ROOT, 'workspaces'),
    desktopHotkey: DEFAULT_HOTKEY
  };
}

async function readConfig() {
  const defaults = safeConfigDefaults();
  if (!existsSync(CONFIG_PATH)) return defaults;
  try {
    const raw = await readFile(CONFIG_PATH, 'utf-8');
    const parsed = JSON.parse(raw);
    return { ...defaults, ...parsed };
  } catch {
    return defaults;
  }
}

async function writeConfig(nextConfig) {
  await writeFile(CONFIG_PATH, JSON.stringify(nextConfig, null, 2) + '\n', 'utf-8');
}

async function uiUrl() {
  const cfg = await readConfig();
  const base = `http://localhost:${cfg.port || 4000}`;
  return cfg.authToken ? `${base}?token=${cfg.authToken}` : base;
}

function startAgent() {
  if (agentProcess && !agentProcess.killed) return;
  const nodeBin = findNodeBin();
  const agentScript = join(AGENT_ROOT, 'agent.mjs');

  bootLog(`startAgent: IS_PACKAGED=${IS_PACKAGED}, nodeBin=${nodeBin}`);
  bootLog(`startAgent: agentScript=${agentScript}`);

  // Ensure the data directory exists before spawning the agent —
  // app.getPath('userData') may not exist on first launch and the agent
  // needs it for config.json and agent.db.
  if (IS_PACKAGED) {
    const dataDir = app.getPath('userData');
    bootLog(`startAgent: creating dataDir=${dataDir}`);
    mkdirSync(dataDir, { recursive: true });
    bootLog(`startAgent: dataDir created`);
  }

  // Write stderr directly to a log file so we capture crash output
  // even when the process exits before stream events fire.
  const agentLogPath = IS_PACKAGED
    ? join(app.getPath('userData'), 'agent-stderr.log')
    : join(AGENT_ROOT, 'agent-stderr.log');
  const stderrFd = openSync(agentLogPath, 'w');
  bootLog(`startAgent: stderr log at ${agentLogPath}`);

  agentProcess = spawn(nodeBin, IS_PACKAGED ? [agentScript] : ['agent.mjs'], {
    cwd: AGENT_ROOT,
    env: {
      ...process.env,
      ...(IS_PACKAGED ? {
        ELECTRON_RUN_AS_NODE: '1',
        AGENT_DATA_DIR: app.getPath('userData')
      } : {})
    },
    stdio: ['ignore', 'pipe', stderrFd],
    shell: false
  });
  bootLog(`startAgent: agent spawned, pid=${agentProcess.pid}`);
  relayChildStream(agentProcess.stdout);

  agentProcess.on('exit', (code, signal) => {
    bootLog(`agent exited: code=${code}, signal=${signal}`);
    agentProcess = null;
    if (isQuitting) return;

    const now = Date.now();
    // Reset counter if the agent ran for longer than the restart window
    if (now - lastRestartTime > RESTART_WINDOW_MS) {
      restartCount = 0;
    }
    restartCount++;
    lastRestartTime = now;

    if (restartCount > MAX_RESTARTS) {
      console.error(`[desktop] Agent crashed ${restartCount} times in ${RESTART_WINDOW_MS / 1000}s — stopping auto-restart.`);
      console.error(`[desktop] Check config.json and agent logs for errors. Fix the issue and restart the app.`);
      if (mainWindow && mainWindow.webContents) {
        mainWindow.webContents.send('desktop:agent-error',
          'Agent keeps crashing. Check the "cli" field in Settings or config.json.');
      }
      return;
    }

    console.log(`[desktop] Agent exited (${code ?? 'null'}/${signal ?? 'null'}), restarting in 2s (attempt ${restartCount}/${MAX_RESTARTS})`);
    setTimeout(startAgent, 2000);
  });

  agentProcess.on('error', (err) => {
    bootLog(`agent spawn error: ${err.message}`);
    console.error(`[desktop] Failed to launch agent with "${nodeBin}": ${err.message}`);
  });
}

async function waitForServerReady(timeoutMs = 30000) {
  bootLog('waitForServerReady: starting');
  const start = Date.now();
  while (Date.now() - start < timeoutMs) {
    try {
      const url = await uiUrl();
      const res = await fetch(url, { method: 'GET' });
      if (res.ok) {
        bootLog(`waitForServerReady: server ready after ${Date.now() - start}ms`);
        return true;
      }
    } catch {}
    await new Promise(r => setTimeout(r, 500));
  }
  bootLog(`waitForServerReady: TIMED OUT after ${timeoutMs}ms`);
  return false;
}

function getErrorPageHtml() {
  const stderrPath = IS_PACKAGED
    ? pjoin(app.getPath('userData'), 'agent-stderr.log')
    : pjoin(AGENT_ROOT, 'agent-stderr.log');
  let errorDetail = '';
  try {
    const { readFileSync: rfs } = require('fs');
    errorDetail = rfs(stderrPath, 'utf-8').trim().split('\n').slice(-5).join('<br>');
  } catch {}

  return `<!DOCTYPE html>
<html><head><meta charset="utf-8"><style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  background: #0f0f1a; color: #e8eaf6; display: flex; align-items: center;
  justify-content: center; height: 100vh; padding: 40px;
}
.error-box {
  max-width: 540px; text-align: center;
  background: rgba(255,255,255,0.05); border-radius: 16px; padding: 40px;
  border: 1px solid rgba(255,255,255,0.1);
}
h1 { font-size: 24px; margin-bottom: 12px; }
p { color: rgba(255,255,255,0.6); font-size: 14px; line-height: 1.6; margin-bottom: 16px; }
code {
  display: block; text-align: left; padding: 12px; background: rgba(0,0,0,0.3);
  border-radius: 8px; font-size: 12px; color: #ff6b6b; margin: 12px 0;
  word-break: break-all; max-height: 120px; overflow: auto;
}
.hint { font-size: 13px; color: rgba(255,255,255,0.4); }
button {
  margin-top: 20px; padding: 10px 24px; border: none; border-radius: 8px;
  background: #4a9eff; color: #fff; font-size: 14px; font-weight: 600;
  cursor: pointer;
}
button:hover { background: #3d8be0; }
</style></head><body>
<div class="error-box">
  <h1>Agent Failed to Start</h1>
  <p>The AI-Do agent process could not start the server. This usually means the LLM adapter isn't configured.</p>
  ${errorDetail ? '<code>' + errorDetail + '</code>' : ''}
  <p class="hint">Check the boot log at:<br><code style="color:#4a9eff;">${BOOT_LOG}</code></p>
  <p class="hint">Try deleting the config to reset:<br><code style="color:#4a9eff;">${CONFIG_PATH}</code></p>
  <button onclick="location.reload()">Retry</button>
</div>
</body></html>`;
}

function showUI() {
  if (!mainWindow) return;
  if (mainWindow.isMinimized()) mainWindow.restore();
  if (!mainWindow.isVisible()) mainWindow.show();
  mainWindow.focus();
}

function loadAppIcon() {
  for (const name of ['icon-512.png', 'icon-256.png', 'icon-1024.png', 'icon.icns']) {
    const p = join(__dirname, 'icons', name);
    if (existsSync(p)) return p;
  }
  return undefined;
}

function createSplash() {
  const appIconPath = loadAppIcon();
  splashWindow = new BrowserWindow({
    width: 400,
    height: 300,
    frame: false,
    transparent: true,
    resizable: false,
    alwaysOnTop: true,
    skipTaskbar: true,
    icon: appIconPath,
    webPreferences: { nodeIntegration: false, contextIsolation: true }
  });

  const splashHtml = `<!DOCTYPE html>
<html><head><meta charset="utf-8"><style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  display: flex; align-items: center; justify-content: center;
  height: 100vh; background: transparent; -webkit-app-region: drag;
  user-select: none;
}
.splash {
  background: linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 50%, #16213e 100%);
  border-radius: 20px; padding: 48px 40px; text-align: center;
  box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 40px rgba(74,158,255,0.15);
  border: 1px solid rgba(255,255,255,0.08);
}
.logo {
  width: 72px; height: 72px; margin: 0 auto 20px;
  background: linear-gradient(135deg, #4a9eff 0%, #7c5aff 100%);
  border-radius: 18px; display: flex; align-items: center; justify-content: center;
  font-size: 22px; font-weight: 700; color: #fff; letter-spacing: -0.5px;
}
h1 { color: #e8eaf6; font-size: 22px; font-weight: 600; margin-bottom: 8px; }
p { color: rgba(255,255,255,0.45); font-size: 13px; margin-bottom: 24px; }
.loader {
  width: 120px; height: 3px; background: rgba(255,255,255,0.08);
  border-radius: 2px; margin: 0 auto; overflow: hidden;
}
.loader-bar {
  width: 40%; height: 100%; border-radius: 2px;
  background: linear-gradient(90deg, #4a9eff, #7c5aff);
  animation: slide 1.4s ease-in-out infinite;
}
@keyframes slide {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(350%); }
}
</style></head><body>
<div class="splash">
  <div class="logo">AI-Do</div>
  <h1>Agent</h1>
  <p>Starting up\u2026</p>
  <div class="loader"><div class="loader-bar"></div></div>
</div>
</body></html>`;

  splashWindow.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(splashHtml));
  splashWindow.center();
  splashWindow.show();
}

function closeSplash() {
  if (splashWindow && !splashWindow.isDestroyed()) {
    splashWindow.close();
  }
  splashWindow = null;
}

async function createWindow() {
  const appIconPath = loadAppIcon();
  mainWindow = new BrowserWindow({
    width: 1360,
    height: 900,
    minWidth: 980,
    minHeight: 680,
    show: false,
    icon: appIconPath,
    webPreferences: {
      preload: join(__dirname, 'preload.cjs'),
      contextIsolation: true,
      nodeIntegration: false
    }
  });

  mainWindow.on('close', (event) => {
    if (isQuitting) return;
    event.preventDefault();
    mainWindow.hide();
  });

  mainWindow.on('ready-to-show', () => {
    closeSplash();
    if (mainWindow && !mainWindow.isVisible()) mainWindow.show();
  });

  // Clear browser cache to ensure fresh JS/CSS on every launch
  try {
    await mainWindow.webContents.session.clearCache();
  } catch {}

  const serverReady = await waitForServerReady();
  if (serverReady) {
    await mainWindow.loadURL(await uiUrl());
  } else {
    // Show error page instead of blank screen
    await mainWindow.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(getErrorPageHtml()));
  }
}

async function updateConfigViaApiOrFile(updates) {
  const cfg = await readConfig();
  const port = cfg.port || 4000;
  const token = cfg.authToken;

  try {
    const headers = { 'Content-Type': 'application/json' };
    if (token) headers.Authorization = `Bearer ${token}`;
    const res = await fetch(`http://localhost:${port}/api/config`, {
      method: 'PUT',
      headers,
      body: JSON.stringify(updates)
    });
    if (res.ok) return true;
  } catch {}

  await writeConfig({ ...cfg, ...updates });
  return false;
}

async function callAgentApi(path, method = 'GET', body = null) {
  const cfg = await readConfig();
  const port = cfg.port || 4000;
  const token = cfg.authToken;
  const headers = {};
  if (token) headers.Authorization = `Bearer ${token}`;
  if (body) headers['Content-Type'] = 'application/json';

  const res = await fetch(`http://localhost:${port}${path}`, {
    method,
    headers,
    body: body ? JSON.stringify(body) : undefined
  });
  if (!res.ok) throw new Error(`API ${method} ${path} failed with ${res.status}`);
  return res.json();
}

async function buildWorkspaceSubmenu() {
  try {
    const workspaces = await callAgentApi('/api/workspaces');
    return workspaces.map(ws => ({
      label: `${ws.name || ws.id}${ws.is_active ? ' ✓' : ''}`,
      click: async () => {
        try {
          await callAgentApi(`/api/workspaces/${ws.id}/activate`, 'PUT');
          console.log(`[desktop] Switched to workspace: ${ws.id}`);
        } catch (err) {
          console.error(`[desktop] Workspace switch failed: ${err.message}`);
        }
      }
    }));
  } catch {
    return [{ label: '(loading...)', enabled: false }];
  }
}

async function pickWorkspaceFolder() {
  const result = await dialog.showOpenDialog({
    title: 'Select Workspace Folder',
    properties: ['openDirectory', 'createDirectory']
  });
  if (result.canceled || !result.filePaths.length) return null;
  const folder = resolve(result.filePaths[0]);
  await updateConfigViaApiOrFile({ workspaceRoot: folder });
  return folder;
}

function loadTrayIcon() {
  const iconDir = join(__dirname, 'icons');

  if (process.platform === 'darwin') {
    const templatePath = join(iconDir, 'tray-iconTemplate.png');
    if (existsSync(templatePath)) {
      const img = nativeImage.createFromPath(templatePath);
      img.setTemplateImage(true);
      return img;
    }
  }

  const colourPath = join(iconDir, 'tray-icon.png');
  if (existsSync(colourPath)) {
    return nativeImage.createFromPath(colourPath);
  }

  try {
    return process.platform === 'win32'
      ? nativeImage.createFromPath(process.execPath)
      : nativeImage.createEmpty();
  } catch {
    return nativeImage.createEmpty();
  }
}

async function createTray() {
  if (tray) return;
  const trayIcon = loadTrayIcon();

  tray = new Tray(trayIcon);
  tray.setToolTip('AI-Do');
  tray.on('double-click', showUI);

  const template = [
    { label: 'Show UI', click: () => showUI() },
    {
      label: 'Hide UI',
      click: () => {
        if (mainWindow) mainWindow.hide();
      }
    },
    { type: 'separator' },
    {
      label: 'Select Workspace Folder...',
      click: async () => {
        const folder = await pickWorkspaceFolder();
        if (folder && mainWindow && mainWindow.webContents) {
          mainWindow.webContents.send('desktop:workspace-updated', folder);
        }
      }
    },
    { type: 'separator' },
    {
      label: 'Pause Background Loop',
      click: async () => {
        try {
          const result = await callAgentApi('/api/loop', 'POST', { enabled: false });
          console.log(`[desktop] Background loop paused: ${result.enabled === false ? 'ok' : 'unexpected state'}`);
        } catch (err) {
          console.error(`[desktop] Could not pause loop: ${err.message}`);
        }
      }
    },
    {
      label: 'Resume Background Loop',
      click: async () => {
        try {
          const result = await callAgentApi('/api/loop', 'POST', { enabled: true });
          console.log(`[desktop] Background loop resumed: ${result.enabled === true ? 'ok' : 'unexpected state'}`);
        } catch (err) {
          console.error(`[desktop] Could not resume loop: ${err.message}`);
        }
      }
    },
    { type: 'separator' },
    {
      label: 'Switch Workspace',
      type: 'submenu',
      submenu: await buildWorkspaceSubmenu()
    },
    {
      label: 'Ingest Clipboard',
      click: async () => {
        try {
          const text = clipboard.readText();
          if (!text || !text.trim()) {
            console.log('[desktop] Clipboard is empty');
            return;
          }
          await callAgentApi('/api/ingest', 'POST', {
            sourceType: 'text',
            sourcePath: text.slice(0, 10000)
          });
          console.log('[desktop] Clipboard content sent to ingestion queue');
        } catch (err) {
          console.error(`[desktop] Clipboard ingest failed: ${err.message}`);
        }
      }
    },
    { type: 'separator' },
    {
      label: 'Voice Activation',
      type: 'submenu',
      submenu: [
        {
          label: 'Enable',
          click: async () => {
            try {
              await callAgentApi('/api/voice/toggle', 'POST', { enabled: true });
              console.log('[desktop] Voice activation enabled');
            } catch (err) {
              console.error(`[desktop] Voice toggle failed: ${err.message}`);
            }
          }
        },
        {
          label: 'Disable',
          click: async () => {
            try {
              await callAgentApi('/api/voice/toggle', 'POST', { enabled: false });
              console.log('[desktop] Voice activation disabled');
            } catch (err) {
              console.error(`[desktop] Voice toggle failed: ${err.message}`);
            }
          }
        },
        {
          label: 'Listen Now',
          click: async () => {
            try {
              await callAgentApi('/api/voice/listen', 'POST');
              console.log('[desktop] Voice listen triggered');
            } catch (err) {
              console.error(`[desktop] Voice listen failed: ${err.message}`);
            }
          }
        }
      ]
    },
    { type: 'separator' },
    {
      label: 'Persona',
      type: 'submenu',
      submenu: [
        {
          label: 'Edit Persona',
          click: () => {
            showUI();
            if (mainWindow && mainWindow.webContents) {
              mainWindow.webContents.send('desktop:open-persona-editor');
            }
          }
        }
      ]
    },
    {
      label: 'Theme',
      type: 'submenu',
      submenu: [
        {
          label: 'Dark',
          click: async () => {
            try {
              await updateConfigViaApiOrFile({ theme: 'dark' });
              if (mainWindow && mainWindow.webContents) {
                mainWindow.webContents.send('desktop:theme-change', 'dark');
              }
              console.log('[desktop] Theme set to dark');
            } catch (err) {
              console.error(`[desktop] Theme change failed: ${err.message}`);
            }
          }
        },
        {
          label: 'Light',
          click: async () => {
            try {
              await updateConfigViaApiOrFile({ theme: 'light' });
              if (mainWindow && mainWindow.webContents) {
                mainWindow.webContents.send('desktop:theme-change', 'light');
              }
              console.log('[desktop] Theme set to light');
            } catch (err) {
              console.error(`[desktop] Theme change failed: ${err.message}`);
            }
          }
        }
      ]
    },
    {
      label: 'Focus Mode',
      click: () => {
        if (mainWindow && mainWindow.webContents) {
          mainWindow.webContents.send('desktop:focus-mode-toggled');
        }
        console.log('[desktop] Focus mode toggled');
      }
    },
    { type: 'separator' },
    {
      label: 'Quit',
      click: () => {
        isQuitting = true;
        app.quit();
      }
    }
  ];
  tray.setContextMenu(Menu.buildFromTemplate(template));
}

function normalizeHotkey(candidate) {
  const value = String(candidate || '').trim();
  return value || DEFAULT_HOTKEY;
}

function registerShowHotkey(nextHotkey) {
  const normalized = normalizeHotkey(nextHotkey);
  if (normalized === currentHotkey) return true;

  if (currentHotkey) {
    try { globalShortcut.unregister(currentHotkey); } catch {}
    currentHotkey = '';
  }

  const ok = globalShortcut.register(normalized, () => showUI());
  if (ok) {
    currentHotkey = normalized;
    return true;
  }

  if (normalized !== DEFAULT_HOTKEY) {
    const fallbackOk = globalShortcut.register(DEFAULT_HOTKEY, () => showUI());
    if (fallbackOk) {
      currentHotkey = DEFAULT_HOTKEY;
      return true;
    }
  }
  return false;
}

async function refreshHotkeyFromConfig() {
  const cfg = await readConfig();
  registerShowHotkey(cfg.desktopHotkey);
}

// --- IPC Handlers ---

ipcMain.handle('desktop:show-ui', async () => {
  showUI();
  return true;
});

ipcMain.handle('desktop:pick-workspace-folder', async () => {
  const folder = await pickWorkspaceFolder();
  if (folder && mainWindow && mainWindow.webContents) {
    mainWindow.webContents.send('desktop:workspace-updated', folder);
  }
  return folder;
});

ipcMain.handle('desktop:voice-toggle', async (_, enabled) => {
  try {
    return await callAgentApi('/api/voice/toggle', 'POST', { enabled });
  } catch (err) {
    console.error(`[desktop] Voice toggle IPC failed: ${err.message}`);
    return { error: err.message };
  }
});

ipcMain.handle('desktop:voice-listen', async () => {
  try {
    showUI();
    return await callAgentApi('/api/voice/listen', 'POST');
  } catch (err) {
    console.error(`[desktop] Voice listen IPC failed: ${err.message}`);
    return { error: err.message };
  }
});

ipcMain.handle('desktop:get-persona', async () => {
  try {
    return await callAgentApi('/api/persona');
  } catch (err) {
    console.error(`[desktop] Get persona IPC failed: ${err.message}`);
    return { error: err.message };
  }
});

ipcMain.handle('desktop:update-persona', async (_, data) => {
  try {
    return await callAgentApi('/api/persona', 'PUT', data);
  } catch (err) {
    console.error(`[desktop] Update persona IPC failed: ${err.message}`);
    return { error: err.message };
  }
});

ipcMain.handle('desktop:toggle-theme', async () => {
  try {
    const cfg = await readConfig();
    const newTheme = cfg.theme === 'dark' ? 'light' : 'dark';
    await writeConfig({ ...cfg, theme: newTheme });
    if (mainWindow && mainWindow.webContents) {
      mainWindow.webContents.send('desktop:theme-change', newTheme);
    }
    return newTheme;
  } catch (err) {
    console.error(`[desktop] Toggle theme IPC failed: ${err.message}`);
    return { error: err.message };
  }
});

ipcMain.handle('desktop:toggle-focus-mode', async () => {
  try {
    if (mainWindow && mainWindow.webContents) {
      mainWindow.webContents.send('desktop:focus-mode-toggled');
    }
    return { ok: true };
  } catch (err) {
    console.error(`[desktop] Toggle focus mode IPC failed: ${err.message}`);
    return { error: err.message };
  }
});

ipcMain.handle('desktop:run-skill', async (_, skillName) => {
  try {
    return await callAgentApi('/api/chat', 'POST', { message: `Run the ${skillName} skill` });
  } catch (err) {
    console.error(`[desktop] Run skill IPC failed: ${err.message}`);
    return { error: err.message };
  }
});

ipcMain.handle('desktop:open-path', async (_, folderPath) => {
  try {
    await shell.openPath(folderPath);
    return { ok: true };
  } catch (err) {
    console.error(`[desktop] Open path failed: ${err.message}`);
    return { error: err.message };
  }
});

// --- App Lifecycle ---

const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
  app.quit();
} else {
  app.on('second-instance', () => {
    showUI();
  });
}

app.whenReady().then(async () => {
  bootLog('app ready — creating splash');
  createSplash();
  bootLog('starting agent');
  startAgent();
  bootLog('creating window (will wait for server)');
  await createWindow();
  bootLog('window created — creating tray');
  await createTray();
  await refreshHotkeyFromConfig();
  hotkeyPollTimer = setInterval(refreshHotkeyFromConfig, 4000);

  try {
    globalShortcut.register('CommandOrControl+Shift+V', async () => {
      try {
        const text = clipboard.readText();
        if (text?.trim()) {
          await callAgentApi('/api/ingest', 'POST', { sourceType: 'text', sourcePath: text.slice(0, 10000) });
          console.log('[desktop] Clipboard ingested via hotkey');
        }
      } catch (err) {
        console.error(`[desktop] Hotkey clipboard ingest failed: ${err.message}`);
      }
    });
  } catch (err) {
    console.error(`[desktop] Failed to register clipboard hotkey: ${err.message}`);
  }

  try {
    const cfg = await readConfig();
    const voiceHotkey = cfg.voice?.listenHotkey || 'CommandOrControl+Shift+Space';
    globalShortcut.register(voiceHotkey, async () => {
      try {
        showUI();
        await callAgentApi('/api/voice/listen', 'POST');
        console.log('[desktop] Voice listen triggered via hotkey');
      } catch (err) {
        console.error(`[desktop] Voice hotkey failed: ${err.message}`);
      }
    });
  } catch (err) {
    console.error(`[desktop] Failed to register voice hotkey: ${err.message}`);
  }

  try {
    globalShortcut.register('CommandOrControl+Shift+F', () => {
      if (mainWindow && mainWindow.webContents) {
        mainWindow.webContents.send('desktop:focus-mode-toggled');
      }
      console.log('[desktop] Focus mode toggled via hotkey');
    });
  } catch (err) {
    console.error(`[desktop] Failed to register focus mode hotkey: ${err.message}`);
  }

  app.on('activate', () => {
    showUI();
  });
});

app.on('window-all-closed', (event) => {
  event.preventDefault();
});

app.on('before-quit', () => {
  isQuitting = true;
  if (hotkeyPollTimer) clearInterval(hotkeyPollTimer);
  hotkeyPollTimer = null;
  try { globalShortcut.unregisterAll(); } catch {}
  if (agentProcess && !agentProcess.killed) {
    try { agentProcess.kill('SIGINT'); } catch {}
  }
});
