/**
 * Shared RPC schema for PTY bridge between Bun process and WebView.
 *
 * Bun holds the Unix socket connection to agor-ptyd; the WebView calls
 * into Bun via requests, and Bun pushes output/close events via messages.
 */

// ── Requests (WebView → Bun, expects a response) ─────────────────────────────

export type PtyRPCRequests = {
  /** Create a PTY session and subscribe to its output. */
  "pty.create": {
    params: {
      sessionId: string;
      cols: number;
      rows: number;
      /** Working directory for the shell process. */
      cwd?: string;
      /** Override shell binary (e.g. /usr/bin/ssh). Fix #3: direct spawn, no shell injection. */
      shell?: string;
      /** Arguments for the shell binary (e.g. ['-p', '22', 'user@host']). */
      args?: string[];
    };
    response: { ok: boolean; error?: string };
  };
  /**
   * Write input to a PTY session.
   * `data` is raw UTF-8 text from the user (xterm onData). The pty-client
   * layer encodes it to base64 before sending to the daemon; this RPC boundary
   * carries raw text which the Bun handler forwards to PtyClient.writeInput().
   */
  "pty.write": {
    params: {
      sessionId: string;
      /** Raw UTF-8 text typed by the user (xterm onData delivers this). Encoded to base64 by pty-client before daemon transport. */
      data: string;
    };
    response: { ok: boolean };
  };
  /** Notify the daemon that the terminal dimensions changed. */
  "pty.resize": {
    params: { sessionId: string; cols: number; rows: number };
    response: { ok: boolean };
  };
  /** Unsubscribe from a session's output (session stays alive). */
  "pty.unsubscribe": {
    params: { sessionId: string };
    response: { ok: boolean };
  };
  /** Kill a PTY session. */
  "pty.close": {
    params: { sessionId: string };
    response: { ok: boolean };
  };

  // ── Settings RPC ───────────────────────────────────────────────────────────

  /** Get a single setting value by key. Returns null if not set. */
  "settings.get": {
    params: { key: string };
    response: { value: string | null };
  };
  /** Persist a setting key/value pair. */
  "settings.set": {
    params: { key: string; value: string };
    response: { ok: boolean };
  };
  /** Return all settings as a flat object. */
  "settings.getAll": {
    params: Record<string, never>;
    response: { settings: Record<string, string> };
  };
  /** Return all persisted projects. */
  "settings.getProjects": {
    params: Record<string, never>;
    response: { projects: Array<{ id: string; config: string }> };
  };
  /** Persist a project config (JSON-serialised on the caller side). */
  "settings.setProject": {
    params: { id: string; config: string };
    response: { ok: boolean };
  };
  /** Delete a project by id. */
  "settings.deleteProject": {
    params: { id: string };
    response: { ok: boolean };
  };

  // ── Custom Themes RPC ──────────────────────────────────────────────────────

  /** Return all user-saved custom themes. */
  "themes.getCustom": {
    params: Record<string, never>;
    response: { themes: Array<{ id: string; name: string; palette: Record<string, string> }> };
  };
  /** Save (upsert) a custom theme by id. */
  "themes.saveCustom": {
    params: { id: string; name: string; palette: Record<string, string> };
    response: { ok: boolean };
  };
  /** Delete a custom theme by id. */
  "themes.deleteCustom": {
    params: { id: string };
    response: { ok: boolean };
  };

  // ── File I/O RPC ──────────────────────────────────────────────────────────

  /** List directory children (files + subdirs). Returns sorted entries. */
  "files.list": {
    params: { path: string };
    response: {
      entries: Array<{
        name: string;
        type: "file" | "dir";
        size: number;
      }>;
      error?: string;
    };
  };
  /** Read a file's content. Returns text for text files, base64 for binary. */
  "files.read": {
    params: { path: string };
    response: {
      content?: string;
      encoding: "utf8" | "base64";
      size: number;
      error?: string;
    };
  };
  /** Get file stat info (mtime, size) for conflict detection. */
  "files.stat": {
    params: { path: string };
    response: { mtimeMs: number; size: number; error?: string };
  };
  /** Extended stat — directory check, git detection, writability. Used by ProjectWizard. */
  "files.statEx": {
    params: { path: string };
    response: {
      exists: boolean;
      isDirectory: boolean;
      isGitRepo: boolean;
      gitBranch?: string;
      size?: number;
      writable?: boolean;
      error?: string;
    };
  };
  "files.ensureDir": {
    params: { path: string };
    response: { ok: boolean; path?: string; error?: string };
  };
  /** Unguarded directory listing for PathBrowser (dirs only, no file content) */
  "files.browse": {
    params: { path: string };
    response: { entries: { name: string; type: 'dir'; size: number }[]; error?: string };
  };
  /** Native folder picker dialog */
  "files.pickDirectory": {
    params: { startingFolder?: string };
    response: { path: string | null };
  };
  /** Get home directory path */
  "files.homeDir": {
    params: {};
    response: { path: string };
  };
  /** Write text content to a file (atomic temp+rename). */
  "files.write": {
    params: { path: string; content: string };
    response: { ok: boolean; error?: string };
  };

  // ── Groups RPC ─────────────────────────────────────────────────────────────

  /** Return all project groups. */
  "groups.list": {
    params: Record<string, never>;
    response: { groups: Array<{ id: string; name: string; icon: string; position: number }> };
  };
  /** Create a new group. */
  "groups.create": {
    params: { id: string; name: string; icon: string; position: number };
    response: { ok: boolean };
  };
  /** Delete a group by id. */
  "groups.delete": {
    params: { id: string };
    response: { ok: boolean };
  };

  // ── Memora RPC ──────────────────────────────────────────────────────────

  /** Search memories by query text (FTS5). */
  "memora.search": {
    params: { query: string; limit?: number };
    response: {
      memories: Array<{
        id: number;
        content: string;
        tags: string;
        metadata: string;
        createdAt: string;
        updatedAt: string;
      }>;
    };
  };
  /** List recent memories. */
  "memora.list": {
    params: { limit?: number; tag?: string };
    response: {
      memories: Array<{
        id: number;
        content: string;
        tags: string;
        metadata: string;
        createdAt: string;
        updatedAt: string;
      }>;
    };
  };

  // ── Project clone RPC ──────────────────────────────────────────────────────

  /** Clone a project into a git worktree. branchName must match /^[a-zA-Z0-9\/_.-]+$/. */
  "project.clone": {
    params: { projectId: string; branchName: string };
    response: { ok: boolean; project?: { id: string; config: string }; error?: string };
  };

  // ── Git RPC ──────────────────────────────────────────────────────────────────

  /** List branches in a git repository. */
  "git.branches": {
    params: { path: string };
    response: { branches: string[]; current: string; error?: string };
  };
  /** Clone a git repository. */
  "git.clone": {
    params: { url: string; target: string; branch?: string };
    response: { ok: boolean; error?: string };
  };
  /** Probe a git remote URL (ls-remote). Returns branches on success. */
  "git.probe": {
    params: { url: string };
    response: { ok: boolean; branches: string[]; defaultBranch: string; error?: string };
  };

  /** Check if sshfs is installed. */
  "ssh.checkSshfs": {
    params: Record<string, never>;
    response: { installed: boolean; path: string | null };
  };

  /** Detect available shells on this system. */
  "system.shells": {
    params: Record<string, never>;
    response: { shells: Array<{ path: string; name: string }>; loginShell: string };
  };

  /** Detect installed system fonts (uses fc-list). */
  "system.fonts": {
    params: Record<string, never>;
    response: {
      uiFonts: Array<{ family: string; preferred: boolean }>;
      monoFonts: Array<{ family: string; isNerdFont: boolean }>;
    };
  };

  // ── Project templates RPC ───────────────────────────────────────────────────

  /** Return available project templates. Optionally pass custom template dir. */
  "project.templates": {
    params: { templateDir?: string };
    response: {
      templates: Array<{
        id: string;
        name: string;
        description: string;
        icon: string;
      }>;
    };
  };
  /** Create a project from a template with real scaffold files. */
  "project.createFromTemplate": {
    params: { templateId: string; targetDir: string; projectName: string };
    response: { ok: boolean; path: string; error?: string };
  };

  // ── Provider RPC ──────────────────────────────────────────────────────────

  /** Scan for available AI providers on this machine. */
  "provider.scan": {
    params: Record<string, never>;
    response: {
      providers: Array<{
        id: string;
        available: boolean;
        hasApiKey: boolean;
        hasCli: boolean;
        cliPath: string | null;
        version: string | null;
      }>;
    };
  };
  /** Fetch model list for a specific provider. */
  "provider.models": {
    params: { provider: string };
    response: {
      models: Array<{
        id: string;
        name: string;
        provider: string;
      }>;
    };
  };

  // ── Window control RPC ─────────────────────────────────────────────────────

  /** Minimize the main window. */
  "window.minimize": {
    params: Record<string, never>;
    response: { ok: boolean };
  };
  /** Toggle maximize/restore on the main window. */
  "window.maximize": {
    params: Record<string, never>;
    response: { ok: boolean };
  };
  /** Close the main window. */
  "window.close": {
    params: Record<string, never>;
    response: { ok: boolean };
  };
  /** Get current window frame (x, y, width, height). */
  "window.getFrame": {
    params: Record<string, never>;
    response: { x: number; y: number; width: number; height: number };
  };
  /** Set the window position. */
  "window.setPosition": {
    params: { x: number; y: number };
    response: { ok: boolean };
  };

  /** Begin native GTK resize drag — delegates to window manager. */
  "window.beginResize": {
    params: { edge: string; button: number; rootX: number; rootY: number };
    response: { ok: boolean; error?: string };
  };
  /** Begin native GTK move drag — delegates to window manager. */
  "window.beginMove": {
    params: { button: number; rootX: number; rootY: number };
    response: { ok: boolean };
  };

  // ── Keybindings RPC ────────────────────────────────────────────────────────

  /** Return all persisted custom keybindings (overrides only). */
  "keybindings.getAll": {
    params: Record<string, never>;
    response: { keybindings: Record<string, string> };
  };
  /** Persist a single keybinding override. */
  "keybindings.set": {
    params: { id: string; chord: string };
    response: { ok: boolean };
  };
  /** Reset a keybinding to default (removes override). */
  "keybindings.reset": {
    params: { id: string };
    response: { ok: boolean };
  };

  // ── Agent RPC ─────────────────────────────────────────────────────────────

  /** Start an agent session with a given provider. */
  "agent.start": {
    params: {
      sessionId: string;
      provider: "claude" | "codex" | "ollama";
      prompt: string;
      cwd?: string;
      model?: string;
      systemPrompt?: string;
      maxTurns?: number;
      permissionMode?: string;
      claudeConfigDir?: string;
      extraEnv?: Record<string, string>;
      additionalDirectories?: string[];
      worktreeName?: string;
    };
    response: { ok: boolean; error?: string };
  };
  /** Stop a running agent session. */
  "agent.stop": {
    params: { sessionId: string };
    response: { ok: boolean; error?: string };
  };
  /** Send a follow-up prompt to a running agent session. */
  "agent.prompt": {
    params: { sessionId: string; prompt: string };
    response: { ok: boolean; error?: string };
  };
  /** List all active agent sessions with their state. */
  "agent.list": {
    params: Record<string, never>;
    response: {
      sessions: Array<{
        sessionId: string;
        provider: string;
        status: string;
        costUsd: number;
        inputTokens: number;
        outputTokens: number;
        startedAt: number;
      }>;
    };
  };

  // ── Session persistence RPC ────────────────────────────────────────────

  /** Save/update a session record. */
  "session.save": {
    params: {
      projectId: string; sessionId: string; provider: string;
      status: string; costUsd: number; inputTokens: number;
      outputTokens: number; model: string; error?: string;
      createdAt: number; updatedAt: number;
    };
    response: { ok: boolean };
  };
  /** Load the most recent session for a project. */
  "session.load": {
    params: { projectId: string };
    response: {
      session: {
        projectId: string; sessionId: string; provider: string;
        status: string; costUsd: number; inputTokens: number;
        outputTokens: number; model: string; error?: string;
        createdAt: number; updatedAt: number;
      } | null;
    };
  };
  /** List sessions for a project (max 20). */
  "session.list": {
    params: { projectId: string };
    response: {
      sessions: Array<{
        projectId: string; sessionId: string; provider: string;
        status: string; costUsd: number; inputTokens: number;
        outputTokens: number; model: string; error?: string;
        createdAt: number; updatedAt: number;
      }>;
    };
  };
  /** Save agent messages (batch). */
  "session.messages.save": {
    params: {
      messages: Array<{
        sessionId: string; msgId: string; role: string; content: string;
        toolName?: string; toolInput?: string; timestamp: number;
        costUsd?: number; inputTokens?: number; outputTokens?: number;
      }>;
    };
    response: { ok: boolean };
  };
  /** Load all messages for a session. */
  "session.messages.load": {
    params: { sessionId: string };
    response: {
      messages: Array<{
        sessionId: string; msgId: string; role: string; content: string;
        toolName?: string; toolInput?: string; timestamp: number;
        costUsd: number; inputTokens: number; outputTokens: number;
      }>;
    };
  };

  // ── btmsg RPC ──────────────────────────────────────────────────────────

  /** Register an agent in btmsg. */
  "btmsg.registerAgent": {
    params: {
      id: string; name: string; role: string;
      groupId: string; tier: number; model?: string;
    };
    response: { ok: boolean };
  };
  /** List agents for a group. */
  "btmsg.getAgents": {
    params: { groupId: string };
    response: {
      agents: Array<{
        id: string; name: string; role: string; groupId: string;
        tier: number; model: string | null; status: string; unreadCount: number;
      }>;
    };
  };
  /** Send a direct message between agents. */
  "btmsg.sendMessage": {
    params: { fromAgent: string; toAgent: string; content: string };
    response: { ok: boolean; messageId?: string; error?: string };
  };
  /** Get message history between two agents. */
  "btmsg.listMessages": {
    params: { agentId: string; otherId: string; limit?: number };
    response: {
      messages: Array<{
        id: string; fromAgent: string; toAgent: string; content: string;
        read: boolean; replyTo: string | null; createdAt: string;
        senderName: string | null; senderRole: string | null;
      }>;
    };
  };
  /** Mark messages as read. */
  "btmsg.markRead": {
    params: { agentId: string; messageIds: string[] };
    response: { ok: boolean };
  };
  /** List channels for a group. */
  "btmsg.listChannels": {
    params: { groupId: string };
    response: {
      channels: Array<{
        id: string; name: string; groupId: string; createdBy: string;
        memberCount: number; createdAt: string;
      }>;
    };
  };
  /** Create a channel. */
  "btmsg.createChannel": {
    params: { name: string; groupId: string; createdBy: string };
    response: { ok: boolean; channelId?: string };
  };
  /** Get channel messages. */
  "btmsg.getChannelMessages": {
    params: { channelId: string; limit?: number };
    response: {
      messages: Array<{
        id: string; channelId: string; fromAgent: string; content: string;
        createdAt: string; senderName: string; senderRole: string;
      }>;
    };
  };
  /** Feature 7: Join a channel. */
  "btmsg.joinChannel": {
    params: { channelId: string; agentId: string };
    response: { ok: boolean; error?: string };
  };
  /** Feature 7: Leave a channel. */
  "btmsg.leaveChannel": {
    params: { channelId: string; agentId: string };
    response: { ok: boolean; error?: string };
  };
  /** Feature 7: Get channel member list. */
  "btmsg.getChannelMembers": {
    params: { channelId: string };
    response: { members: Array<{ agentId: string; name: string; role: string }> };
  };
  /** Send a channel message. */
  "btmsg.sendChannelMessage": {
    params: { channelId: string; fromAgent: string; content: string };
    response: { ok: boolean; messageId?: string };
  };
  /** Record agent heartbeat. */
  "btmsg.heartbeat": {
    params: { agentId: string };
    response: { ok: boolean };
  };
  /** Get dead letter queue entries. */
  "btmsg.getDeadLetters": {
    params: { limit?: number };
    response: {
      letters: Array<{
        id: number; fromAgent: string; toAgent: string;
        content: string; error: string; createdAt: string;
      }>;
    };
  };
  /** Log an audit event. */
  "btmsg.logAudit": {
    params: { agentId: string; eventType: string; detail: string };
    response: { ok: boolean };
  };
  /** Get audit log. */
  "btmsg.getAuditLog": {
    params: { limit?: number };
    response: {
      entries: Array<{
        id: number; agentId: string; eventType: string;
        detail: string; createdAt: string;
      }>;
    };
  };

  // ── bttask RPC ─────────────────────────────────────────────────────────

  /** List tasks for a group. */
  "bttask.listTasks": {
    params: { groupId: string };
    response: {
      tasks: Array<{
        id: string; title: string; description: string; status: string;
        priority: string; assignedTo: string | null; createdBy: string;
        groupId: string; parentTaskId: string | null; sortOrder: number;
        createdAt: string; updatedAt: string; version: number;
      }>;
    };
  };
  /** Create a task. */
  "bttask.createTask": {
    params: {
      title: string; description: string; priority: string;
      groupId: string; createdBy: string; assignedTo?: string;
    };
    response: { ok: boolean; taskId?: string; error?: string };
  };
  /** Update task status with optimistic locking. */
  "bttask.updateTaskStatus": {
    params: { taskId: string; status: string; expectedVersion: number };
    response: { ok: boolean; newVersion?: number; error?: string };
  };
  /** Delete a task. */
  "bttask.deleteTask": {
    params: { taskId: string };
    response: { ok: boolean };
  };
  /** Add a comment to a task. */
  "bttask.addComment": {
    params: { taskId: string; agentId: string; content: string };
    response: { ok: boolean; commentId?: string };
  };
  /** List comments for a task. */
  "bttask.listComments": {
    params: { taskId: string };
    response: {
      comments: Array<{
        id: string; taskId: string; agentId: string;
        content: string; createdAt: string;
      }>;
    };
  };
  /** Count tasks in 'review' status. */
  "bttask.reviewQueueCount": {
    params: { groupId: string };
    response: { count: number };
  };

  // ── Search RPC ──────────────────────────────────────────────────────────

  /** Full-text search across messages, tasks, and btmsg. Fix #13: typed error for invalid queries. */
  "search.query": {
    params: { query: string; limit?: number };
    response: {
      results: Array<{
        resultType: string;
        id: string;
        title: string;
        snippet: string;
        score: number;
      }>;
      /** Set when query is invalid (e.g. FTS5 syntax error). */
      error?: string;
    };
  };
  /** Index a message for search. */
  "search.indexMessage": {
    params: { sessionId: string; role: string; content: string };
    response: { ok: boolean };
  };
  /** Rebuild the entire search index. */
  "search.rebuild": {
    params: Record<string, never>;
    response: { ok: boolean };
  };

  // ── Plugin RPC ──────────────────────────────────────────────────────────

  /** Discover plugins from ~/.config/agor/plugins/. */
  "plugin.discover": {
    params: Record<string, never>;
    response: {
      plugins: Array<{
        id: string;
        name: string;
        version: string;
        description: string;
        main: string;
        permissions: string[];
      }>;
    };
  };
  /** Read a plugin file (path-traversal-safe). */
  "plugin.readFile": {
    params: { pluginId: string; filePath: string };
    response: { ok: boolean; content: string; error?: string };
  };

  // ── Remote machine (relay) RPC ────────────────────────────────────────────

  /** Connect to an agor-relay instance. */
  "remote.connect": {
    params: { url: string; token: string; label?: string };
    response: { ok: boolean; machineId?: string; error?: string };
  };
  /** Disconnect from a relay instance (keeps machine in list for reconnect). */
  "remote.disconnect": {
    params: { machineId: string };
    response: { ok: boolean; error?: string };
  };
  /** Remove a machine entirely — disconnects AND deletes from tracking. */
  "remote.remove": {
    params: { machineId: string };
    response: { ok: boolean; error?: string };
  };
  /** List all known remote machines with connection status. */
  "remote.list": {
    params: Record<string, never>;
    response: {
      machines: Array<{
        machineId: string;
        label: string;
        url: string;
        status: "connecting" | "connected" | "disconnected" | "error";
        latencyMs: number | null;
      }>;
    };
  };
  /** Send a command to a connected relay. */
  "remote.send": {
    params: { machineId: string; command: string; payload: Record<string, unknown> };
    response: { ok: boolean; error?: string };
  };
  /** Feature 3: Get stored relay credentials. */
  "remote.getStoredCredentials": {
    params: Record<string, never>;
    response: { credentials: Array<{ url: string; label: string }> };
  };
  /** Feature 3: Store a relay credential (XOR-obfuscated). */
  "remote.storeCredential": {
    params: { url: string; token: string; label?: string };
    response: { ok: boolean };
  };
  /** Feature 3: Delete a stored relay credential. */
  "remote.deleteCredential": {
    params: { url: string };
    response: { ok: boolean };
  };
  /** Get the status of a specific machine. */
  "remote.status": {
    params: { machineId: string };
    response: {
      status: "connecting" | "connected" | "disconnected" | "error";
      latencyMs: number | null;
      error?: string;
    };
  };

  // ── Telemetry RPC ─────────────────────────────────────────────────────────

  /** Feature 8: Transport diagnostics stats. */
  "diagnostics.stats": {
    params: Record<string, never>;
    response: {
      ptyConnected: boolean;
      relayConnections: number;
      activeSidecars: number;
      rpcCallCount: number;
      droppedEvents: number;
    };
  };

  /** Log a telemetry event from the frontend. */
  "telemetry.log": {
    params: {
      level: "info" | "warn" | "error";
      message: string;
      attributes?: Record<string, string | number | boolean>;
    };
    response: { ok: boolean };
  };

  // ── Updater RPC ──────────────────────────────────────────────────────────

  /** Check GitHub Releases for a newer version. */
  "updater.check": {
    params: Record<string, never>;
    response: {
      available: boolean;
      version: string;
      downloadUrl: string;
      releaseNotes: string;
      checkedAt: number;
      error?: string;
    };
  };
  /** Get the current app version and last check timestamp. */
  "updater.getVersion": {
    params: Record<string, never>;
    response: { version: string; lastCheck: number };
  };
};

// ── Messages (Bun → WebView, fire-and-forget) ────────────────────────────────

export type PtyRPCMessages = {
  /** PTY output chunk. data is base64-encoded raw bytes from the daemon. */
  "pty.output": { sessionId: string; data: string };
  /** PTY session exited. */
  "pty.closed": { sessionId: string; exitCode: number | null };

  // ── Agent events (Bun → WebView) ─────────────────────────────────────────

  /** Agent message(s) parsed from sidecar NDJSON. */
  "agent.message": {
    sessionId: string;
    messages: Array<{
      id: string;
      type: string;
      parentId?: string;
      content: unknown;
      timestamp: number;
    }>;
  };
  /** Agent session status change. */
  "agent.status": {
    sessionId: string;
    status: string;
    error?: string;
  };
  /** Agent cost/token update. */
  "agent.cost": {
    sessionId: string;
    costUsd: number;
    inputTokens: number;
    outputTokens: number;
  };

  // ── Remote machine events (Bun → WebView) ────────────────────────────────

  /** Remote relay event forwarded from a connected machine. */
  "remote.event": {
    machineId: string;
    eventType: string;
    sessionId?: string;
    payload?: unknown;
  };
  /** Remote machine connection status change. */
  "remote.statusChange": {
    machineId: string;
    status: "connecting" | "connected" | "disconnected" | "error";
    error?: string;
  };

  // Feature 4: Push-based task/relay updates
  /** Task board data changed (created, moved, deleted). */
  "bttask.changed": { groupId: string };
  /** New btmsg channel or DM message. */
  "btmsg.newMessage": { groupId: string; channelId?: string };
};

// ── Combined schema ───────────────────────────────────────────────────────────

export type PtyRPCSchema = {
  requests: PtyRPCRequests;
  messages: PtyRPCMessages;
};
