import path from "path";
import fs from "fs";
import { BrowserWindow, BrowserView, Updater } from "electrobun/bun";
import { PtyClient } from "./pty-client.ts";
import { settingsDb } from "./settings-db.ts";
import { sessionDb } from "./session-db.ts";
import { btmsgDb } from "./btmsg-db.ts";
import { bttaskDb } from "./bttask-db.ts";
import { SidecarManager } from "./sidecar-manager.ts";
import type { PtyRPCSchema } from "../shared/pty-rpc-schema.ts";
import { randomUUID } from "crypto";
import { SearchDb } from "./search-db.ts";
import { homedir } from "os";
import { join } from "path";

const DEV_SERVER_PORT = 9760; // Project convention: 9700+ range
const DEV_SERVER_URL = `http://localhost:${DEV_SERVER_PORT}`;

// ── PTY daemon client ────────────────────────────────────────────────────────

const ptyClient = new PtyClient();
const sidecarManager = new SidecarManager();
const searchDb = new SearchDb();
const PLUGINS_DIR = join(homedir(), ".config", "agor", "plugins");

async function connectToDaemon(retries = 5, delayMs = 500): Promise<boolean> {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      await ptyClient.connect();
      console.log("[agor-ptyd] Connected to PTY daemon");
      return true;
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      if (attempt < retries) {
        console.warn(`[agor-ptyd] Connect attempt ${attempt}/${retries} failed: ${msg}. Retrying in ${delayMs}ms…`);
        await new Promise((r) => setTimeout(r, delayMs));
        delayMs = Math.min(delayMs * 2, 4000);
      } else {
        console.error(`[agor-ptyd] Could not connect after ${retries} attempts: ${msg}`);
        console.error("[agor-ptyd] Terminals will not work. Start agor-ptyd and restart the app.");
      }
    }
  }
  return false;
}

// ── Clone helpers ────────────────────────────────────────────────────────────

const BRANCH_RE = /^[a-zA-Z0-9/_.-]+$/;

async function gitWorktreeAdd(mainRepoPath: string, worktreePath: string, branchName: string): Promise<{ ok: boolean; error?: string }> {
  const proc = Bun.spawn(
    ["git", "worktree", "add", worktreePath, "-b", branchName],
    { cwd: mainRepoPath, stderr: "pipe", stdout: "pipe" }
  );
  const exitCode = await proc.exited;
  if (exitCode !== 0) {
    const errText = await new Response(proc.stderr).text();
    return { ok: false, error: errText.trim() || `git exited with code ${exitCode}` };
  }
  return { ok: true };
}

// ── RPC definition ────────────────────────────────────────────────────────────

const rpc = BrowserView.defineRPC<PtyRPCSchema>({
  maxRequestTime: 15_000,
  handlers: {
    requests: {
      "pty.create": async ({ sessionId, cols, rows, cwd }) => {
        if (!ptyClient.isConnected) {
          return { ok: false, error: "PTY daemon not connected" };
        }
        try {
          ptyClient.createSession({ id: sessionId, cols, rows, cwd });
          return { ok: true };
        } catch (err) {
          const error = err instanceof Error ? err.message : String(err);
          console.error(`[pty.create] ${sessionId}: ${error}`);
          return { ok: false, error };
        }
      },

      "pty.write": ({ sessionId, data }) => {
        if (!ptyClient.isConnected) {
          console.error(`[pty.write] ${sessionId}: daemon not connected`);
          return { ok: false };
        }
        try {
          ptyClient.writeInput(sessionId, data);
          return { ok: true };
        } catch (err) {
          const error = err instanceof Error ? err.message : String(err);
          console.error(`[pty.write] ${sessionId}: ${error}`);
          return { ok: false };
        }
      },

      "pty.resize": ({ sessionId, cols, rows }) => {
        if (!ptyClient.isConnected) return { ok: true };
        try {
          ptyClient.resize(sessionId, cols, rows);
        } catch (err) {
          console.error(`[pty.resize] ${sessionId}:`, err);
        }
        return { ok: true };
      },

      "pty.unsubscribe": ({ sessionId }) => {
        try {
          ptyClient.unsubscribe(sessionId);
        } catch (err) {
          console.error(`[pty.unsubscribe] ${sessionId}:`, err);
        }
        return { ok: true };
      },

      "pty.close": ({ sessionId }) => {
        try {
          ptyClient.closeSession(sessionId);
        } catch (err) {
          console.error(`[pty.close] ${sessionId}:`, err);
        }
        return { ok: true };
      },

      // ── Settings handlers ─────────────────────────────────────────────────

      "settings.get": ({ key }) => {
        try {
          return { value: settingsDb.getSetting(key) };
        } catch (err) {
          console.error("[settings.get]", err);
          return { value: null };
        }
      },

      "settings.set": ({ key, value }) => {
        try {
          settingsDb.setSetting(key, value);
          return { ok: true };
        } catch (err) {
          console.error("[settings.set]", err);
          return { ok: false };
        }
      },

      "settings.getAll": () => {
        try {
          return { settings: settingsDb.getAll() };
        } catch (err) {
          console.error("[settings.getAll]", err);
          return { settings: {} };
        }
      },

      "settings.getProjects": () => {
        try {
          const projects = settingsDb.listProjects().map((p) => ({
            id: p.id,
            config: JSON.stringify(p),
          }));
          return { projects };
        } catch (err) {
          console.error("[settings.getProjects]", err);
          return { projects: [] };
        }
      },

      "settings.setProject": ({ id, config }) => {
        try {
          const parsed = JSON.parse(config);
          settingsDb.setProject(id, { id, ...parsed });
          return { ok: true };
        } catch (err) {
          console.error("[settings.setProject]", err);
          return { ok: false };
        }
      },

      // ── Custom Themes handlers ───────────────────────────────────────────

      "themes.getCustom": () => {
        try {
          return { themes: settingsDb.getCustomThemes() };
        } catch (err) {
          console.error("[themes.getCustom]", err);
          return { themes: [] };
        }
      },

      "themes.saveCustom": ({ id, name, palette }) => {
        try {
          settingsDb.saveCustomTheme(id, name, palette);
          return { ok: true };
        } catch (err) {
          console.error("[themes.saveCustom]", err);
          return { ok: false };
        }
      },

      "themes.deleteCustom": ({ id }) => {
        try {
          settingsDb.deleteCustomTheme(id);
          return { ok: true };
        } catch (err) {
          console.error("[themes.deleteCustom]", err);
          return { ok: false };
        }
      },

      // ── File I/O handlers ────────────────────────────────────────────────

      "files.list": async ({ path: dirPath }) => {
        try {
          const dirents = fs.readdirSync(dirPath, { withFileTypes: true });
          const entries = dirents
            .filter((d) => !d.name.startsWith("."))
            .map((d) => {
              let size = 0;
              if (d.isFile()) {
                try {
                  size = fs.statSync(path.join(dirPath, d.name)).size;
                } catch { /* ignore stat errors */ }
              }
              return {
                name: d.name,
                type: (d.isDirectory() ? "dir" : "file") as "file" | "dir",
                size,
              };
            })
            .sort((a, b) => {
              // Directories first, then alphabetical
              if (a.type !== b.type) return a.type === "dir" ? -1 : 1;
              return a.name.localeCompare(b.name);
            });
          return { entries };
        } catch (err) {
          const error = err instanceof Error ? err.message : String(err);
          console.error("[files.list]", error);
          return { entries: [], error };
        }
      },

      "files.read": async ({ path: filePath }) => {
        try {
          const stat = fs.statSync(filePath);
          const MAX_SIZE = 10 * 1024 * 1024; // 10MB
          if (stat.size > MAX_SIZE) {
            return { encoding: "utf8" as const, size: stat.size, error: `File too large (${(stat.size / 1024 / 1024).toFixed(1)}MB). Maximum is 10MB.` };
          }

          // Detect binary by reading first 8KB
          const buf = Buffer.alloc(Math.min(8192, stat.size));
          const fd = fs.openSync(filePath, "r");
          fs.readSync(fd, buf, 0, buf.length, 0);
          fs.closeSync(fd);

          const isBinary = buf.includes(0); // null byte = binary
          if (isBinary) {
            const content = fs.readFileSync(filePath).toString("base64");
            return { content, encoding: "base64" as const, size: stat.size };
          }

          const content = fs.readFileSync(filePath, "utf8");
          return { content, encoding: "utf8" as const, size: stat.size };
        } catch (err) {
          const error = err instanceof Error ? err.message : String(err);
          console.error("[files.read]", error);
          return { encoding: "utf8" as const, size: 0, error };
        }
      },

      "files.write": async ({ path: filePath, content }) => {
        try {
          fs.writeFileSync(filePath, content, "utf8");
          return { ok: true };
        } catch (err) {
          const error = err instanceof Error ? err.message : String(err);
          console.error("[files.write]", error);
          return { ok: false, error };
        }
      },

      // ── Groups handlers ──────────────────────────────────────────────────

      "groups.list": () => {
        try {
          return { groups: settingsDb.listGroups() };
        } catch (err) {
          console.error("[groups.list]", err);
          return { groups: [] };
        }
      },

      // ── Project clone handler ────────────────────────────────────────────

      "project.clone": async ({ projectId, branchName }) => {
        try {
          if (!BRANCH_RE.test(branchName)) {
            return { ok: false, error: "Invalid branch name. Use only letters, numbers, /, _, -, ." };
          }

          const source = settingsDb.getProject(projectId);
          if (!source) {
            return { ok: false, error: `Project not found: ${projectId}` };
          }

          // Determine the authoritative main repo path
          const mainRepoPath = source.mainRepoPath ?? source.cwd;

          // Count existing clones
          const allProjects = settingsDb.listProjects();
          const existingClones = allProjects.filter(
            (p) => p.cloneOf === projectId || (source.cloneOf && p.cloneOf === source.cloneOf)
          );
          if (existingClones.length >= 3) {
            return { ok: false, error: "Maximum 3 clones per project reached" };
          }

          const cloneIndex = existingClones.length + 1;
          // Fix #8: Use UUID suffix to prevent race conditions between concurrent clones
          const wtSuffix = randomUUID().slice(0, 8);
          const worktreePath = `${mainRepoPath}-wt-${wtSuffix}`;

          const gitResult = await gitWorktreeAdd(mainRepoPath, worktreePath, branchName);
          if (!gitResult.ok) {
            return { ok: false, error: gitResult.error };
          }

          const cloneId = `${projectId}-clone-${cloneIndex}-${randomUUID().slice(0, 8)}`;
          const cloneConfig = {
            id: cloneId,
            name: `${source.name} [${branchName}]`,
            cwd: worktreePath,
            accent: source.accent,
            provider: source.provider,
            profile: source.profile,
            model: source.model,
            groupId: source.groupId ?? "dev",
            mainRepoPath,
            cloneOf: projectId,
            worktreePath,
            worktreeBranch: branchName,
            cloneIndex,
          };

          settingsDb.setProject(cloneId, cloneConfig);

          return {
            ok: true,
            project: { id: cloneId, config: JSON.stringify(cloneConfig) },
          };
        } catch (err) {
          const error = err instanceof Error ? err.message : String(err);
          console.error("[project.clone]", err);
          return { ok: false, error };
        }
      },

      // ── Window control handlers ──────────────────────────────────────────

      "window.minimize": () => {
        try {
          mainWindow.minimize();
          return { ok: true };
        } catch (err) {
          console.error("[window.minimize]", err);
          return { ok: false };
        }
      },

      "window.maximize": () => {
        try {
          const frame = mainWindow.getFrame();
          // Heuristic: if window is already near the screen edge, unmaximize
          if (frame.x <= 0 && frame.y <= 0) {
            mainWindow.unmaximize();
          } else {
            mainWindow.maximize();
          }
          return { ok: true };
        } catch (err) {
          console.error("[window.maximize]", err);
          return { ok: false };
        }
      },

      "window.close": () => {
        try {
          mainWindow.close();
          return { ok: true };
        } catch (err) {
          console.error("[window.close]", err);
          return { ok: false };
        }
      },

      "window.getFrame": () => {
        try {
          return mainWindow.getFrame();
        } catch {
          return { x: 0, y: 0, width: 1400, height: 900 };
        }
      },

      "window.setPosition": ({ x, y }: { x: number; y: number }) => {
        try {
          mainWindow.setPosition(x, y);
          return { ok: true };
        } catch {
          return { ok: false };
        }
      },

      // ── Keybinding handlers ──────────────────────────────────────────────

      "keybindings.getAll": () => {
        try {
          return { keybindings: settingsDb.getKeybindings() };
        } catch (err) {
          console.error("[keybindings.getAll]", err);
          return { keybindings: {} };
        }
      },

      "keybindings.set": ({ id, chord }) => {
        try {
          settingsDb.setKeybinding(id, chord);
          return { ok: true };
        } catch (err) {
          console.error("[keybindings.set]", err);
          return { ok: false };
        }
      },

      "keybindings.reset": ({ id }) => {
        try {
          settingsDb.deleteKeybinding(id);
          return { ok: true };
        } catch (err) {
          console.error("[keybindings.reset]", err);
          return { ok: false };
        }
      },

      // ── Agent handlers ──────────────────────────────────────────────────

      "agent.start": ({ sessionId, provider, prompt, cwd, model, systemPrompt, maxTurns, permissionMode, claudeConfigDir, extraEnv }) => {
        try {
          const result = sidecarManager.startSession(sessionId, provider, prompt, {
            cwd,
            model,
            systemPrompt,
            maxTurns,
            permissionMode,
            claudeConfigDir,
            extraEnv,
          });

          if (result.ok) {
            // Forward sidecar messages to webview
            sidecarManager.onMessage(sessionId, (sid, messages) => {
              try {
                rpc.send["agent.message"]({ sessionId: sid, messages });
              } catch (err) {
                console.error("[agent.message] forward error:", err);
              }
            });

            sidecarManager.onStatus(sessionId, (sid, status, error) => {
              try {
                rpc.send["agent.status"]({ sessionId: sid, status, error });
              } catch (err) {
                console.error("[agent.status] forward error:", err);
              }

              // Send cost update on status change
              const sessions = sidecarManager.listSessions();
              const session = sessions.find((s) => s.sessionId === sid);
              if (session) {
                try {
                  rpc.send["agent.cost"]({
                    sessionId: sid,
                    costUsd: session.costUsd,
                    inputTokens: session.inputTokens,
                    outputTokens: session.outputTokens,
                  });
                } catch { /* ignore */ }
              }
            });
          }

          return result;
        } catch (err) {
          const error = err instanceof Error ? err.message : String(err);
          console.error("[agent.start]", err);
          return { ok: false, error };
        }
      },

      "agent.stop": ({ sessionId }) => {
        try {
          return sidecarManager.stopSession(sessionId);
        } catch (err) {
          const error = err instanceof Error ? err.message : String(err);
          console.error("[agent.stop]", err);
          return { ok: false, error };
        }
      },

      "agent.prompt": ({ sessionId, prompt }) => {
        try {
          return sidecarManager.writePrompt(sessionId, prompt);
        } catch (err) {
          const error = err instanceof Error ? err.message : String(err);
          console.error("[agent.prompt]", err);
          return { ok: false, error };
        }
      },

      "agent.list": () => {
        try {
          return { sessions: sidecarManager.listSessions() };
        } catch (err) {
          console.error("[agent.list]", err);
          return { sessions: [] };
        }
      },

      // ── Session persistence handlers ──────────────────────────────────

      "session.save": ({ projectId, sessionId, provider, status, costUsd, inputTokens, outputTokens, model, error, createdAt, updatedAt }) => {
        try {
          sessionDb.saveSession({
            projectId, sessionId, provider, status, costUsd,
            inputTokens, outputTokens, model, error, createdAt, updatedAt,
          });
          return { ok: true };
        } catch (err) {
          console.error("[session.save]", err);
          return { ok: false };
        }
      },

      "session.load": ({ projectId }) => {
        try {
          return { session: sessionDb.loadSession(projectId) };
        } catch (err) {
          console.error("[session.load]", err);
          return { session: null };
        }
      },

      "session.list": ({ projectId }) => {
        try {
          return { sessions: sessionDb.listSessionsByProject(projectId) };
        } catch (err) {
          console.error("[session.list]", err);
          return { sessions: [] };
        }
      },

      "session.messages.save": ({ messages }) => {
        try {
          sessionDb.saveMessages(messages.map((m) => ({
            sessionId: m.sessionId, msgId: m.msgId, role: m.role,
            content: m.content, toolName: m.toolName, toolInput: m.toolInput,
            timestamp: m.timestamp, costUsd: m.costUsd ?? 0,
            inputTokens: m.inputTokens ?? 0, outputTokens: m.outputTokens ?? 0,
          })));
          return { ok: true };
        } catch (err) {
          console.error("[session.messages.save]", err);
          return { ok: false };
        }
      },

      "session.messages.load": ({ sessionId }) => {
        try {
          return { messages: sessionDb.loadMessages(sessionId) };
        } catch (err) {
          console.error("[session.messages.load]", err);
          return { messages: [] };
        }
      },

      // ── btmsg handlers ────────────────────────────────────────────────

      "btmsg.registerAgent": ({ id, name, role, groupId, tier, model }) => {
        try {
          btmsgDb.registerAgent(id, name, role, groupId, tier, model);
          return { ok: true };
        } catch (err) {
          console.error("[btmsg.registerAgent]", err);
          return { ok: false };
        }
      },

      "btmsg.getAgents": ({ groupId }) => {
        try {
          return { agents: btmsgDb.getAgents(groupId) };
        } catch (err) {
          console.error("[btmsg.getAgents]", err);
          return { agents: [] };
        }
      },

      "btmsg.sendMessage": ({ fromAgent, toAgent, content }) => {
        try {
          const messageId = btmsgDb.sendMessage(fromAgent, toAgent, content);
          return { ok: true, messageId };
        } catch (err) {
          const error = err instanceof Error ? err.message : String(err);
          console.error("[btmsg.sendMessage]", err);
          return { ok: false, error };
        }
      },

      "btmsg.listMessages": ({ agentId, otherId, limit }) => {
        try {
          return { messages: btmsgDb.listMessages(agentId, otherId, limit ?? 50) };
        } catch (err) {
          console.error("[btmsg.listMessages]", err);
          return { messages: [] };
        }
      },

      "btmsg.markRead": ({ agentId, messageIds }) => {
        try {
          btmsgDb.markRead(agentId, messageIds);
          return { ok: true };
        } catch (err) {
          console.error("[btmsg.markRead]", err);
          return { ok: false };
        }
      },

      "btmsg.listChannels": ({ groupId }) => {
        try {
          return { channels: btmsgDb.listChannels(groupId) };
        } catch (err) {
          console.error("[btmsg.listChannels]", err);
          return { channels: [] };
        }
      },

      "btmsg.createChannel": ({ name, groupId, createdBy }) => {
        try {
          const channelId = btmsgDb.createChannel(name, groupId, createdBy);
          return { ok: true, channelId };
        } catch (err) {
          console.error("[btmsg.createChannel]", err);
          return { ok: false };
        }
      },

      "btmsg.getChannelMessages": ({ channelId, limit }) => {
        try {
          return { messages: btmsgDb.getChannelMessages(channelId, limit ?? 100) };
        } catch (err) {
          console.error("[btmsg.getChannelMessages]", err);
          return { messages: [] };
        }
      },

      "btmsg.sendChannelMessage": ({ channelId, fromAgent, content }) => {
        try {
          const messageId = btmsgDb.sendChannelMessage(channelId, fromAgent, content);
          return { ok: true, messageId };
        } catch (err) {
          console.error("[btmsg.sendChannelMessage]", err);
          return { ok: false };
        }
      },

      "btmsg.heartbeat": ({ agentId }) => {
        try {
          btmsgDb.heartbeat(agentId);
          return { ok: true };
        } catch (err) {
          console.error("[btmsg.heartbeat]", err);
          return { ok: false };
        }
      },

      "btmsg.getDeadLetters": ({ limit }) => {
        try {
          return { letters: btmsgDb.getDeadLetters(limit ?? 50) };
        } catch (err) {
          console.error("[btmsg.getDeadLetters]", err);
          return { letters: [] };
        }
      },

      "btmsg.logAudit": ({ agentId, eventType, detail }) => {
        try {
          btmsgDb.logAudit(agentId, eventType, detail);
          return { ok: true };
        } catch (err) {
          console.error("[btmsg.logAudit]", err);
          return { ok: false };
        }
      },

      "btmsg.getAuditLog": ({ limit }) => {
        try {
          return { entries: btmsgDb.getAuditLog(limit ?? 100) };
        } catch (err) {
          console.error("[btmsg.getAuditLog]", err);
          return { entries: [] };
        }
      },

      // ── bttask handlers ───────────────────────────────────────────────

      "bttask.listTasks": ({ groupId }) => {
        try {
          return { tasks: bttaskDb.listTasks(groupId) };
        } catch (err) {
          console.error("[bttask.listTasks]", err);
          return { tasks: [] };
        }
      },

      "bttask.createTask": ({ title, description, priority, groupId, createdBy, assignedTo }) => {
        try {
          const taskId = bttaskDb.createTask(title, description, priority, groupId, createdBy, assignedTo);
          return { ok: true, taskId };
        } catch (err) {
          const error = err instanceof Error ? err.message : String(err);
          console.error("[bttask.createTask]", err);
          return { ok: false, error };
        }
      },

      "bttask.updateTaskStatus": ({ taskId, status, expectedVersion }) => {
        try {
          const newVersion = bttaskDb.updateTaskStatus(taskId, status, expectedVersion);
          return { ok: true, newVersion };
        } catch (err) {
          const error = err instanceof Error ? err.message : String(err);
          console.error("[bttask.updateTaskStatus]", err);
          return { ok: false, error };
        }
      },

      "bttask.deleteTask": ({ taskId }) => {
        try {
          bttaskDb.deleteTask(taskId);
          return { ok: true };
        } catch (err) {
          console.error("[bttask.deleteTask]", err);
          return { ok: false };
        }
      },

      "bttask.addComment": ({ taskId, agentId, content }) => {
        try {
          const commentId = bttaskDb.addComment(taskId, agentId, content);
          return { ok: true, commentId };
        } catch (err) {
          console.error("[bttask.addComment]", err);
          return { ok: false };
        }
      },

      "bttask.listComments": ({ taskId }) => {
        try {
          return { comments: bttaskDb.listComments(taskId) };
        } catch (err) {
          console.error("[bttask.listComments]", err);
          return { comments: [] };
        }
      },

      "bttask.reviewQueueCount": ({ groupId }) => {
        try {
          return { count: bttaskDb.reviewQueueCount(groupId) };
        } catch (err) {
          console.error("[bttask.reviewQueueCount]", err);
          return { count: 0 };
        }
      },

      // ── Search handlers ──────────────────────────────────────────────────

      "search.query": ({ query, limit }) => {
        try {
          const results = searchDb.searchAll(query, limit ?? 20);
          return { results };
        } catch (err) {
          console.error("[search.query]", err);
          return { results: [] };
        }
      },

      "search.indexMessage": ({ sessionId, role, content }) => {
        try {
          searchDb.indexMessage(sessionId, role, content);
          return { ok: true };
        } catch (err) {
          console.error("[search.indexMessage]", err);
          return { ok: false };
        }
      },

      "search.rebuild": () => {
        try {
          searchDb.rebuildIndex();
          return { ok: true };
        } catch (err) {
          console.error("[search.rebuild]", err);
          return { ok: false };
        }
      },

      // ── Plugin handlers ──────────────────────────────────────────────────

      "plugin.discover": () => {
        try {
          const plugins: Array<{
            id: string; name: string; version: string;
            description: string; main: string; permissions: string[];
          }> = [];

          if (!fs.existsSync(PLUGINS_DIR)) return { plugins };

          const entries = fs.readdirSync(PLUGINS_DIR, { withFileTypes: true });
          for (const entry of entries) {
            if (!entry.isDirectory()) continue;
            const manifestPath = join(PLUGINS_DIR, entry.name, "plugin.json");
            if (!fs.existsSync(manifestPath)) continue;

            try {
              const raw = fs.readFileSync(manifestPath, "utf-8");
              const manifest = JSON.parse(raw);
              plugins.push({
                id: manifest.id ?? entry.name,
                name: manifest.name ?? entry.name,
                version: manifest.version ?? "0.0.0",
                description: manifest.description ?? "",
                main: manifest.main ?? "index.js",
                permissions: Array.isArray(manifest.permissions) ? manifest.permissions : [],
              });
            } catch (parseErr) {
              console.error(`[plugin.discover] Bad manifest in ${entry.name}:`, parseErr);
            }
          }

          return { plugins };
        } catch (err) {
          console.error("[plugin.discover]", err);
          return { plugins: [] };
        }
      },

      "plugin.readFile": ({ pluginId, filePath }) => {
        try {
          // Path traversal protection: resolve and verify within plugins dir
          const pluginDir = join(PLUGINS_DIR, pluginId);
          const resolved = path.resolve(pluginDir, filePath);
          if (!resolved.startsWith(pluginDir + path.sep) && resolved !== pluginDir) {
            return { ok: false, content: "", error: "Path traversal blocked" };
          }
          if (!fs.existsSync(resolved)) {
            return { ok: false, content: "", error: "File not found" };
          }
          const content = fs.readFileSync(resolved, "utf-8");
          return { ok: true, content };
        } catch (err) {
          const error = err instanceof Error ? err.message : String(err);
          console.error("[plugin.readFile]", err);
          return { ok: false, content: "", error };
        }
      },
    },

    messages: {},
  },
});

// ── Forward daemon events to WebView ────────────────────────────────────────

ptyClient.on("session_output", (msg) => {
  if (msg.type !== "session_output") return;
  try {
    rpc.send["pty.output"]({ sessionId: msg.session_id, data: msg.data });
  } catch (err) {
    console.error("[pty.output] forward error:", err);
  }
});

ptyClient.on("session_closed", (msg) => {
  if (msg.type !== "session_closed") return;
  try {
    rpc.send["pty.closed"]({ sessionId: msg.session_id, exitCode: msg.exit_code });
  } catch (err) {
    console.error("[pty.closed] forward error:", err);
  }
});

// ── App window ───────────────────────────────────────────────────────────────

async function getMainViewUrl(): Promise<string> {
  const channel = await Updater.localInfo.channel();
  if (channel === "dev") {
    try {
      await fetch(DEV_SERVER_URL, { method: "HEAD" });
      console.log(`HMR enabled: Using Vite dev server at ${DEV_SERVER_URL}`);
      return DEV_SERVER_URL;
    } catch {
      console.log(
        "Vite dev server not running. Run 'bun run dev:hmr' for HMR support.",
      );
    }
  }
  return "views://mainview/index.html";
}

// Connect to daemon (non-blocking — window opens regardless).
connectToDaemon();

const url = await getMainViewUrl();

// Restore persisted window frame if available
const savedX      = Number(settingsDb.getSetting("win_x")      ?? 100);
const savedY      = Number(settingsDb.getSetting("win_y")      ?? 100);
const savedWidth  = Number(settingsDb.getSetting("win_width")  ?? 1400);
const savedHeight = Number(settingsDb.getSetting("win_height") ?? 900);

const mainWindow = new BrowserWindow({
  title: "Agent Orchestrator",
  titleBarStyle: "default", // "hidden" breaks clicks on WebKitGTK — testing with default
  url,
  rpc,
  frame: {
    width:  isNaN(savedWidth)  ? 1400 : savedWidth,
    height: isNaN(savedHeight) ? 900  : savedHeight,
    x:      isNaN(savedX)      ? 100  : savedX,
    y:      isNaN(savedY)      ? 100  : savedY,
  },
});

console.log("Agent Orchestrator (Electrobun) started!");
