/**
 * Shared ACP `Client` builder — the callback closures every ACP connection needs
 * (worktree-scoped FS bridge + permission/elicitation routing + session updates).
 *
 * Extracted (v2.7 audit reshape) from the byte-identical `buildClient` closures in
 * `acp-dispatch.ts` (one-shot) and `backends/warm-acp.ts` (warm). The two differed
 * only in WHERE the per-turn context comes from (a fixed dispatch vs. the warm
 * backend's `activeTurn`) and a trivially-equivalent permission gate — both are now
 * supplied via the `resolveTurn` callback, so the FS/permission/elicitation wiring
 * lives once. Behavior is preserved exactly:
 *   - `sessionUpdate` drops when `resolveTurn()` returns null (between turns).
 *   - permission/elicitation route to the UI only when BOTH a taskId AND sessionId
 *     are present (warm always has a sessionId, so this matches its prior
 *     `turn?.taskId` gate); otherwise the same auto-select-first / decline fallback.
 */
import type {
  Client,
  SessionNotification,
  RequestPermissionRequest,
  RequestPermissionResponse,
  ReadTextFileRequest,
  ReadTextFileResponse,
  WriteTextFileRequest,
  WriteTextFileResponse,
  CreateTerminalRequest,
  CreateTerminalResponse,
  CreateElicitationRequest,
  CreateElicitationResponse,
} from '@agentclientprotocol/sdk';
import { readWorktreeTextFile, writeWorktreeTextFile } from './acp-client-fs.js';
import { waitForPermissionResponse, waitForElicitationResponse } from './permission-waiter.js';

/** The per-turn context an ACP `Client` closure needs, resolved lazily per call. */
export interface AcpTurnContext {
  /** Per-turn task id, for routing permission/elicitation prompts back to the UI. */
  taskId: string | undefined;
  /** BooCode session id (for permission-waiter's broker frames). */
  sessionId: string | undefined;
  /** Per-turn mode id (autonomous-mode gate in permission-waiter). */
  modeId: string | undefined;
  /** The agent name (for permission-waiter routing). */
  agent: string;
  /** Forward a session/update notification to the turn's event sink. */
  onSessionUpdate: (params: SessionNotification) => void | Promise<void>;
}

/**
 * Build the ACP `Client` callbacks once per connection. `resolveTurn` is called at
 * the moment each callback fires and returns the live turn context (or null when no
 * turn is active — `sessionUpdate` then drops, matching the warm backend's
 * between-turns behavior). The FS bridge is scoped to `worktreePath`.
 */
export function buildAcpClient(worktreePath: string, resolveTurn: () => AcpTurnContext | null): Client {
  return {
    sessionUpdate: async (params: SessionNotification): Promise<void> => {
      const turn = resolveTurn();
      if (!turn) return; // between turns — drop (no orphan settles a future turn)
      await turn.onSessionUpdate(params);
    },
    requestPermission: async (params: RequestPermissionRequest): Promise<RequestPermissionResponse> => {
      const turn = resolveTurn();
      if (turn && turn.taskId && turn.sessionId) {
        return waitForPermissionResponse(turn.taskId, turn.sessionId, turn.agent, turn.modeId, params);
      }
      const firstOption = params.options[0];
      if (firstOption) return { outcome: { outcome: 'selected', optionId: firstOption.optionId } };
      return { outcome: { outcome: 'cancelled' } };
    },
    readTextFile: async (params: ReadTextFileRequest): Promise<ReadTextFileResponse> => {
      const content = await readWorktreeTextFile(worktreePath, params.path, params.line, params.limit);
      return { content };
    },
    writeTextFile: async (params: WriteTextFileRequest): Promise<WriteTextFileResponse> => {
      await writeWorktreeTextFile(worktreePath, params.path, params.content);
      return {};
    },
    createTerminal: async (_params: CreateTerminalRequest): Promise<CreateTerminalResponse> => {
      return { terminalId: 'noop' };
    },
    unstable_createElicitation: async (params: CreateElicitationRequest): Promise<CreateElicitationResponse> => {
      const turn = resolveTurn();
      if (turn && turn.taskId && turn.sessionId) {
        return waitForElicitationResponse(turn.taskId, turn.sessionId, turn.agent, turn.modeId, params);
      }
      return { action: 'decline' };
    },
  };
}
