/**
 * Boundary types for the per-category tool modules under src/tools/.
 *
 * The CallToolRequest handler in src/index.ts resolves account routing,
 * permission gates, destructive-confirm, and response-size limits, then
 * invokes the per-tool handler with a fully-populated ToolCallContext.
 * Handlers never touch module-level singletons directly — they read
 * everything they need from ctx, which keeps the boundary explicit and
 * makes it safe to hot-swap services (e.g. per-account routing).
 *
 * ─────────────────────────────────────────────────────────────────────────
 * Error-shape rule for tool handlers
 * ─────────────────────────────────────────────────────────────────────────
 *
 * Tool handlers report failure in one of three shapes. Pick the shape that
 * matches the *agent's* mental model, not just whichever happens to be
 * convenient at the call site:
 *
 *   1. throw new McpError(ErrorCode.InvalidParams, msg)
 *      → Programmer/caller error: malformed, missing, oversized, or
 *        typed-wrong arguments. The MCP SDK serializes this as a JSON-RPC
 *        error response so the agent knows to retry the call differently.
 *
 *   2. throw new McpError(ErrorCode.InvalidRequest, msg)
 *      → Server-side precondition the call cannot satisfy regardless of
 *        arguments: service not configured, feature flag off, bridge
 *        unreachable, dependent CLI not installed. Treated as a JSON-RPC
 *        error so the agent surfaces the configuration gap instead of
 *        narrating it as a normal tool result.
 *
 *   3. return { isError: true, content, structuredContent }
 *      → Expected domain outcome: the tool ran correctly but the
 *        operation has nothing to report — email not found, attachment
 *        missing, folder doesn't exist, scheduler had no matching record,
 *        SMTP delivery rejected. The agent gets a tool-result shape it
 *        can reason about and relay to the user.
 *
 * When in doubt, prefer the shape already used in this file or in a
 * neighboring handler for the same kind of failure — consistency across
 * sites matters more than any individual judgment call.
 */

import type { SimpleIMAPService } from "../services/simple-imap-service.js";
import type { SMTPService } from "../services/smtp-service.js";
import type { SimpleLoginService } from "../services/simplelogin-service.js";
import type { AnalyticsService } from "../services/analytics-service.js";
import type { SchedulerService } from "../services/scheduler.js";
import type { ReminderService } from "../services/reminder-service.js";
import type { PassService } from "../services/pass-service.js";
import type { FtsIndexService, FtsRecord } from "../services/fts-service.js";
import type { ProtonMailConfig, EmailMessage } from "../types/index.js";

export interface ToolDef {
  name: string;
  title?: string;
  description: string;
  annotations?: Record<string, unknown>;
  inputSchema: unknown;
  outputSchema?: unknown;
}

export type ToolResult = {
  content: Array<{ type: "text"; text: string }>;
  isError?: boolean;
  structuredContent?: Record<string, unknown>;
};

/** Mutable shared state that several handlers mutate in-place. */
export interface ToolSharedState {
  analyticsCache: { inbox: EmailMessage[]; sent: EmailMessage[]; fetchedAt: number } | null;
  analyticsCacheInflight: Promise<{ inbox: EmailMessage[]; sent: EmailMessage[] }> | null;
  bridgeAutoStarted: boolean;
  smtpStatus: { connected: boolean; lastCheck: Date; error?: string };
}

/** Per-call context injected by the dispatcher into every handler. */
export interface ToolCallContext {
  /** Arguments forwarded from the MCP client. */
  args: Record<string, unknown>;

  /** Per-call resolved services (honor `account_id` routing). */
  imapService: SimpleIMAPService;
  smtpService: SMTPService;

  /** Module-level singletons — never re-bound per call. */
  simpleloginService: SimpleLoginService;
  analyticsService: AnalyticsService;
  schedulerService: SchedulerService;
  reminderService: ReminderService;
  /** Lazy-initialized; handlers must call getPassService()/check ctx.passService for null. */
  passService: PassService | null;
  /** Lazy accessor for the FTS index — may throw FtsUnavailableError. */
  getFts: () => FtsIndexService;

  /**
   * Resolve the current caller's folder allowlist from their grant.
   *  - Returns `undefined` for trusted/stdio callers, missing grants, or
   *    grants without a folder restriction → tools should treat as
   *    "no restriction" and preserve existing behavior.
   *  - Returns a non-empty `string[]` when the grant restricts the caller
   *    to a specific set of folders → folder-bearing tools (e.g.,
   *    `fts_search`) must scope their results to that set.
   * The grant gate in `index.ts` already blocks per-call `folder` args
   * outside the allowlist; this accessor is for tools whose response
   * content (not args) is folder-bearing.
   */
  getCallerAllowedFolders: () => string[] | undefined;

  /** Live server config (SMTP host/port/username for reply-to, etc.). */
  config: ProtonMailConfig;

  /** Response limits — hot-reloaded from config, provided by the gate. */
  limits: {
    maxResponseBytes: number;
    maxEmailBodyChars: number;
    maxEmailListResults: number;
    maxAttachmentBytes: number;
    warnOnLargeResponse: boolean;
  };

  /** Response helpers — enforce size limits + shape. */
  ok: (structured: Record<string, unknown>, text?: string) => ToolResult;
  actionOk: (messageId?: string) => ToolResult;
  bulkOk: (result: { success: number; failed: number; errors: string[] }) => ToolResult;
  sendProgress: (progress: number, total: number, message: string) => Promise<void>;

  /** Shared cursors (email-list pagination). */
  encodeCursor: (c: { folder: string; offset: number; limit: number }) => string;
  decodeCursor: (token: string) => { folder: string; offset: number; limit: number } | null;

  /** Analytics cache warmer — shared across handlers that need it. */
  getAnalyticsEmails: () => Promise<{ inbox: EmailMessage[]; sent: EmailMessage[] }>;
  recordFromEmail: (m: EmailMessage) => FtsRecord;

  /** Bridge lifecycle helpers (start_bridge / shutdown_server / restart_server). */
  launchProtonBridge: () => Promise<void>;
  killProtonBridge: () => Promise<void>;
  isBridgeReachable: (host: string, port: number, timeoutMs?: number) => Promise<boolean>;
  gracefulShutdown: (signal: string) => Promise<void>;

  /** Diagnostic helper — shared across error paths. */
  safeErrorMessage: (error: unknown) => string;

  /** Shared constants + mutable state. */
  MAX_BULK_IDS: number;
  MAX_BODY_LENGTH: number;
  MAX_SUBJECT_LENGTH: number;
  state: ToolSharedState;
}

export type ToolHandler = (ctx: ToolCallContext) => Promise<ToolResult>;

export interface ToolModule {
  /** Tool definitions exposed through ListTools. */
  defs: ToolDef[];
  /** Handlers keyed by tool name — dispatcher looks up by request.params.name. */
  handlers: Record<string, ToolHandler>;
}
