import type { GatewayRequest, GatewayAttachment, OutboundMessage } from '../protocol.js';
import type { SendFn } from '../router.js';
import { makeEvent, makeError, ErrorCode } from '../protocol.js';
import type { SessionBridge } from '../session-bridge.js';
import type { LaneQueue } from '../lane-queue.js';
import type { LaneQueueConfig } from '../lane-queue.js';
import { LaneQueueRejectedError } from '../lane-queue.js';
import type { MetricsCollector } from '../metrics.js';
import type { Attachment } from '../../channels/types.js';
import type { SessionManager } from '../../session/manager.js';
import type { ModelTier } from '../../models/router.js';
import type { ModelRouter } from '../../models/router.js';
import {
  executeRuntimeBackendModeCommand,
  type CommandRegistry,
  type RuntimeBackendMode,
} from '../../commands/index.js';
import type { Config, ModelConfig, ModelProvider } from '../../config/index.js';
import { MODEL_PROVIDERS } from '../../config/index.js';
import { createClientFromConfig } from '../../daemon/models.js';
import { auditLogger } from '../../audit/index.js';
import type { HookEngine } from '../../hooks/index.js';
import { getElevationStatusMessage, setElevationFromInput } from '../../security/elevation.js';

export interface AgentHandlerDeps {
  sessionBridge: SessionBridge;
  laneQueue: LaneQueue;
  resolveQueuePolicy?: (ctx: {
    laneId: string;
    sessionId?: string;
    connectionId: string;
    channel: string;
  }) => Partial<LaneQueueConfig> | undefined;
  metrics?: MetricsCollector;
  sessionManager?: SessionManager;
  commandRegistry?: CommandRegistry;
  modelRouter?: ModelRouter;
  runtimeConfig?: Config;
  hookEngine?: HookEngine;
  getBackendMode?: () => RuntimeBackendMode;
  setBackendMode?: (mode: RuntimeBackendMode) => void;
}

function buildProviderConfigMap(config: Config): Partial<Record<ModelProvider, ModelConfig>> {
  const providerConfigs: Partial<Record<ModelProvider, ModelConfig>> = {};
  const modelConfigs: ModelConfig[] = [
    config.models.default,
    ...(config.models.fast ? [config.models.fast] : []),
    ...(config.models.complex ? [config.models.complex] : []),
    ...(config.models.local ? [config.models.local] : []),
    ...Object.values(config.models.local_providers ?? {}),
  ];

  for (const modelConfig of modelConfigs) {
    providerConfigs[modelConfig.provider] = modelConfig;
    if (modelConfig.fallback) {
      providerConfigs[modelConfig.fallback.provider] = modelConfig.fallback;
    }
  }

  return providerConfigs;
}

function listEnabledExternalBackends(config?: Config): string[] {
  if (!config) {
    return [];
  }

  const names: string[] = [];
  if (config.backends.claude_code.enabled) {
    names.push('claude_code');
  }
  if (config.backends.opencode.enabled) {
    names.push('opencode');
  }
  if (config.backends.codex.enabled) {
    names.push('codex');
  }
  if (config.backends.gemini.enabled) {
    names.push('gemini');
  }
  if (config.backends.pi_embedded.enabled) {
    names.push('pi_embedded');
  }
  return names;
}

export function createAgentHandlers(deps: AgentHandlerDeps) {
  return {
    'agent.send': async (request: GatewayRequest, send: SendFn): Promise<OutboundMessage | void> => {
      const params = request.params as { message?: string; connectionId?: string; attachments?: GatewayAttachment[]; metadata?: { isCommand?: boolean; command?: string; commandArgs?: string } } | undefined;
      if (!params) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'params are required');
      }

      const safeParams = params;
      const hasMessage = Boolean(safeParams.message && safeParams.message.trim());
      const hasAttachments = Boolean(safeParams.attachments && safeParams.attachments.length > 0);
      if (!hasMessage && !hasAttachments && !safeParams.metadata?.isCommand) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'message or attachments are required');
      }

      const connectionId = safeParams.connectionId as string;
      if (!connectionId) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'connectionId is required (set by server)');
      }

      const agent = deps.sessionBridge.getAgent(connectionId);
      if (!agent) {
        return makeError(request.id, ErrorCode.SessionNotFound, 'No agent for this connection');
      }

      // Queue by session ID so multiple connections sharing a session are serialised.
      // Falls back to connectionId if session lookup fails (shouldn't happen).
      const sessionId = deps.sessionBridge.getSessionId(connectionId);
      const laneId = sessionId ?? connectionId;
      const channel = sessionId?.split(':', 1)[0] ?? 'ws';
      const resolvedPolicy = deps.resolveQueuePolicy?.({ laneId, sessionId, connectionId, channel });
      const laneQueueWithProcessing = deps.laneQueue as LaneQueue & { isProcessing?: (lane: string) => boolean };
      const laneIsProcessing = typeof laneQueueWithProcessing.isProcessing === 'function'
        ? laneQueueWithProcessing.isProcessing(laneId)
        : false;
      const requestId = request.id.toString();
      let interruptedPreviousRun = false;

      // Interrupt mode should preempt active work when a newer request arrives.
      // LaneQueue itself only rejects queued entries, so we also request agent cancellation.
      if (resolvedPolicy?.mode === 'interrupt' && laneIsProcessing) {
        const cancelled = sessionId
          ? deps.sessionBridge.cancelSession(sessionId)
          : deps.sessionBridge.cancel(connectionId);
        interruptedPreviousRun = cancelled;
        auditLogger?.queuePreempt?.({
          session_id: sessionId ?? `ws:${connectionId}`,
          channel: 'ws',
          sender: connectionId,
          lane_id: laneId,
          request_id: requestId,
          mode: 'interrupt',
          cancelled_active_run: cancelled,
        });
      }

      // Enqueue the work — if the lane is idle it runs immediately,
      // otherwise it waits for earlier requests on the same session to finish.
      deps.metrics?.startRequest(requestId, { sessionId: laneId, channel: 'ws' });

      try {
        return await deps.laneQueue.enqueue(laneId, async () => {
          deps.sessionBridge.setBusy(connectionId, true);

          if (interruptedPreviousRun) {
            await send(makeEvent(request.id, 'content', {
              text: 'Interrupt mode: cancelled the previous in-flight run and processing your latest message.',
            }));
          }

          const commandInput = safeParams.metadata?.isCommand && typeof safeParams.metadata.command === 'string'
            ? `/${safeParams.metadata.command}${safeParams.metadata.commandArgs ? ` ${safeParams.metadata.commandArgs}` : ''}`
            : (safeParams.message ?? '');

          const parsedCommand = safeParams.metadata?.isCommand
            ? safeParams.metadata.command
            : commandInput.startsWith('/') ? commandInput.slice(1).split(/\s+/, 1)[0] : undefined;
          auditLogger?.userAction({
            session_id: sessionId ?? `ws:${connectionId}`,
            channel: 'ws',
            sender: connectionId,
            source: 'gateway',
            action_type: parsedCommand ? 'command' : 'message',
            content_length: commandInput.length,
            attachments_count: safeParams.attachments?.length ?? 0,
            command: parsedCommand,
          });

          if (commandInput && deps.commandRegistry?.isCommand(commandInput)) {
            const sessionId = deps.sessionBridge.getSessionId(connectionId);
            const commandResult = await deps.commandRegistry.execute(commandInput, {
              channel: 'ws',
              senderId: connectionId,
              sessionId: sessionId ?? `ws:${connectionId}`,
              rawInput: commandInput,
              services: {
                getStatus: () => `Gateway session active. Current model tier: ${agent.getModelTier()}`,
                getUsage: () => {
                  const usage = agent.getUsage();
                  const budget = agent.getContextBudget();
                  const lines = [
                    '**Token Usage**',
                    '',
                    `Primary: ${usage.primary.inputTokens.toLocaleString()} in / ${usage.primary.outputTokens.toLocaleString()} out (${usage.primary.calls} calls)`,
                  ];

                  const delegationEntries = Object.entries(usage.delegation);
                  if (delegationEntries.length > 0) {
                    lines.push('');
                    lines.push('Delegation:');
                    for (const [tier, stats] of delegationEntries) {
                      lines.push(`  ${tier}: ${stats.inputTokens.toLocaleString()} in / ${stats.outputTokens.toLocaleString()} out (${stats.calls} calls)`);
                    }
                  }

                  lines.push('');
                  lines.push(`**Total:** ${usage.total.inputTokens.toLocaleString()} in / ${usage.total.outputTokens.toLocaleString()} out (${usage.total.calls} calls)`);
                  lines.push('');
                  lines.push('**Context usage (estimated):** '
                  + `${budget.estimatedTokens.toLocaleString()}/${budget.contextWindow.toLocaleString()} `
                  + `(${budget.usagePct.toFixed(1)}%)`);

                  if (usage.total.estimatedCost > 0) {
                    lines.push(`**Estimated cost:** $${usage.total.estimatedCost.toFixed(4)}`);
                  }

                  return lines.join('\n');
                },
                getContext: () => {
                  const budget = agent.getContextBudget();
                  const lines = [
                    '**Context Usage**',
                    '',
                    `Estimated: ${budget.estimatedTokens.toLocaleString()} / ${budget.contextWindow.toLocaleString()} tokens`,
                    `Remaining: ${budget.remainingTokens.toLocaleString()} tokens`,
                    `Usage: ${budget.usagePct.toFixed(1)}%`,
                    `Compaction threshold: ${budget.thresholdPct}% (${budget.thresholdTokens.toLocaleString()} tokens)`,
                    `Should compact: ${budget.shouldCompact ? 'yes' : 'no'}`,
                  ];
                  return lines.join('\n');
                },
                getModel: () => `Current model tier: ${agent.getModelTier()}`,
                setModel: (input) => {
                  const raw = input.trim();
                  if (!raw) {
                    return 'Usage: /model <tier>  OR  /model <tier> <provider/model>  OR  /model <tier> reset';
                  }
                  const parts = raw.split(/\s+/);
                  const requestedTier = parts[0];
                  const validTiers: ModelTier[] = ['default', 'fast', 'complex', 'local'];
                  if (!validTiers.includes(requestedTier as ModelTier)) {
                    return `Invalid tier: ${requestedTier}. Available: ${validTiers.join(', ')}`;
                  }
                  const modelTier = requestedTier as ModelTier;

                  // /model <tier>
                  if (parts.length === 1) {
                    agent.setModelTier(modelTier);
                    if (sessionId && deps.sessionManager) {
                      deps.sessionManager.setSessionConfig('ws', sessionId, 'modelTier', modelTier);
                    }
                    return `Switched to model tier: ${modelTier}`;
                  }

                  if (!deps.modelRouter || !deps.runtimeConfig) {
                    return 'Provider/model switching is unavailable in this gateway runtime.';
                  }

                  const arg2 = parts[1];
                  // /model <tier> reset
                  if (arg2.toLowerCase() === 'reset') {
                    const configured: ModelConfig | undefined = modelTier === 'default'
                      ? deps.runtimeConfig.models.default
                      : modelTier === 'fast'
                        ? deps.runtimeConfig.models.fast
                        : modelTier === 'complex'
                          ? deps.runtimeConfig.models.complex
                          : modelTier === 'local'
                            ? deps.runtimeConfig.models.local
                            : undefined;
                    if (!configured) {
                      return `No configured model for tier: ${modelTier}`;
                    }

                    const client = createClientFromConfig(configured);
                    const label = `${configured.provider}/${configured.model}`;
                    deps.modelRouter.setClient(modelTier, client, label);
                    deps.modelRouter.setTierStrict(modelTier, false);
                    agent.setModelTier(modelTier);
                    if (sessionId && deps.sessionManager) {
                      deps.sessionManager.setSessionConfig('ws', sessionId, 'modelTier', modelTier);
                    }
                    return `Reset ${modelTier} to: ${label}`;
                  }

                  // /model <tier> <provider/model>
                  const providerModel = arg2;
                  if (!providerModel.includes('/')) {
                    return 'Invalid format. Use: /model <tier> <provider/model> (e.g. /model default github/gpt-5-mini)';
                  }

                  const slashIdx = providerModel.indexOf('/');
                  const provider = providerModel.slice(0, slashIdx);
                  const model = providerModel.slice(slashIdx + 1);

                  if (!MODEL_PROVIDERS.includes(provider as ModelProvider)) {
                    return `Unknown provider "${provider}". Known providers: ${MODEL_PROVIDERS.join(', ')}`;
                  }

                  const providerType = provider as ModelProvider;
                  const providerConfigs = buildProviderConfigMap(deps.runtimeConfig);
                  const template = providerConfigs[providerType];

                  try {
                    const client = createClientFromConfig(
                      template
                        ? { ...template, provider: providerType, model }
                        : { provider: providerType, model },
                    );

                    deps.modelRouter.setClient(modelTier, client, providerModel);
                    deps.modelRouter.setTierStrict(modelTier, true);
                    agent.setModelTier(modelTier);
                    if (sessionId && deps.sessionManager) {
                      deps.sessionManager.setSessionConfig('ws', sessionId, 'modelTier', modelTier);
                    }

                    const lines = [
                      `Set ${modelTier} to: ${providerModel}`,
                      `Fallbacks for ${modelTier} disabled (strict tier mode).`,
                    ];
                    if (parts.length > 2) {
                      lines.push(`Note: ignored extra args: ${parts.slice(2).join(' ')}`);
                    }
                    return lines.join('\n');
                  } catch (error) {
                    const message = error instanceof Error ? error.message : String(error);
                    return `Failed to switch ${modelTier} to ${providerModel}: ${message}`;
                  }
                },
                compact: async () => {
                  const result = await agent.compact();
                  if (result && result.compactedCount > 0) {
                    return `Compacted ${result.compactedCount} messages: ${result.tokensBefore} → ${result.tokensAfter} tokens`;
                  }
                  return 'Nothing to compact.';
                },
                reset: () => {
                  agent.reset();
                  if (sessionId && deps.sessionManager) {
                    deps.sessionManager.deleteSessionConfig('ws', sessionId, 'modelTier');
                  }
                  return 'Session reset.';
                },
                cancelRun: () => {
                  const cancelled = deps.sessionBridge.cancel(connectionId);
                  return cancelled
                    ? 'Cancellation requested. The active operation will stop at the next safe point.'
                    : 'No active operation to cancel.';
                },
                getApprovals: () => {
                  if (!deps.hookEngine) {
                    return 'Approval gates are not enabled in this runtime.';
                  }
                  if (!sessionId) {
                    return 'No pending approvals for this session.';
                  }
                  const pending = deps.hookEngine.getPendingConfirmations({ sessionId });
                  if (pending.length === 0) {
                    return 'No pending approvals for this session.';
                  }
                  const lines = ['Pending approvals:'];
                  for (const item of pending) {
                    const ageSec = Math.max(0, Math.round((Date.now() - item.createdAt.getTime()) / 1000));
                    lines.push(`- ${item.id} | ${item.tool} | ${ageSec}s old`);
                  }
                  lines.push('');
                  lines.push('Use `/approve <id>` or `/deny <id> <reason>` (id optional: latest is used).');
                  return lines.join('\n');
                },
                approvePending: (inputRaw: string) => {
                  if (!deps.hookEngine) {
                    return 'Approval gates are not enabled in this runtime.';
                  }
                  if (!sessionId) {
                    return 'Approve command is unavailable in this session.';
                  }
                  const pending = deps.hookEngine.getPendingConfirmations({ sessionId });
                  if (pending.length === 0) {
                    return 'No pending approvals for this session.';
                  }

                  const input = inputRaw.trim();
                  const selected = input
                    ? pending.find((item) => item.id === input)
                    : pending[pending.length - 1];

                  if (!selected) {
                    return `Approval id not found in this session: ${input}`;
                  }

                  const resolved = deps.hookEngine.resolveConfirmation(selected.id, { approved: true });
                  return resolved
                    ? `Approved: ${selected.tool} (${selected.id})`
                    : `Approval request is no longer pending: ${selected.id}`;
                },
                denyPending: (inputRaw: string) => {
                  if (!deps.hookEngine) {
                    return 'Approval gates are not enabled in this runtime.';
                  }
                  if (!sessionId) {
                    return 'Deny command is unavailable in this session.';
                  }
                  const pending = deps.hookEngine.getPendingConfirmations({ sessionId });
                  if (pending.length === 0) {
                    return 'No pending approvals for this session.';
                  }

                  const input = inputRaw.trim();
                  let targetId: string | undefined;
                  let reason = 'Denied by user';

                  if (input) {
                    const [first, ...rest] = input.split(/\s+/);
                    const matched = pending.find((item) => item.id === first);
                    if (matched) {
                      targetId = matched.id;
                      reason = rest.join(' ').trim() || reason;
                    } else {
                      targetId = pending[pending.length - 1].id;
                      reason = input;
                    }
                  } else {
                    targetId = pending[pending.length - 1].id;
                  }

                  const selected = pending.find((item) => item.id === targetId);
                  if (!selected) {
                    return `Approval request is no longer pending: ${targetId}`;
                  }

                  const resolved = deps.hookEngine.resolveConfirmation(selected.id, { approved: false, reason });
                  return resolved
                    ? `Denied: ${selected.tool} (${selected.id}) — ${reason}`
                    : `Approval request is no longer pending: ${selected.id}`;
                },

                getElevation: () => {
                  if (!sessionId || !deps.sessionManager) {
                    return 'Elevated mode: off';
                  }
                  return getElevationStatusMessage({
                    get: (key) => deps.sessionManager!.getSessionConfig('ws', sessionId, key),
                    set: (key, value) => deps.sessionManager!.setSessionConfig('ws', sessionId, key, value),
                    delete: (key) => deps.sessionManager!.deleteSessionConfig('ws', sessionId, key),
                  }, {
                    showExpiredSuffix: true,
                    auditContext: {
                      sessionId: `ws:${sessionId}`,
                      channel: 'ws',
                      sender: connectionId,
                    },
                  });
                },

                setElevation: (input: string) => {
                  if (!sessionId || !deps.sessionManager) {
                    return 'Elevate command is not available in this session.';
                  }
                  return setElevationFromInput({
                    get: (key) => deps.sessionManager!.getSessionConfig('ws', sessionId, key),
                    set: (key, value) => deps.sessionManager!.setSessionConfig('ws', sessionId, key, value),
                    delete: (key) => deps.sessionManager!.deleteSessionConfig('ws', sessionId, key),
                  }, input, {
                    auditContext: {
                      sessionId: `ws:${sessionId}`,
                      channel: 'ws',
                      sender: connectionId,
                    },
                  });
                },

                backendCommand: (inputRaw: string) => {
                  const availableExternalBackends = listEnabledExternalBackends(deps.runtimeConfig);
                  const mode = deps.getBackendMode?.() ?? 'config_default';
                  const configuredDefault = deps.runtimeConfig?.backends.default ?? 'native';
                  const getEffectiveDefaultBackend = (): string => {
                    if (mode === 'force_native') {
                      return 'native';
                    }
                    if (mode === 'force_pi_embedded') {
                      return availableExternalBackends.includes('pi_embedded') ? 'pi_embedded' : 'native';
                    }
                    if (configuredDefault === 'native') {
                      return 'native';
                    }
                    return availableExternalBackends.includes(configuredDefault) ? configuredDefault : 'native';
                  };

                  return executeRuntimeBackendModeCommand(inputRaw, {
                    getActiveTier: () => agent.getModelTier(),
                    getBackendMode: () => deps.getBackendMode?.() ?? 'config_default',
                    setBackendMode: deps.setBackendMode,
                    getConfiguredDefaultBackend: () => configuredDefault,
                    getEffectiveDefaultBackend,
                    getAvailableExternalBackends: () => availableExternalBackends,
                  });
                },

                getQueue: () => {
                  const mode = resolvedPolicy?.mode ?? 'collect';
                  const cap = resolvedPolicy?.cap ?? 50;
                  const overflow = resolvedPolicy?.overflow ?? 'drop_old';
                  const debounceMs = resolvedPolicy?.debounceMs ?? 0;
                  const summarizeOverflow = resolvedPolicy?.summarizeOverflow ?? true;
                  const source = deps.sessionManager && sessionId
                    ? deps.sessionManager.getSessionConfig('ws', sessionId, 'queue.mode') ? 'session override' : 'default/channel'
                    : 'default/channel';
                  return [
                    '**Queue policy**',
                    `mode: ${mode}`,
                    `cap: ${cap}`,
                    `overflow: ${overflow}`,
                    `debounce_ms: ${debounceMs}`,
                    `summarize_overflow: ${summarizeOverflow}`,
                    `source: ${source}`,
                  ].join('\n');
                },

                setQueue: (input: string) => {
                  if (!deps.sessionManager || !sessionId) {
                    return 'Queue command is not available in this session.';
                  }
                  const [rawKey, ...rest] = input.trim().split(/\s+/);
                  const value = rest.join(' ').trim();
                  if (!rawKey || !value) {
                    return 'Usage: /queue <mode|cap|overflow|debounce_ms|summarize_overflow> <value>';
                  }
                  const key = rawKey.toLowerCase();
                  if (key === 'mode') {
                    if (!['collect', 'followup', 'steer', 'steer_backlog', 'interrupt'].includes(value)) {
                      return 'Invalid mode. Use one of: collect, followup, steer, steer_backlog, interrupt';
                    }
                    deps.sessionManager.setSessionConfig('ws', sessionId, 'queue.mode', value);
                    return `Set queue.mode=${value} for this session`;
                  }
                  if (key === 'cap') {
                    const cap = Number.parseInt(value, 10);
                    if (!Number.isFinite(cap) || cap < 1 || cap > 1000) {
                      return 'Invalid cap. Use an integer between 1 and 1000';
                    }
                    deps.sessionManager.setSessionConfig('ws', sessionId, 'queue.cap', String(cap));
                    return `Set queue.cap=${cap} for this session`;
                  }
                  if (key === 'overflow') {
                    if (value !== 'drop_old' && value !== 'drop_new') {
                      return 'Invalid overflow. Use drop_old or drop_new';
                    }
                    deps.sessionManager.setSessionConfig('ws', sessionId, 'queue.overflow', value);
                    return `Set queue.overflow=${value} for this session`;
                  }
                  if (key === 'debounce_ms') {
                    const debounceMs = Number.parseInt(value, 10);
                    if (!Number.isFinite(debounceMs) || debounceMs < 0 || debounceMs > 60_000) {
                      return 'Invalid debounce_ms. Use an integer between 0 and 60000';
                    }
                    deps.sessionManager.setSessionConfig('ws', sessionId, 'queue.debounce_ms', String(debounceMs));
                    return `Set queue.debounce_ms=${debounceMs} for this session`;
                  }
                  if (key === 'summarize_overflow') {
                    const normalized = value.toLowerCase();
                    if (normalized !== 'true' && normalized !== 'false') {
                      return 'Invalid summarize_overflow. Use true or false';
                    }
                    deps.sessionManager.setSessionConfig('ws', sessionId, 'queue.summarize_overflow', normalized);
                    return `Set queue.summarize_overflow=${normalized} for this session`;
                  }
                  return 'Unknown queue key. Use one of: mode, cap, overflow, debounce_ms, summarize_overflow';
                },

                resetQueue: () => {
                  if (!deps.sessionManager || !sessionId) {
                    return 'Queue command is not available in this session.';
                  }
                  deps.sessionManager.deleteSessionConfig('ws', sessionId, 'queue.mode');
                  deps.sessionManager.deleteSessionConfig('ws', sessionId, 'queue.cap');
                  deps.sessionManager.deleteSessionConfig('ws', sessionId, 'queue.overflow');
                  deps.sessionManager.deleteSessionConfig('ws', sessionId, 'queue.debounce_ms');
                  deps.sessionManager.deleteSessionConfig('ws', sessionId, 'queue.summarize_overflow');
                  return 'Reset session queue overrides.';
                },
              },
            });

            if (commandResult.handled) {
              send(makeEvent(request.id, 'done', { content: commandResult.text }));
              return;
            }
          }

          // Set up tool use callback to emit streaming events
          deps.sessionBridge.setOnToolUse(connectionId, (event) => {
            if (event.type === 'start') {
              send(makeEvent(request.id, 'tool_start', { tool: event.tool, args: event.args }));
            } else if (event.type === 'end') {
              send(makeEvent(request.id, 'tool_end', {
                tool: event.tool,
                result: event.result ? {
                  success: event.result.success,
                  output: event.result.output,
                  error: event.result.error,
                } : undefined,
              }));
              // Record tool failures as error events
              if (event.result && !event.result.success) {
                deps.metrics?.incrementErrors();
                deps.metrics?.recordEvent({
                  timestamp: Date.now(),
                  level: 'error',
                  source: 'tool',
                  message: `Tool '${event.tool}' failed: ${event.result.error ?? 'unknown error'}`,
                  context: { sessionId: laneId, tool: event.tool },
                });
              }
            }
          });

          try {
          // Convert gateway attachments to channel attachments
            const attachments: Attachment[] | undefined = safeParams.attachments?.map(a => ({
              mimeType: a.mimeType,
              data: a.data,
              url: a.url,
              filename: a.filename,
            }));

            const response = await agent.process(safeParams.message ?? '', attachments);
            deps.metrics?.incrementMessages();
            const contextAlert = agent.consumeContextAlert();
            if (contextAlert) {
              send(makeEvent(request.id, 'context_warning', contextAlert));
              deps.metrics?.recordEvent({
                timestamp: Date.now(),
                level: 'warn',
                source: 'context',
                message: contextAlert.message,
                context: { sessionId: laneId, level: contextAlert.level },
              });
            }
            send(makeEvent(request.id, 'done', { content: response }));
          } catch (err) {
            const message = err instanceof Error ? err.message : 'Unknown error';
            deps.metrics?.incrementErrors();
            deps.metrics?.recordEvent({
              timestamp: Date.now(),
              level: 'error',
              source: 'agent.send',
              message,
              context: { sessionId: laneId },
            });
            send(makeEvent(request.id, 'error', { code: ErrorCode.InternalError, message }));
          } finally {
            deps.sessionBridge.setBusy(connectionId, false);
            deps.sessionBridge.setOnToolUse(connectionId, undefined);
            deps.metrics?.endRequest(requestId);
          }
        }, resolvedPolicy);
      } catch (err) {
        if (err instanceof LaneQueueRejectedError) {
          send(makeEvent(request.id, 'error', {
            code: ErrorCode.AgentBusy,
            message: err.message,
            queue: err.details,
          }));
          return;
        }
        throw err;
      }
    },

    'agent.cancel': async (request: GatewayRequest): Promise<OutboundMessage> => {
      const params = request.params as { connectionId?: string } | undefined;
      const connectionId = params?.connectionId as string;

      if (!connectionId) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'connectionId is required');
      }

      const sessionId = deps.sessionBridge.getSessionId(connectionId);
      const laneId = sessionId ?? connectionId;

      // Clear any queued (not-yet-started) work first.
      deps.laneQueue.cancel(laneId);

      const cancelled = deps.sessionBridge.cancel(connectionId);
      return {
        id: request.id,
        result: {
          cancelled,
          message: cancelled
            ? 'Cancellation requested. The active operation will stop at the next safe point.'
            : 'No active operation to cancel.',
        },
      };
    },
  };
}
