/**
 * MetonaAI Desktop — Electron 主进程入口
 *
 * 启动流程（按架构规范强制顺序）：
 * 1. 初始化日志系统
 * 2. 选择/创建工作空间 → 校验必需文件（缺失自动创建）
 * 3. 连接 SQLite → 执行 schema 迁移 → 加载配置
 * 4. 初始化 Provider Adapter
 * 5. 加载 4 个磁盘文件 → 构建 System Prompt
 * 6. 注册内置工具 + 连接 MCP Servers
 * 7. 启动 React UI → Agent 就绪
 *
 * @see docs/MetonaAI-Desktop 架构与交互设计.html — 启动流程
 * @see docs/MetonaAI-Desktop UI UX 设计集成方案.html — 窗口管理
 */

import { app, shell, Menu, BrowserWindow, ipcMain } from 'electron';
import { join } from 'path';
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { electronApp, optimizer } from '@electron-toolkit/utils';
import log from 'electron-log';
import { DatabaseService } from './services/database.service';
import { SessionService } from './services/session.service';
import { ConfigService } from './services/config.service';
import { GlobalConfigService } from './services/global-config.service';
import { WorkspaceService } from './services/workspace.service';
import { AuditService } from './services/audit.service';
import { SessionRecorder } from './services/session-recorder.service';
import { TrayManager } from './services/tray-manager.service';
import { WindowManager } from './services/window-manager.service';
import { MCPManager } from './services/mcp-manager.service';
import { ContextBuilder } from './harness/prompts/context-builder';
import { MemoryManager } from './harness/memory/manager';
import { MemoryConsolidator } from './harness/memory/consolidator';
import { registerAllIPCHandlers } from './ipc/handlers';
import { AgentLoopEngine } from './harness/agent-loop';
import { ToolRegistry } from './harness/tools/registry';
import { DeepSeekAdapter } from './harness/adapters/deepseek.adapter';
import { AgnesAdapter } from './harness/adapters/agnes-ai.adapter';
import { MimoAdapter } from './harness/adapters/mimo.adapter';
import { OllamaAdapter } from './harness/adapters/ollama.adapter';
import type { IMetonaProviderAdapter } from './harness/types/metona-adapter';
import {
  ReadFileTool, WriteFileTool, ListDirectoryTool, SearchFilesTool,
  WebSearchTool, WebFetchTool,
  MemoryStoreTool, MemorySearchTool,
  RunCommandTool,
  WebBrowserTool, cleanupBrowser,
  DelegateTaskTool,
  // v0.2.0 新增工具
  FileEditorTool,
  CodeSearchTool,
  TaskManagerTool,
  DiffViewerTool,
  // v0.3.1 新增工具（11 个）
  GitStatusTool, GitDiffTool, GitLogTool, GitCommitTool,
  LintCodeTool, RunTestsTool, ProjectInfoTool,
  HttpRequestTool,
  ThinkTool,
  ViewImageTool,
  // v0.3.2 新增工具（1 个）
  DeleteFileTool,
  // v0.3.3 新增工具（2 个）
  FileMoveTool,
  FileInfoTool,
} from './harness/tools/built-in';
import { AuditLogHook, MemoryTriggerHook, PermissionCheckHook, RateLimitHook } from './harness/hooks';
import { ConfirmationHook } from './harness/hooks/confirmation-hook';
import { PolicyEngine } from './harness/sandbox/permissions';
import { SandboxManager } from './harness/sandbox/sandbox';
import { PromptInjectionDefender } from './harness/security/prompt-injection-defense';
import { OutputValidator } from './harness/verification/output-validator';
import { TaskOrchestrator } from './harness/orchestration/orchestrator';
import { UpdateService } from './services/update.service';

// ===== 步骤 1: 初始化日志系统（SYS 层）=====
log.transports.file.level = 'info';
log.transports.console.level = 'debug';

// ===== 工作空间路径独立存储（解决 DB 在 workspace 内的鸡生蛋问题）=====
const WORKSPACE_CONFIG_FILE = join(app.getPath('userData'), 'workspace-config.json');

function readWorkspacePathFromFile(): string | null {
  try {
    if (existsSync(WORKSPACE_CONFIG_FILE)) {
      const data = JSON.parse(readFileSync(WORKSPACE_CONFIG_FILE, 'utf-8'));
      return data.workspacePath ?? null;
    }
  } catch {
    // 忽略读取错误
  }
  return null;
}

function writeWorkspacePathToFile(workspacePath: string): void {
  try {
    writeFileSync(WORKSPACE_CONFIG_FILE, JSON.stringify({ workspacePath }, null, 2), 'utf-8');
  } catch (err) {
    log.error('Failed to write workspace config file:', err);
  }
}

export { readWorkspacePathFromFile, writeWorkspacePathToFile };

let databaseService: DatabaseService | null = null;
let trayManager: TrayManager | null = null;
let windowManager: WindowManager | null = null;

async function initialize(): Promise<void> {
  log.info('MetonaAI Desktop starting...');
  electronApp.setAppUserModelId('com.metona.ai-desktop');

  // 移除原生菜单栏
  Menu.setApplicationMenu(null);

  app.on('browser-window-created', (_, window) => {
    optimizer.watchWindowShortcuts(window);
  });

  // ===== 步骤 2: 工作空间（优先从独立配置文件读取路径）=====
  const savedWorkspacePath = readWorkspacePathFromFile();
  const workspaceService = new WorkspaceService(savedWorkspacePath ?? undefined);
  const workspaceInfo = workspaceService.initialize();
  // 持久化工作空间路径（供下次启动读取）
  if (savedWorkspacePath !== workspaceInfo.path) {
    writeWorkspacePathToFile(workspaceInfo.path);
  }
  log.info(`Workspace: ${workspaceInfo.path} (missing: ${workspaceInfo.missingFiles.join(', ') || 'none'})`);

  // ===== 步骤 3: SQLite =====
  databaseService = new DatabaseService(workspaceInfo.path);
  databaseService.initialize();
  const db = databaseService.getDB();

  // v0.3.17: 初始化全局配置层（跨工作空间共享 LLM/Agent/onboarding 等机器级配置）
  // 必须在 ConfigService 创建前初始化，以便注入
  const globalConfigService = new GlobalConfigService();
  globalConfigService.initialize();

  const sessionService = new SessionService(() => db);
  const configService = new ConfigService(() => db);
  // 注入全局配置层：读取时回退到全局 JSON，写入时双写
  configService.setGlobalConfig(globalConfigService);

  // v0.3.17 迁移: 首次启用全局配置层时，把工作空间 DB 中的全局配置同步到全局 JSON
  // 幂等设计：migrateFromWorkspaceDB 仅写入全局层不存在的 key，重复执行无副作用
  // 解决场景：老用户从 v0.3.16 升级，LLM 配置在工作空间 DB 里，需要迁移到全局层
  // 注意: 此处直接查 DB 而不通过 configService.getAll()，因为后者会合并全局层，导致迁移逻辑循环
  const configRows = db.prepare('SELECT key, value FROM app_config').all() as Array<{ key: string; value: string }>;
  const workspaceConfig: Record<string, unknown> = {};
  for (const row of configRows) {
    try {
      workspaceConfig[row.key] = JSON.parse(row.value);
    } catch {
      workspaceConfig[row.key] = row.value;
    }
  }
  const migratedCount = globalConfigService.migrateFromWorkspaceDB(workspaceConfig);
  if (migratedCount > 0) {
    log.info(`[MIGRATION] Migrated ${migratedCount} global config keys from workspace DB to global layer`);
  }

  // ===== 日志服务 =====
  const auditService = new AuditService(() => db);
  const sessionRecorder = new SessionRecorder(workspaceInfo.path);

  // ===== 记忆系统 =====
  const memoryManager = new MemoryManager(() => db);
  memoryManager.initialize();

  // ===== 步骤 4: Provider Adapter 工厂 =====
  // P0-2 修复: 配置缺失时返回 null 而非抛异常，让应用能启动到 Onboarding
  const createAdapter = (): IMetonaProviderAdapter | null => {
    const provider = configService.get<string>('llm.provider') ?? '';
    const model = configService.get<string>('llm.model') ?? '';
    const apiKey = configService.get<string>('llm.apiKey') ?? '';
    const baseURL = configService.get<string>('llm.baseURL') ?? '';

    // 配置不完整时返回 null（不抛异常，让应用能启动到 Onboarding）
    if (!provider || !baseURL || !model) {
      log.warn('LLM not fully configured. Please set provider, baseURL, and model in Settings.');
      return null;
    }

    if (!apiKey && provider !== 'ollama') {
      log.warn(`API key is required for provider "${provider}". Please set it in Settings.`);
      return null;
    }

    // v0.3.1: 读取 Provider 对应的 contextWindow 配置（Ollama 不使用此字段）
    const contextWindow = provider !== 'ollama'
      ? configService.get<number>(`${provider}.contextWindow`) ?? undefined
      : undefined;

    const adapterConfig = { provider, baseURL, apiKey, defaultModel: model, contextWindow };
    switch (provider) {
      case 'agnes': return new AgnesAdapter(adapterConfig);
      case 'mimo': return new MimoAdapter(adapterConfig);
      case 'ollama': return new OllamaAdapter(adapterConfig);
      default: return new DeepSeekAdapter(adapterConfig);
    }
  };

  // 配置未就绪时的 fallback adapter — getContextWindow 返回安全值，send/sendStream 会报错但 P0-1 修复后前端可见
  const FALLBACK_ADAPTER = new DeepSeekAdapter({
    provider: '', baseURL: '', apiKey: '', defaultModel: '', contextWindow: 4096,
  });

  const adapter = createAdapter() ?? FALLBACK_ADAPTER;

  // ===== 步骤 5: 工作空间文件 + System Prompt =====
  const contextBuilder = new ContextBuilder();

  // ===== v0.2.0: 安全模块（必须在工具注册之前，便于 RunCommandTool 注入 SandboxManager）=====
  const policyEngine = new PolicyEngine();
  const sandboxManager = new SandboxManager({
    allowedPaths: [workspaceInfo.path],
    networkPolicy: 'allowlist',
  });
  const promptDefender = new PromptInjectionDefender();
  const outputValidator = new OutputValidator();

  // v0.2.0: ConfirmationHook（提前创建，mainWindow 创建后再注入）
  // 注入 ConfigService 以支持持久化自动执行设置
  const confirmationHook = new ConfirmationHook(null, configService);

  // ===== 步骤 6: 注册内置工具 =====
  const toolRegistry = new ToolRegistry();
  toolRegistry.registerBuiltin(new ReadFileTool());
  toolRegistry.registerBuiltin(new WriteFileTool());
  toolRegistry.registerBuiltin(new ListDirectoryTool());
  toolRegistry.registerBuiltin(new SearchFilesTool());

  // v0.3.2: 文件删除工具（破坏性操作，HIGH + requireConfirmation）
  toolRegistry.registerBuiltin(new DeleteFileTool());

  // v0.3.3: 文件移动/重命名工具 + 文件信息查询工具
  toolRegistry.registerBuiltin(new FileMoveTool());
  toolRegistry.registerBuiltin(new FileInfoTool());

  // v0.2.0: 新增文件工具
  toolRegistry.registerBuiltin(new FileEditorTool());
  toolRegistry.registerBuiltin(new CodeSearchTool());
  toolRegistry.registerBuiltin(new DiffViewerTool());

  // WebFetchTool 先于 WebSearchTool 构造，注入为依赖
  const webFetchTool = new WebFetchTool();
  toolRegistry.registerBuiltin(webFetchTool);
  toolRegistry.registerBuiltin(new WebSearchTool(configService, webFetchTool));

  toolRegistry.registerBuiltin(new MemoryStoreTool(memoryManager));
  toolRegistry.registerBuiltin(new MemorySearchTool(memoryManager));

  // v0.2.0: RunCommandTool 注入 SandboxManager
  const runCommandTool = new RunCommandTool();
  runCommandTool.setSandboxManager(sandboxManager);
  toolRegistry.registerBuiltin(runCommandTool);

  // v0.2.0: 任务管理工具
  // P2（v0.3.13）: 注入 onTaskChanged 回调，工具写入后广播 IPC 事件给所有窗口
  toolRegistry.registerBuiltin(new TaskManagerTool(() => db, (sessionId) => {
    // 广播任务变更事件给所有窗口（UI 订阅后自动刷新）
    for (const win of BrowserWindow.getAllWindows()) {
      win.webContents.send('task:changed', sessionId);
    }
  }));

  // 注册 Web Browser 统一浏览器工具
  toolRegistry.registerBuiltin(new WebBrowserTool());

  // v0.3.1: Git 工具集（4 个）
  toolRegistry.registerBuiltin(new GitStatusTool());
  toolRegistry.registerBuiltin(new GitDiffTool());
  toolRegistry.registerBuiltin(new GitLogTool());
  toolRegistry.registerBuiltin(new GitCommitTool());

  // v0.3.1: 开发工具集（3 个）
  toolRegistry.registerBuiltin(new LintCodeTool());
  toolRegistry.registerBuiltin(new RunTestsTool());
  toolRegistry.registerBuiltin(new ProjectInfoTool());

  // v0.3.1: 独立工具（3 个，v0.3.13 删除 todo_write 后）
  toolRegistry.registerBuiltin(new HttpRequestTool());
  toolRegistry.registerBuiltin(new ThinkTool());
  toolRegistry.registerBuiltin(new ViewImageTool());

  // v0.3.1 修复 WARN-5: DelegateTaskTool 注册后再输出总数（此时才是完整的 26 个）
  // log.info 移至 DelegateTaskTool 注册后

  // ===== MCP Manager =====
  const mcpManager = new MCPManager(() => db, toolRegistry);
  // P1-7 修复: initialize() 移到 agentLoop 创建之后，完成后重新同步工具

  // ===== Hooks =====
  // v0.2.0: ConfirmationHook 注入到 preToolHooks 管道
  // 注意：setToolDefs 延迟到 DelegateTaskTool 注册后调用，确保包含所有工具的风险等级
  const preToolHooks = [
    new PermissionCheckHook(policyEngine),
    new RateLimitHook(20),
    confirmationHook,
  ];
  const postToolHooks = [
    new AuditLogHook(auditService),
    new MemoryTriggerHook(memoryManager),
  ];

  // ===== Agent Loop =====
  // Agent 配置初始化：仅此处一次性读取，配置变更时由 handlers.ts 的 config:set
  // 监听器调用 agentLoop.updateConfig() 和 orchestrator.updateDefaultConfig() 即时生效。
  // @see electron/ipc/handlers.ts — 'config:set' handler
  const ollamaNumCtx = configService.get<number>('ollama.numCtx');
  const agentMaxIter = configService.get<number>('agent.maxIterations');
  const agentTimeout = configService.get<number>('agent.totalTimeoutMs');
  const agentThinkingEnabled = configService.get<boolean>('agent.enableThinking');
  const agentThinkingEffort = configService.get<string>('agent.thinkingEffort') as 'low' | 'medium' | 'high' | 'max' | null;
  const toolExecTimeout = configService.get<number>('agent.toolExecutionTimeoutMs');
  const agentLoop = new AgentLoopEngine(
    {
      maxIterations: agentMaxIter ?? 20,
      totalTimeoutMs: agentTimeout ?? 600_000,
      contextLength: ollamaNumCtx ?? undefined,
      // v0.3.1: 从 adapter.getContextWindow() 读取，修复 Engine 128K vs Adapter 1M 不一致 bug
      contextWindow: adapter.getContextWindow(),
      thinkingEnabled: agentThinkingEnabled ?? true,
      thinkingEffort: agentThinkingEffort ?? 'high',
      toolExecutionTimeoutMs: toolExecTimeout ?? 120_000,
    },
    adapter, toolRegistry, preToolHooks, postToolHooks,
  );
  agentLoop.setTools(toolRegistry.listTools());
  agentLoop.setWorkspacePath(workspaceInfo.path);

  // P1-7 修复: MCP 初始化在 agentLoop 创建后执行，完成后重新同步工具到 AgentLoop
  // v0.3.18 修复: MCP 完成后广播 tools:ready 事件，前端据以启用发送按钮
  // 之前 MCP 初始化是异步的，用户在 MCP 完成前发送消息会用到不全的工具集
  // v0.3.18 修复: 维护 toolsReady 标志 + 暴露 tools:isReady 查询，解决竞态条件
  //   MCP initialize 几乎立即 resolve（connectServer 不 await），tools:ready 事件
  //   可能在前端监听器注册前就发出，导致前端永久错过事件、输入框永久禁用
  let toolsReady = false;
  mcpManager.initialize().then(() => {
    agentLoop.setTools(toolRegistry.listTools());
    log.info('[MCP] Tools registered and synced to AgentLoop');
    toolsReady = true;
    // 广播工具就绪事件给所有窗口
    for (const win of BrowserWindow.getAllWindows()) {
      win.webContents.send('tools:ready', { toolCount: toolRegistry.size });
    }
  }).catch((err) => {
    log.warn('MCP Manager initialization error:', err);
    // 即使 MCP 失败，内置工具已就绪，仍广播 ready 让前端启用发送
    toolsReady = true;
    for (const win of BrowserWindow.getAllWindows()) {
      win.webContents.send('tools:ready', { toolCount: toolRegistry.size });
    }
  });

  // ===== Memory Consolidator（会话结束 AI 提取重要记忆到 MEMORY.md）=====
  const memoryConsolidator = new MemoryConsolidator(adapter, workspaceService);
  // v0.3.18 修复: 注入 MemoryManager，使 consolidate 同步写入 semantic_memories 表
  // 实现 DB 记忆与 MEMORY.md 双轨交叉，避免"AI 记得一半"的不一致
  memoryConsolidator.setMemoryManager(memoryManager);

  // ===== Task Orchestrator（子任务委派）=====
  const orchestrator = new TaskOrchestrator(
    agentLoop, toolRegistry, preToolHooks, postToolHooks,
    {
      thinkingEnabled: agentThinkingEnabled ?? true,
      thinkingEffort: agentThinkingEffort ?? 'high',
      contextLength: ollamaNumCtx ?? undefined,
      // v0.3.1: 同步 contextWindow 到 SubAgent 配置
      contextWindow: adapter.getContextWindow(),
    },
  );
  toolRegistry.registerBuiltin(new DelegateTaskTool(orchestrator));
  // 重新设置工具列表，包含新注册的 delegate_task
  agentLoop.setTools(toolRegistry.listTools());
  // v0.2.0: 在所有工具（包括 DelegateTaskTool）注册完成后，刷新 ConfirmationHook 的工具定义缓存
  confirmationHook.setToolDefs(toolRegistry.listAllTools());
  // v0.3.1: 所有工具（含 DelegateTaskTool）注册完成后输出总数，确保日志显示 26 而非 25
  log.info(`Registered ${toolRegistry.size} built-in tools`);

  // ===== 热重载 Adapter 回调（设置变更时触发）=====
  // 记录上次创建 adapter 时的配置快照，用于检测配置是否真的变化
  let lastProvider = configService.get<string>('llm.provider') ?? '';
  // v0.3.1: configSig 加入 contextWindow 配置，使 contextWindow 变化也能触发热重载
  const buildConfigSig = () => {
    const provider = configService.get<string>('llm.provider') ?? '';
    return JSON.stringify({
      provider,
      model: configService.get<string>('llm.model') ?? '',
      apiKey: configService.get<string>('llm.apiKey') ?? '',
      baseURL: configService.get<string>('llm.baseURL') ?? '',
      // v0.3.1: 加入 contextWindow 配置（Ollama 用 numCtx，其他 Provider 用 ${provider}.contextWindow）
      contextWindow: provider === 'ollama'
        ? configService.get<number>('ollama.numCtx')
        : configService.get<number>(`${provider}.contextWindow`),
    });
  };
  let lastConfigSig = buildConfigSig();
  const reloadAdapter = (): boolean => {
    try {
      // 检测配置是否真的变化（避免每次发消息都重建 adapter 和弹 toast）
      const currentConfig = {
        provider: configService.get<string>('llm.provider') ?? '',
        model: configService.get<string>('llm.model') ?? '',
        apiKey: configService.get<string>('llm.apiKey') ?? '',
        baseURL: configService.get<string>('llm.baseURL') ?? '',
      };
      const currentSig = buildConfigSig();

      // 配置未变化 — 幂等返回成功，不重建 adapter，不发通知
      if (currentSig === lastConfigSig) {
        return true;
      }

      // 配置变化 — 重建 adapter
      const newAdapter = createAdapter();
      if (!newAdapter) {
        log.warn('[CONFIG] Cannot create adapter: LLM config incomplete (provider/apiKey/baseURL/model)');
        // 不更新 lastConfigSig，下次还会尝试
        if (mainWindow && !mainWindow.isDestroyed()) {
          mainWindow.webContents.send('toast:show', {
            type: 'warning',
            message: 'LLM 配置不完整，请在设置中补全 Provider、API Key、Base URL 和 Model',
          });
        }
        return false;
      }
      agentLoop.setAdapter(newAdapter);
      memoryConsolidator.setAdapter(newAdapter);
      // Provider 切换时同步 contextLength 和 contextWindow
      const provider = currentConfig.provider;
      if (provider === 'ollama') {
        const numCtx = configService.get<number>('ollama.numCtx');
        agentLoop.updateConfig({
          contextLength: numCtx ?? undefined,
          // v0.3.1: 同步 contextWindow（Ollama 的 getContextWindow 返回固定 4096）
          contextWindow: newAdapter.getContextWindow(),
        });
      } else {
        // v0.3.1: DeepSeek/Agnes 无 contextLength，但同步 contextWindow
        agentLoop.updateConfig({
          contextLength: undefined,
          contextWindow: newAdapter.getContextWindow(),
        });
      }
      log.info(`[CONFIG] Adapter reloaded: provider=${provider}`);
      // 仅在 Provider 真正变化时通知渲染进程
      if (mainWindow && !mainWindow.isDestroyed()) {
        if (lastProvider && lastProvider !== provider) {
          mainWindow.webContents.send('agent:providerSwitched', {
            from: lastProvider,
            to: provider,
            reason: 'config_changed',
            sessionId: '',
          });
          mainWindow.webContents.send('toast:show', {
            type: 'success',
            message: `Provider 已切换: ${lastProvider} → ${provider}`,
          });
        }
      }
      lastProvider = provider;
      lastConfigSig = currentSig;
      return true;
    } catch (err) {
      log.error(`[CONFIG] Failed to reload adapter: ${(err as Error).message}`);
      // 通知渲染进程 Provider 切换失败（UI 显示错误 Toast）
      if (mainWindow && !mainWindow.isDestroyed()) {
        mainWindow.webContents.send('toast:show', {
          type: 'error',
          message: `Provider 切换失败: ${(err as Error).message}`,
        });
      }
      return false;
    }
  };

  // ===== 窗口管理 =====
  windowManager = new WindowManager();
  const mainWindow = windowManager.createWindow({
    id: 'main',
    workspacePath: workspaceInfo.path,
    title: 'MetonaAI Desktop',
    // P2-11 修复: 在 loadURL 之前注册 IPC handler，消除渲染进程加载与 IPC 注册的时序窗口
    beforeLoad: (win) => {
      confirmationHook.setMainWindow(win);
      registerAllIPCHandlers(
        win, sessionService, configService, workspaceService,
        contextBuilder, agentLoop, toolRegistry, auditService,
        sessionRecorder, memoryManager, mcpManager, reloadAdapter,
        promptDefender, outputValidator,
        confirmationHook, memoryConsolidator, orchestrator,
      );
      // v0.3.18 修复: 注册 tools:isReady 查询，前端注册 onReady 监听器后立即查询，
      //   解决 tools:ready 事件在监听器注册前发出的竞态条件
      ipcMain.handle('tools:isReady', () => ({ ready: toolsReady, toolCount: toolRegistry.size }));
    },
  });

  // P2-11 修复: ConfirmationHook.setMainWindow 已在 beforeLoad 中调用，此处无需重复
  // （beforeLoad 在 loadURL 之前执行，确保 IPC handler 注册时 mainWindow 已注入）

  // ===== 系统托盘 =====
  const resourcesPath = join(__dirname, '../../assets');
  trayManager = new TrayManager(resourcesPath);
  trayManager.initialize(mainWindow);

  // ===== 全局快捷键 =====
  windowManager.registerGlobalShortcuts();

  // ===== Agent 状态同步到托盘 =====
  agentLoop.on('stateChange', (data: { previous: string; current: string; state?: string }) => {
    const statusMap: Record<string, 'idle' | 'thinking' | 'executing' | 'error'> = {
      INIT: 'idle', THINKING: 'thinking', PARSING: 'thinking',
      EXECUTING: 'executing', OBSERVING: 'thinking', REFLECTING: 'thinking',
      COMPRESSING: 'thinking', TERMINATED: 'idle',
    };
    const stateValue = (data.current || data.state) ?? '';
    trayManager?.setStatus(statusMap[stateValue] ?? 'idle');
  });

  // ===== Agent 完成时发送系统通知 =====
  agentLoop.on('complete', (data: { sessionId: string; durationMs: number }) => {
    trayManager?.sendNotification(
      'MetonaAI — 任务完成',
      `Agent 已完成任务 (${(data.durationMs / 1000).toFixed(1)}s)`,
      () => windowManager?.focusWindow(),
    );
  });

  // TODO: Initialize UpdateService for auto-update functionality
  // const updateService = new UpdateService();
  // updateService.initialize(mainWindow);

  // ===== 应用生命周期 =====
  app.on('window-all-closed', () => {
    // macOS: 保持应用运行（托盘模式）
  });

  app.on('activate', () => {
    if (windowManager && windowManager.count === 0) {
      windowManager.createWindow({ id: 'main', workspacePath: workspaceInfo.path });
    } else {
      windowManager?.focusWindow();
    }
  });

  // M-13 修复: before-quit 回调改为同步 + event.preventDefault() 确保异步清理完成
  // 之前 async 回调 Electron 不会 await，导致 MCP shutdown 未完成时应用已退出
  app.on('before-quit', (event) => {
    // 标记为正在退出，允许窗口关闭（两处 isQuitting 统一设置）
    (global as Record<string, unknown>).isQuitting = true;
    TrayManager.markQuitting();
    windowManager?.unregisterGlobalShortcuts();
    trayManager?.destroy();
    windowManager?.closeAll();

    // 防止重复触发（macOS before-quit 可能触发多次）
    if ((global as Record<string, unknown>).shutdownInProgress === true) {
      return;
    }
    (global as Record<string, unknown>).shutdownInProgress = true;

    event.preventDefault();
    // v0.3.18 修复: 超时从 5 秒延长到 40 秒，以容纳 consolidate 的 35 秒等待
    // 之前 5 秒超时会导致进行中的 consolidate 被 kill，记忆丢失
    const shutdownTimeout = setTimeout(() => {
      log.warn('[Shutdown] Timeout reached, forcing exit');
      if (databaseService) { databaseService.close(); databaseService = null; }
      app.exit(0);
    }, 40_000);

    (async () => {
      try {
        await mcpManager.shutdown();
      } catch (err) {
        log.error('[Shutdown] MCP shutdown failed:', err);
      }
      cleanupBrowser();

      // v0.3.18 修复: 等待进行中的记忆固化任务完成，避免应用退出导致记忆丢失
      // consolidate 内部有 30 秒 LLM 超时保护，此处等待 35 秒足够覆盖
      if (memoryConsolidator.isRunning()) {
        log.info('[Shutdown] Waiting for memory consolidation to complete...');
        const completed = await memoryConsolidator.waitForCompletion(35_000);
        if (!completed) {
          log.warn('[Shutdown] Memory consolidation timed out, proceeding with exit');
        } else {
          log.info('[Shutdown] Memory consolidation completed');
        }
      }

      if (databaseService) { databaseService.close(); databaseService = null; }
      clearTimeout(shutdownTimeout);
      app.exit(0);
    })().catch((err) => {
      log.error('[Shutdown] Async cleanup failed:', err);
      clearTimeout(shutdownTimeout);
      if (databaseService) { try { databaseService.close(); } catch { /* ignore */ } databaseService = null; }
      app.exit(1);
    });
  });

  // ===== 应用日志级别配置 =====
  const logLevel = configService.get<string>('logging.level') as 'error' | 'warn' | 'info' | 'debug' | 'verbose' | 'silly' | null;
  if (logLevel) {
    log.transports.file.level = logLevel;
    log.transports.console.level = logLevel;
    log.info(`Log level applied: ${logLevel}`);
  }

  // ===== 恢复工具启用/禁用状态 =====
  for (const toolDef of toolRegistry.listAllTools()) {
    const stored = configService.get<boolean>(`tools.${toolDef.name}.enabled`);
    if (stored === false) {
      toolRegistry.setToolEnabled(toolDef.name, false);
      log.info(`Tool restored as disabled: ${toolDef.name}`);
    }
  }

  log.info(`MetonaAI Desktop initialized [Provider: ${configService.get<string>('llm.provider') ?? 'none'}, Model: ${configService.get<string>('llm.model') ?? 'none'}, Tools: ${toolRegistry.size}]`);
}

app.whenReady().then(initialize);

app.on('web-contents-created', (_, contents) => {
  // C-8 修复: 全局 web-contents 监听器也校验 URL 协议
  contents.setWindowOpenHandler(({ url }) => {
    try {
      const parsed = new URL(url);
      if (parsed.protocol === 'http:' || parsed.protocol === 'https:') {
        shell.openExternal(url);
      } else {
        log.warn(`[Main] Blocked window.open with unsafe protocol: ${parsed.protocol}`);
      }
    } catch {
      log.warn(`[Main] Blocked window.open with invalid URL: ${url.slice(0, 100)}`);
    }
    return { action: 'deny' };
  });
});
