// ABOUTME: Shared types and constants for the agent server, extracted from server.ts
// This module contains type definitions and configuration constants used across
// the server implementation, including session updates, job state, and server configuration.

import type { ChildProcess } from 'node:child_process';
import type { z } from 'zod';
import { SessionUpdateNotificationSchema } from '@lace/ent-protocol';
import type { JsonRpcId, JsonRpcPeer } from '@lace/ent-protocol';
import type { LoadedSession } from './storage/session-store';
import type { PendingPermissionRecord } from './storage/permissions-from-events';
import { ProviderCatalogManager } from './providers/catalog/manager';
import { ProviderInstanceManager } from './providers/instance/manager';
import { MCPServerManager } from './mcp/server-manager';
import type { JobManager } from './jobs/job-manager';
import type { ToolExecutor } from './tools/executor';
import type { Tool } from './tools/tool';
import type { SkillRegistry } from './skills';
import type { PersonaRegistry } from './config/persona-registry';
import type { EnvironmentRegistry } from './config/environment-registry';
import type { ContainerManager } from './containers/container-manager';
import type { ReminderScheduler } from './reminders';
import type { RuntimeExecutionBinding } from './tools/runtime/types';
import type { RuntimeSecretResolver } from './tools/runtime/secrets';
import type { WorkspaceReaper } from './jobs/workspace-reaper';
import type { CachedProjection } from './message-building/incremental-projection';

/**
 * Per-build allowlist of tool names. `undefined` means "no scope filter" (all tools available).
 * An empty array means "allow nothing".
 */
export type AgentToolScope = readonly string[] | undefined;

/**
 * Factory function type for creating tool executors.
 * Used by RPC handlers to create executors with the appropriate mode and dependencies.
 */
export type CreateToolExecutorFn = (
  mode: 'plan' | 'execute',
  mcpServerManager?: MCPServerManager,
  jobManager?: JobManager,
  skillRegistry?: SkillRegistry,
  toolScope?: AgentToolScope,
  personaRegistry?: PersonaRegistry,
  activePersona?: string,
  environmentRegistry?: EnvironmentRegistry
) => Promise<{
  executor: ToolExecutor;
  toolsForProvider: Tool[];
}>;

// Configuration Constants
export const SUPPORTED_PROVIDER_TYPES = new Set([
  'anthropic',
  'openai',
  'gemini',
  'lmstudio',
  'ollama',
]);
export const JOB_LOG_DIR = 'jobs';
export const MAX_CONCURRENT_JOBS = 10;
export const MAX_JOB_OUTPUT_BYTES = 10 * 1024 * 1024; // 10 MB
export const DEFAULT_PROGRESS_INTERVAL_MS = 300000; // 5 minutes

// Type Utilities
type DistributiveOmit<T, K extends PropertyKey> = T extends any ? Omit<T, K> : never;

// Session Update Types
export type SessionUpdateParams = z.infer<typeof SessionUpdateNotificationSchema>['params'];
export type SessionUpdate = DistributiveOmit<SessionUpdateParams, 'sessionId' | 'streamSeq'>;
export type JobInnerUpdate = Extract<SessionUpdateParams, { type: 'job_update' }>['update'];

// Job Types
export type JobType = 'bash' | 'delegate';
export type JobStatus = 'running' | 'completed' | 'failed' | 'cancelled';
export type JobNotificationType = 'completed' | 'failed' | 'cancelled' | 'progress';

export type JobState = {
  jobId: string;
  parentJobId?: string;
  type: JobType;
  status: JobStatus;
  description?: string;
  command?: string;
  subagentContent?: unknown[];
  startedAt: string;
  originTurnId?: string;
  originTurnSeq?: number;
  exitCode?: number;
  outputPath: string;
  proc?: ChildProcess;
  permissionAbortController?: AbortController;
  childPeer?: JsonRpcPeer;
  subagentSessionId?: string;
  // True when the host pre-allocated subagentSessionId before session/new.
  // Distinguishes from the resume case, where subagentSessionId is set because a
  // prior job already ran with that session.
  subagentSessionPreallocated?: boolean;
  // Host scratch-directory path reserved for this invocation.
  scratchDirHostPath?: string;
  // Container-sharing mode for this delegate job.
  containerSharing?: 'per_invocation' | 'persistent';
  // Per-invocation container spec name. Computed once at delegate time and
  // tracked on the WorkspaceReaper entry so teardown can route through the
  // shim's releasePerInvocation.
  containerSpecName?: string;
  childTransportClose?: () => void;
  finished: boolean;
  completion: Promise<void>;
  resolveCompletion: () => void;
  // Progress notification fields
  progressIntervalMs?: number;
  lastProgressAt?: number;
  lastProgressBytes?: number;
  progressTimer?: ReturnType<typeof setInterval>;
  // Subagent provider/model configuration
  connectionId?: string;
  modelId?: string;
  runtimeBinding?: RuntimeExecutionBinding;
  executionEnv?: Record<string, string>;
  // Persona bundle applied to subagent session (delegate jobs only)
  persona?: string;
};

// Server State Type
export type AgentServerState = {
  initialized: boolean;
  activeSession: LoadedSession | null;
  config: {
    executionMode: 'plan' | 'execute';
    approvalMode:
      | 'ask'
      | 'approveReads'
      | 'approveEdits'
      | 'approve'
      | 'deny'
      | 'dangerouslySkipPermissions';
    connectionId?: string;
    modelId?: string;
    maxBudgetUsd?: number;
    maxThinkingTokens?: number;
    environment?: Record<string, string>;
    /**
     * Host-only credential broker socket path for this connection. Stamped from
     * the initialize config and merged into every session's effective config so
     * the runner forwards it into the credential exec-tool envelope (Part B).
     */
    credentialBrokerSocket?: string;
  };
  activeTurn: null | {
    turnId: string;
    startedAt: string;
    status: 'running' | 'awaiting_permission';
    abortController: AbortController;
  };
  providerCatalog: ProviderCatalogManager;
  providerCatalogLoaded: boolean;
  providerInstances: ProviderInstanceManager;
  mcpServerManager: MCPServerManager;
  jobManager: JobManager; // Replaces: jobs, jobStreaming, jobNotificationQueue
  pendingPermissionRequests: Map<
    string,
    {
      requestId: string;
      rpcId: JsonRpcId;
      record: PendingPermissionRecord;
      result: Promise<unknown>;
    }
  >;
  sessionMutex: Promise<void>;
  // Key: `${sessionId}|${executionMode}`. Holds Promises so concurrent calls
  // for the same key share one in-flight build.
  toolExecutorCache: Map<string, Promise<{ executor: ToolExecutor; toolsForProvider: Tool[] }>>;
  // Per-session in-memory conversation projection, keyed by sessionId. Holds the
  // persisted-prefix FoldState + system prompt + files-read + last-turn-end +
  // next-seq-to-fold head so each turn folds only the events appended since the
  // cached .seq head (O(tail)) instead of re-parsing the whole log. Mirrors the
  // toolExecutorCache cross-turn pattern; a session switch uses a different key.
  projectionCache: Map<string, CachedProjection>;
  // Embedder-controlled persona resolver. Defaulted to the module singleton;
  // initialize handler replaces it when the client supplies userPersonasPaths.
  personaRegistry: PersonaRegistry;
  // Embedder-controlled environment resolver (container defs). Defaulted to the
  // module singleton; initialize handler replaces it when the client supplies
  // userEnvironmentsPaths.
  environmentRegistry: EnvironmentRegistry;
  // Embedder-controlled skill directories (ordered, earlier wins). When unset,
  // skill registry construction falls back to getSkillDirectories(workDir).
  // Set by the initialize handler when the client supplies skillDirs.
  skillDirs?: string[];
  // Host-only credential exec-tool directories supplied by the embedder. The
  // initialize handler registers each globally with trusted credential
  // provenance (owner: 'credential') and records the paths here. Defaults to []
  // when initialize omits the param.
  credentialToolsPaths?: string[];
  // Embedder-supplied named-mount registry. Consulted at persona-container
  // materialization to resolve persona `runtime.mounts` names into host paths,
  // container paths, and readonly flags. Always present; defaults to {} when
  // initialize omits the param.
  containerMounts: Record<string, MountRegistryEntry>;
  // Persona-container materialization (K-49e). null when the host platform
  // has no supported container runtime — persona-container delegate calls
  // then fail with a clear error. Tests inject fakes by replacing this field.
  containerManager: ContainerManager | null;
  /**
   * Per-process ReminderScheduler bound to the currently active session.
   * Created/replaced on session switch via ensureReminderSchedulerForActiveSession;
   * cleared on session/close. Mid-flight fires are owned by the scheduler.
   */
  reminderScheduler?: ReminderScheduler;
  // The JsonRpcPeer this agent process uses to talk to its parent client (the
  // CLI for a root agent, or the parent lace-agent for a subagent). Set by
  // main.ts after peer construction. Used by emitSubagentExitedIfNeeded to
  // ask the parent to record a `subagent-exited` notification under its
  // runExclusive mutex (avoids cross-process events.jsonl write races).
  peer: JsonRpcPeer | null;
  runtimeSecretResolver?: RuntimeSecretResolver;
  // Per-process map of per_invocation child workspaces in the shared results tree.
  // Constructed eagerly with a null runtime ref; bindRuntime() in boot() supplies
  // the container manager so dispose can route teardown through the shim.
  workspaceReaper: WorkspaceReaper;
};

// Single entry in the embedder-supplied containerMounts registry.
export type MountRegistryEntry = {
  hostPath: string;
  containerPath: string;
  readonly: boolean;
};
