/**
 * Core interfaces for dependency injection
 * All implementations should be injected, not instantiated directly
 */

import { ChildProcess } from 'child_process';
import { SpawnOptions } from './agents.js';
import {
  ActivityEntry,
  Loop,
  LoopCreateRequest,
  LoopId,
  LoopIteration,
  LoopStatus,
  Orchestration,
  OrchestratorChild,
  OrchestratorCreateRequest,
  OrchestratorId,
  OrchestratorStatus,
  Pipeline,
  PipelineCreateRequest,
  PipelineId,
  PipelineResult,
  PipelineStatus,
  ResumeTaskRequest,
  Schedule,
  ScheduleCreateRequest,
  ScheduledLoopCreateRequest,
  ScheduledPipelineCreateRequest,
  ScheduleId,
  ScheduleStatus,
  SystemResources,
  Task,
  TaskCheckpoint,
  TaskId,
  TaskOutput,
  TaskRequest,
  TaskUsage,
  Worker,
  WorkerId,
  WorkerRegistration,
} from './domain.js';
import { AutobeatEvent, BaseEvent, EventHandler } from './events/events.js';
import { Result } from './result.js';

/**
 * Task queue management
 */
export interface TaskQueue {
  enqueue(task: Task): Result<void>;
  dequeue(): Result<Task | null>;
  peek(): Result<Task | null>;
  remove(taskId: TaskId): Result<boolean>;
  getAll(): Result<readonly Task[]>;
  contains(taskId: TaskId): boolean;
  size(): number;
  clear(): Result<void>;
  isEmpty(): boolean;
}

/**
 * Process spawning abstraction
 *
 * ARCHITECTURE: Widened in v1.3.0 to accept a SpawnOptions bag instead of
 * 4 positional params. This preserves orchestratorId and jsonSchema through
 * the ProcessSpawnerAdapter shim — both fields were silently dropped before
 * the refactor. See #139 review (batch-D-process-spawner).
 */
export interface ProcessSpawner {
  spawn(options: SpawnOptions): Result<{ process: ChildProcess; pid: number }>;
  kill(pid: number): Result<void>;
}

/**
 * System resource monitoring
 */
export interface ResourceMonitor {
  getResources(): Promise<Result<SystemResources>>;
  canSpawnWorker(): Promise<Result<boolean>>;
  getThresholds(): {
    readonly maxCpuPercent: number;
    readonly minMemoryBytes: number;
  };
  incrementWorkerCount(): void;
  decrementWorkerCount(): void;
  /**
   * Record a spawn event for settling worker tracking
   * Call immediately after spawning to track workers during their settling period
   * (before they appear in system metrics like load average)
   */
  recordSpawn(): void;
}

/**
 * Worker pool management
 */
export interface WorkerPool {
  spawn(task: Task): Promise<Result<Worker>>;
  kill(workerId: WorkerId): Promise<Result<void>>;
  killAll(): Promise<Result<void>>;
  getWorker(workerId: WorkerId): Result<Worker | null>;
  getWorkers(): Result<readonly Worker[]>;
  getWorkerCount(): number;
  getWorkerForTask(taskId: TaskId): Result<Worker | null>;
}

/**
 * Task output management
 */
export interface OutputCapture {
  capture(taskId: TaskId, type: 'stdout' | 'stderr', data: string): Result<void>;
  getOutput(taskId: TaskId, tail?: number): Result<TaskOutput>;
  clear(taskId: TaskId): Result<void>;
}

/**
 * Task persistence (for Phase 2)
 */
export interface TaskRepository {
  save(task: Task): Promise<Result<void>>;
  update(taskId: TaskId, update: Partial<Task>): Promise<Result<void>>;
  findById(taskId: TaskId): Promise<Result<Task | null>>;
  /**
   * Find tasks with optional pagination
   *
   * All implementations MUST use DEFAULT_LIMIT = 100 when limit is not specified.
   * This ensures consistent behavior across implementations.
   *
   * @param limit Maximum results to return (default: 100, max recommended: 1000)
   * @param offset Skip first N results (default: 0)
   * @returns Paginated task list ordered by created_at DESC
   */
  findAll(limit?: number, offset?: number): Promise<Result<readonly Task[]>>;
  /**
   * Find all tasks without pagination limit
   * ARCHITECTURE: Use only when you genuinely need ALL tasks (e.g., graph initialization)
   * For user-facing queries, use findAll() with pagination instead
   * @returns All tasks ordered by created_at DESC
   */
  findAllUnbounded(): Promise<Result<readonly Task[]>>;
  /**
   * Count total tasks in repository
   * @returns Total task count (useful for pagination UI)
   */
  count(): Promise<Result<number>>;
  /**
   * Find tasks by status (returns all matching tasks - NOT paginated)
   * NOTE: Unlike findAll(), this method has no pagination limit.
   * For large datasets, consider using findAll() with application-level filtering.
   * @param status Task status to filter by
   * @returns All tasks matching the status
   */
  findByStatus(status: string): Promise<Result<readonly Task[]>>;
  delete(taskId: TaskId): Promise<Result<void>>;
  cleanupOldTasks(olderThanMs: number): Promise<Result<number>>;
  /**
   * Count tasks grouped by status
   * ARCHITECTURE: Used by dashboard for panel summary display
   * @returns Map of status → count for all tasks in the repository
   */
  countByStatus(): Promise<Result<Record<string, number>>>;

  /**
   * Find tasks attributed to an orchestration (v1.3.0)
   * @param orchId - Orchestration ID to filter by
   * @param opts.statuses - Optional status filter (e.g., ['queued', 'running'])
   */
  findByOrchestratorId(
    orchId: OrchestratorId,
    opts?: { statuses?: readonly string[] },
  ): Promise<Result<readonly Task[]>>;

  /**
   * Throughput statistics over a time window (v1.3.0)
   * @param windowMs - Milliseconds to look back from now
   */
  getThroughputStats(windowMs: number): Promise<
    Result<{
      tasksPerHour: number;
      loopsPerHour: number;
      successRate: number;
      avgDurationMs: number;
    }>
  >;

  /**
   * Find tasks updated since a given timestamp (v1.3.0)
   * @param sinceMs - Epoch milliseconds lower bound
   * @param limit - Maximum results to return
   */
  findUpdatedSince(sinceMs: number, limit: number): Promise<Result<readonly Task[]>>;
}

/**
 * Task dependency tracking and resolution
 * ARCHITECTURE: Pure Result pattern, no exceptions
 * Pattern: Repository pattern for dependency DAG management
 * Rationale: Enables cycle detection, dependency queries, state tracking
 */
export interface TaskDependency {
  readonly id: number;
  readonly taskId: TaskId;
  readonly dependsOnTaskId: TaskId;
  readonly createdAt: number;
  readonly resolvedAt: number | null;
  readonly resolution: 'pending' | 'completed' | 'failed' | 'cancelled';
}

export interface DependencyRepository {
  /**
   * Add a dependency relationship between tasks
   * @returns Error if dependency would create a cycle
   */
  addDependency(taskId: TaskId, dependsOnTaskId: TaskId): Promise<Result<TaskDependency>>;

  /**
   * Add multiple dependencies atomically in a single transaction
   * All dependencies succeed or all fail together
   * @returns Error if any dependency would create a cycle or if validation fails
   */
  addDependencies(taskId: TaskId, dependsOn: readonly TaskId[]): Promise<Result<readonly TaskDependency[]>>;

  /**
   * Get all tasks that the given task depends on (blocking tasks)
   */
  getDependencies(taskId: TaskId): Promise<Result<readonly TaskDependency[]>>;

  /**
   * Get all tasks that depend on the given task (blocked tasks)
   */
  getDependents(taskId: TaskId): Promise<Result<readonly TaskDependency[]>>;

  /**
   * Mark a dependency as resolved with given resolution state
   * @returns Result<void> - Success or error. Use resolveDependenciesBatch() if you need the count of resolved dependencies.
   */
  resolveDependency(
    taskId: TaskId,
    dependsOnTaskId: TaskId,
    resolution: 'completed' | 'failed' | 'cancelled',
  ): Promise<Result<void>>;

  /**
   * Batch resolve all dependencies that depend on a completed task
   * PERFORMANCE: Single UPDATE query instead of N+1 queries (7-10× faster)
   * @param dependsOnTaskId The task that completed/failed/cancelled
   * @param resolution The resolution state to apply to all dependents
   * @returns Result<number> - Count of dependencies resolved (useful for logging and metrics)
   */
  resolveDependenciesBatch(
    dependsOnTaskId: TaskId,
    resolution: 'completed' | 'failed' | 'cancelled',
  ): Promise<Result<number>>;

  /**
   * Get all unresolved dependencies for a task
   */
  getUnresolvedDependencies(taskId: TaskId): Promise<Result<readonly TaskDependency[]>>;

  /**
   * Check if a task has any unresolved dependencies (is blocked)
   */
  isBlocked(taskId: TaskId): Promise<Result<boolean>>;

  /**
   * Get dependencies with optional pagination
   *
   * All implementations MUST use DEFAULT_LIMIT = 100 when limit is not specified.
   * This ensures consistent behavior across implementations.
   *
   * @param limit Maximum results to return (default: 100, max recommended: 1000)
   * @param offset Skip first N results (default: 0)
   * @returns Paginated dependencies ordered by created_at DESC
   */
  findAll(limit?: number, offset?: number): Promise<Result<readonly TaskDependency[]>>;

  /**
   * Get all dependencies without pagination limit
   * ARCHITECTURE: Use only for graph initialization (DependencyHandler.create())
   * For user queries, use findAll() with pagination instead
   * @returns All dependencies ordered by created_at DESC
   */
  findAllUnbounded(): Promise<Result<readonly TaskDependency[]>>;

  /**
   * Count total dependencies in repository
   * @returns Total dependency count
   */
  count(): Promise<Result<number>>;

  /**
   * Remove all dependencies for a task (on task deletion)
   */
  deleteDependencies(taskId: TaskId): Promise<Result<void>>;
}

/**
 * Schedule execution history record
 * ARCHITECTURE: Tracks individual executions of a schedule for audit/debugging
 * Pattern: Immutable record of each trigger attempt and outcome
 */
export interface ScheduleExecution {
  readonly id: number;
  readonly scheduleId: ScheduleId;
  readonly taskId?: TaskId; // ID of created task (if execution succeeded in creating one)
  readonly scheduledFor: number; // Epoch ms - when execution was scheduled to run
  readonly executedAt?: number; // Epoch ms - when execution actually started
  readonly status: 'pending' | 'triggered' | 'completed' | 'failed' | 'missed' | 'skipped';
  readonly errorMessage?: string; // Error details if status is 'failed' or 'missed'
  readonly pipelineTaskIds?: readonly TaskId[]; // All task IDs from a pipeline trigger (v0.6.0)
  readonly loopId?: LoopId; // Loop ID created by this execution (v0.8.0)
  readonly createdAt: number;
}

/**
 * Schedule persistence and query interface
 * ARCHITECTURE: Pure Result pattern, no exceptions
 * Pattern: Repository pattern for schedule management
 * Rationale: Enables schedule CRUD, status tracking, due schedule queries
 */
export interface ScheduleRepository {
  /**
   * Save a new schedule
   */
  save(schedule: Schedule): Promise<Result<void>>;

  /**
   * Update an existing schedule
   */
  update(id: ScheduleId, update: Partial<Schedule>): Promise<Result<void>>;

  /**
   * Find schedule by ID
   */
  findById(id: ScheduleId): Promise<Result<Schedule | null>>;

  /**
   * Find schedules with optional pagination
   *
   * All implementations MUST use DEFAULT_LIMIT = 100 when limit is not specified.
   * This ensures consistent behavior across implementations.
   *
   * @param limit Maximum results to return (default: 100, max recommended: 1000)
   * @param offset Skip first N results (default: 0)
   * @returns Paginated schedule list ordered by created_at DESC
   */
  findAll(limit?: number, offset?: number): Promise<Result<readonly Schedule[]>>;

  /**
   * Find schedules by status with optional pagination
   *
   * All implementations MUST use DEFAULT_LIMIT = 100 when limit is not specified.
   * This ensures consistent behavior across implementations.
   *
   * @param status Schedule status to filter by
   * @param limit Maximum results to return (default: 100, max recommended: 1000)
   * @param offset Skip first N results (default: 0)
   * @returns Paginated schedule list matching status, ordered by created_at DESC
   */
  findByStatus(status: ScheduleStatus, limit?: number, offset?: number): Promise<Result<readonly Schedule[]>>;

  /**
   * Find schedules that are due to execute (nextRunAt <= beforeTime)
   * ARCHITECTURE: Critical for scheduler tick - finds schedules ready to trigger
   * @param beforeTime Epoch ms - find schedules with nextRunAt before this time
   * @returns Schedules due for execution ordered by nextRunAt ASC
   */
  findDue(beforeTime: number): Promise<Result<readonly Schedule[]>>;

  /**
   * Delete a schedule
   */
  delete(id: ScheduleId): Promise<Result<void>>;

  /**
   * Count total schedules
   */
  count(): Promise<Result<number>>;

  /**
   * Count schedules grouped by status
   * ARCHITECTURE: Used by dashboard for panel summary display
   * @returns Map of status → count for all schedules in the repository
   */
  countByStatus(): Promise<Result<Record<string, number>>>;

  /**
   * Find schedules updated since a given timestamp (v1.3.0)
   * @param sinceMs - Epoch milliseconds lower bound
   * @param limit - Maximum results to return
   */
  findUpdatedSince(sinceMs: number, limit: number): Promise<Result<readonly Schedule[]>>;

  /**
   * Record a schedule execution attempt
   * @param execution Execution record without ID (ID auto-generated)
   * @returns Created execution record with ID
   */
  recordExecution(execution: Omit<ScheduleExecution, 'id'>): Promise<Result<ScheduleExecution>>;

  /**
   * Get execution history for a schedule
   * @param scheduleId Schedule to get history for
   * @param limit Maximum records to return (default: 100)
   * @returns Execution history ordered by scheduledFor DESC
   */
  getExecutionHistory(scheduleId: ScheduleId, limit?: number): Promise<Result<readonly ScheduleExecution[]>>;
}

/**
 * Structured logging
 */
export interface Logger {
  debug(message: string, context?: Record<string, unknown>): void;
  info(message: string, context?: Record<string, unknown>): void;
  warn(message: string, context?: Record<string, unknown>): void;
  error(message: string, error?: Error, context?: Record<string, unknown>): void;
  child(context: Record<string, unknown>): Logger;
}

/**
 * Event emitter for task events
 */
export interface TaskEventEmitter {
  on(event: 'task:queued', listener: (task: Task) => void): void;
  on(event: 'task:started', listener: (task: Task) => void): void;
  on(event: 'task:completed', listener: (task: Task) => void): void;
  on(event: 'task:failed', listener: (task: Task, error: Error) => void): void;
  on(event: 'task:cancelled', listener: (task: Task) => void): void;
  on(event: 'worker:spawned', listener: (worker: Worker) => void): void;
  on(event: 'worker:killed', listener: (worker: Worker) => void): void;

  // biome-ignore lint/suspicious/noExplicitAny: EventEmitter-compatible variadic signatures
  emit(event: string, ...args: any[]): void;
  // biome-ignore lint/suspicious/noExplicitAny: EventEmitter-compatible variadic signatures
  off(event: string, listener: (...args: any[]) => void): void;
}

// EventBus interface has been moved to src/core/events/event-bus.ts
// Import it from there:
// import { EventBus } from './events/event-bus.js';

/**
 * Main task manager orchestrator
 */
export interface TaskManager {
  delegate(request: TaskRequest): Promise<Result<Task>>;
  getStatus(taskId?: TaskId): Promise<Result<Task | readonly Task[]>>;
  getLogs(taskId: TaskId, tail?: number): Promise<Result<TaskOutput>>;
  cancel(taskId: TaskId, reason?: string): Promise<Result<void>>;
  retry(taskId: TaskId): Promise<Result<Task>>;
  resume(request: ResumeTaskRequest): Promise<Result<Task>>;
}

/**
 * Schedule management service
 * ARCHITECTURE: Extracted from MCP adapter for CLI reuse
 * Pattern: Service layer with DI, Result types, event emission
 */
export interface ScheduleService {
  createSchedule(request: ScheduleCreateRequest): Promise<Result<Schedule>>;
  listSchedules(status?: ScheduleStatus, limit?: number, offset?: number): Promise<Result<readonly Schedule[]>>;
  getSchedule(
    scheduleId: ScheduleId,
    includeHistory?: boolean,
    historyLimit?: number,
  ): Promise<Result<{ schedule: Schedule; history?: readonly ScheduleExecution[] }>>;
  cancelSchedule(scheduleId: ScheduleId, reason?: string, cancelTasks?: boolean): Promise<Result<void>>;
  pauseSchedule(scheduleId: ScheduleId): Promise<Result<void>>;
  resumeSchedule(scheduleId: ScheduleId): Promise<Result<void>>;
  createPipeline(request: PipelineCreateRequest): Promise<Result<PipelineResult>>;
  createScheduledPipeline(request: ScheduledPipelineCreateRequest): Promise<Result<Schedule>>;
  createScheduledLoop(request: ScheduledLoopCreateRequest): Promise<Result<Schedule>>;
}

/**
 * Synchronous task operations for use inside Database.runInTransaction().
 * These methods throw on error (the transaction wrapper catches and converts to Result).
 * ARCHITECTURE: Narrow interface — only the operations needed inside transactions.
 */
export interface SyncTaskOperations {
  saveSync(task: Task): void;
  updateSync(taskId: TaskId, update: Partial<Task>): void;
  findByIdSync(taskId: TaskId): Task | null;
}

/**
 * Synchronous schedule operations for use inside Database.runInTransaction().
 * These methods throw on error (the transaction wrapper catches and converts to Result).
 * ARCHITECTURE: Narrow interface — only the operations needed inside transactions.
 */
export interface SyncScheduleOperations {
  updateSync(id: ScheduleId, update: Partial<Schedule>, existing?: Schedule): void;
  recordExecutionSync(execution: Omit<ScheduleExecution, 'id'>): ScheduleExecution;
  findByIdSync(id: ScheduleId): Schedule | null;
}

/**
 * Synchronous transaction runner for atomic multi-step DB operations.
 * ARCHITECTURE: Abstraction over Database — handlers depend on this interface, not concrete Database.
 * Pattern: Dependency Inversion Principle — service layer depends on abstraction.
 */
export interface TransactionRunner {
  runInTransaction<T>(fn: () => T): Result<T>;
}

/**
 * Checkpoint persistence for task resumption
 * ARCHITECTURE: Stores task state snapshots for "smart retry" enrichment
 * Pattern: Repository pattern following ScheduleRepository conventions
 */
export interface CheckpointRepository {
  save(checkpoint: Omit<TaskCheckpoint, 'id'>): Promise<Result<TaskCheckpoint>>;
  findLatest(taskId: TaskId): Promise<Result<TaskCheckpoint | null>>;
  findAll(taskId: TaskId, limit?: number): Promise<Result<readonly TaskCheckpoint[]>>;
  deleteByTask(taskId: TaskId): Promise<Result<void>>;
}

/**
 * Narrow interface for checkpoint lookup
 * ARCHITECTURE: DependencyHandler depends on this, not full CheckpointRepository
 * Rationale: Follows Interface Segregation Principle - handlers only see what they need
 */
export interface CheckpointLookup {
  findLatest(taskId: TaskId): Promise<Result<TaskCheckpoint | null>>;
}

/**
 * Output persistence for task stdout/stderr
 * Pattern: Repository pattern — all repo interfaces live in core/interfaces.ts
 */
export interface OutputRepository {
  /**
   * Persist a full output snapshot for a task
   */
  save(taskId: TaskId, output: TaskOutput): Promise<Result<void>>;

  /**
   * Append incremental stream data (stdout or stderr) to stored output
   */
  append(taskId: TaskId, stream: 'stdout' | 'stderr', data: string): Promise<Result<void>>;

  /**
   * Retrieve stored output for a task
   */
  get(taskId: TaskId): Promise<Result<TaskOutput | null>>;

  /**
   * Remove stored output for a task
   */
  delete(taskId: TaskId): Promise<Result<void>>;

  /**
   * Return the total_size byte-count for a task without loading the full output blob.
   * Returns 0 when no record exists.
   * ARCHITECTURE: Cheap probe used by the dashboard output-stream hook to skip full
   * get() calls when output has not changed — avoids O(N·T) blob reads per panel.
   */
  getSize(taskId: TaskId): Promise<Result<number>>;
}

/**
 * Narrow interface for dependency-aware task enqueuing
 * ARCHITECTURE: PersistenceHandler depends on this, not full QueueHandler
 * Rationale: Follows Interface Segregation Principle - handlers only see what they need
 */
export interface TaskEnqueuer {
  enqueueIfReady(task: Task): Promise<Result<void>>;
}

/**
 * Worker registration persistence for cross-process coordination
 * ARCHITECTURE: Tracks which workers exist across all processes sharing the same SQLite DB.
 * Enables PID-based recovery (replaces 30-minute staleness heuristic) and cross-process
 * resource checks (prevents over-spawning).
 *
 * All methods are synchronous Result<T> — better-sqlite3 is synchronous,
 * enables use inside runInTransaction().
 */
export interface WorkerRepository {
  register(registration: WorkerRegistration): Result<void>;
  unregister(workerId: WorkerId): Result<void>;
  findByTaskId(taskId: TaskId): Result<WorkerRegistration | null>;
  findByOwnerPid(ownerPid: number): Result<readonly WorkerRegistration[]>;
  findAll(): Result<readonly WorkerRegistration[]>;
  getGlobalCount(): Result<number>;
  deleteByOwnerPid(ownerPid: number): Result<number>;
  /**
   * Update the last_heartbeat timestamp for a worker to Date.now().
   * Called periodically by the worker pool to signal the owning process is alive.
   * DECISION: 30s write interval — balances DB write load vs. staleness detection speed.
   */
  updateHeartbeat(workerId: WorkerId): Result<void>;
}

// ============================================================================
// Loop interfaces (v0.7.0: Task/Pipeline Loops)
// ARCHITECTURE: Repository and service interfaces for iterative task execution
// Pattern: Follows ScheduleRepository/ScheduleService conventions
// ============================================================================

/**
 * Loop persistence and query interface
 * ARCHITECTURE: Pure Result pattern, no exceptions
 * Pattern: Repository pattern for loop management
 * Rationale: Enables loop CRUD, status tracking, iteration history
 */
export interface LoopRepository {
  /**
   * Save a new loop
   */
  save(loop: Loop): Promise<Result<void>>;

  /**
   * Update an existing loop
   */
  update(loop: Loop): Promise<Result<void>>;

  /**
   * Find loop by ID
   */
  findById(id: LoopId): Promise<Result<Loop | null>>;

  /**
   * Find loops with optional pagination
   *
   * All implementations MUST use DEFAULT_LIMIT = 100 when limit is not specified.
   * This ensures consistent behavior across implementations.
   *
   * @param limit Maximum results to return (default: 100, max recommended: 1000)
   * @param offset Skip first N results (default: 0)
   * @returns Paginated loop list ordered by created_at DESC
   */
  findAll(limit?: number, offset?: number): Promise<Result<readonly Loop[]>>;

  /**
   * Find loops by status with optional pagination
   *
   * All implementations MUST use DEFAULT_LIMIT = 100 when limit is not specified.
   *
   * @param status Loop status to filter by
   * @param limit Maximum results to return (default: 100)
   * @param offset Skip first N results (default: 0)
   * @returns Paginated loop list matching status, ordered by created_at DESC
   */
  findByStatus(status: LoopStatus, limit?: number, offset?: number): Promise<Result<readonly Loop[]>>;

  /**
   * Find loops by their owning schedule ID
   * ARCHITECTURE: Used to find loops created by a scheduled loop trigger (v0.8.0)
   *
   * @param scheduleId Schedule to find loops for
   * @returns Loops associated with the schedule, ordered by created_at DESC
   */
  findByScheduleId(scheduleId: ScheduleId): Promise<Result<readonly Loop[]>>;

  /**
   * Count total loops
   */
  count(): Promise<Result<number>>;

  /**
   * Count loops grouped by status
   * ARCHITECTURE: Used by dashboard for panel summary display
   * @returns Map of status → count for all loops in the repository
   */
  countByStatus(): Promise<Result<Record<string, number>>>;

  /**
   * Delete a loop
   */
  delete(id: LoopId): Promise<Result<void>>;

  /**
   * Record a loop iteration
   */
  recordIteration(iteration: LoopIteration): Promise<Result<void>>;

  /**
   * Get iteration history for a loop
   * @param loopId Loop to get history for
   * @param limit Maximum records to return (default: 100)
   * @param offset Skip first N results (default: 0)
   * @returns Iterations ordered by iteration_number DESC
   */
  getIterations(loopId: LoopId, limit?: number, offset?: number): Promise<Result<readonly LoopIteration[]>>;

  /**
   * Find iteration by the task ID it spawned
   * ARCHITECTURE: Used by loop handler to correlate task completion events back to iterations
   */
  findIterationByTaskId(taskId: TaskId): Promise<Result<LoopIteration | null>>;

  /**
   * Find all currently running iterations across all active loops
   * ARCHITECTURE: Used by recovery manager for crash recovery
   */
  findRunningIterations(): Promise<Result<readonly LoopIteration[]>>;

  /**
   * Update an existing iteration
   */
  updateIteration(iteration: LoopIteration): Promise<Result<void>>;

  /**
   * Clean up old completed/failed/cancelled loops
   * @param olderThanMs Age threshold in milliseconds — loops completed before (Date.now() - olderThanMs) are deleted
   * @returns Number of loops deleted
   * ARCHITECTURE: FK cascade (ON DELETE CASCADE) auto-deletes associated iterations
   */
  cleanupOldLoops(olderThanMs: number): Promise<Result<number>>;

  /**
   * Find loops updated since a given timestamp (v1.3.0)
   * @param sinceMs - Epoch milliseconds lower bound
   * @param limit - Maximum results to return
   */
  findUpdatedSince(sinceMs: number, limit: number): Promise<Result<readonly Loop[]>>;
}

/**
 * Synchronous loop operations for use inside Database.runInTransaction().
 * These methods throw on error (the transaction wrapper catches and converts to Result).
 * ARCHITECTURE: Narrow interface -- only the operations needed inside transactions.
 */
export interface SyncLoopOperations {
  updateSync(loop: Loop): void;
  recordIterationSync(iteration: LoopIteration): void;
  findByIdSync(id: LoopId): Loop | null;
  updateIterationSync(iteration: LoopIteration): void;
}

/**
 * Loop management service
 * ARCHITECTURE: Extracted for MCP/CLI reuse
 * Pattern: Service layer with DI, Result types, event emission
 */
export interface LoopService {
  createLoop(request: LoopCreateRequest): Promise<Result<Loop>>;
  /**
   * Validate a loop creation request without creating the loop.
   * ARCHITECTURE: Extracted from createLoop() so ScheduleHandler can validate
   * loopConfig before domain factory creation — prevents bypassing validations
   * like prompt length, exit condition, path, eval timeout bounds, pipeline step count.
   */
  validateCreateRequest(request: LoopCreateRequest): Promise<Result<void, Error>>;
  getLoop(
    loopId: LoopId,
    includeHistory?: boolean,
    historyLimit?: number,
  ): Promise<Result<{ loop: Loop; iterations?: readonly LoopIteration[] }>>;
  listLoops(status?: LoopStatus, limit?: number, offset?: number): Promise<Result<readonly Loop[]>>;
  cancelLoop(loopId: LoopId, reason?: string, cancelTasks?: boolean): Promise<Result<void>>;
  pauseLoop(loopId: LoopId, options?: { force?: boolean }): Promise<Result<void>>;
  resumeLoop(loopId: LoopId): Promise<Result<void>>;
}

/**
 * Exit condition evaluation result
 * ARCHITECTURE: Discriminated by strategy — retry returns pass/fail, optimize returns score
 */
export interface EvalResult {
  readonly passed: boolean;
  readonly score?: number;
  readonly exitCode?: number;
  readonly error?: string;
  readonly feedback?: string; // Narrative feedback from agent evaluator (agent mode only)
  /**
   * DECISION: Explicit 'continue'/'stop' decision field.
   * Why: feedforward mode needs 'continue' without triggering consecutiveFailures increment.
   * 'passed' alone conflates "quality gate result" with "loop control signal".
   * When set, loop handler checks this BEFORE the passed flag.
   */
  readonly decision?: 'continue' | 'stop';
  /**
   * Raw agent evaluation response text.
   * Stored per-iteration for audit and debugging.
   */
  readonly evalResponse?: string;
}

/**
 * Exit condition evaluator abstraction for dependency injection
 * ARCHITECTURE: Decouples loop handler from child_process for testability
 * Pattern: Strategy pattern — implementations can use shell exec, HTTP, etc.
 */
export interface ExitConditionEvaluator {
  /**
   * Evaluate the exit condition for a loop iteration.
   * ARCHITECTURE: Strategy semantics (RETRY pass/fail vs OPTIMIZE score) are
   * handled by each implementation — see ShellExitConditionEvaluator.
   */
  evaluate(loop: Loop, taskId: TaskId): Promise<EvalResult>;
}

// ============================================================================
// Orchestration interfaces (v0.9.0: Orchestrator Mode)
// ARCHITECTURE: Repository and service interfaces for autonomous orchestration
// Pattern: Follows LoopRepository/LoopService conventions
// ============================================================================

/**
 * Orchestration persistence and query interface
 * ARCHITECTURE: Pure Result pattern, no exceptions
 * Pattern: Repository pattern for orchestration management
 */
export interface OrchestrationRepository {
  save(orchestration: Orchestration): Promise<Result<void>>;
  update(orchestration: Orchestration): Promise<Result<void>>;
  /**
   * DECISION (2026-04-10): Conditional UPDATE used by createOrchestration to prevent
   * a race where dashboard cancellation and the in-flight create flow's
   * PLANNING→RUNNING transition could clobber each other.
   * Returns ok(true) if the row was updated, ok(false) if the status no longer matches.
   */
  updateIfStatus(orchestration: Orchestration, expectedStatus: OrchestratorStatus): Promise<Result<boolean>>;
  findById(id: OrchestratorId): Promise<Result<Orchestration | null>>;
  findAll(limit?: number, offset?: number): Promise<Result<readonly Orchestration[]>>;
  findByStatus(status: OrchestratorStatus, limit?: number, offset?: number): Promise<Result<readonly Orchestration[]>>;
  findByLoopId(loopId: LoopId): Promise<Result<Orchestration | null>>;
  delete(id: OrchestratorId): Promise<Result<void>>;
  cleanupOldOrchestrations(retentionMs: number): Promise<Result<number>>;
  /**
   * Count orchestrations grouped by status
   * ARCHITECTURE: Used by dashboard for panel summary display
   * @returns Map of status → count for all orchestrations in the repository
   */
  countByStatus(): Promise<Result<Record<string, number>>>;

  /**
   * Get child tasks attributed to an orchestration (v1.3.0)
   * Unions direct attribution (tasks.orchestrator_id) and loop iteration chain.
   * SQL-level deduplication via ROW_NUMBER inside the UNION CTE — the 'iteration'
   * kind is preferred when a task appears in both chains. LIMIT/OFFSET is applied
   * AFTER dedup so pagination is correct even when duplicates cross page boundaries.
   * @param orchestrationId - Orchestration to query
   * @param limit - Maximum tasks to return per page, ordered by updated_at DESC
   * @param offset - Zero-based row offset for pagination (default: 0)
   */
  getOrchestratorChildren(
    orchestrationId: OrchestratorId,
    limit: number,
    offset?: number,
  ): Promise<Result<readonly OrchestratorChild[]>>;

  /**
   * Count all child tasks attributed to an orchestration (v1.3.0)
   * Deduped on task_id — same semantics as getOrchestratorChildren.
   * Used for pagination footer ("Page N of M").
   */
  countOrchestratorChildren(orchestrationId: OrchestratorId): Promise<Result<number>>;

  /**
   * Find orchestrations updated since a given timestamp (v1.3.0)
   * @param sinceMs - Epoch milliseconds lower bound
   * @param limit - Maximum results to return
   */
  findUpdatedSince(sinceMs: number, limit: number): Promise<Result<readonly Orchestration[]>>;
}

/**
 * Synchronous orchestration operations for use inside Database.runInTransaction().
 * These methods throw on error (the transaction wrapper catches and converts to Result).
 * ARCHITECTURE: Narrow interface — only the operations needed inside transactions.
 */
export interface SyncOrchestrationOperations {
  saveSync(orchestration: Orchestration): void;
  updateSync(orchestration: Orchestration): void;
  findByIdSync(id: OrchestratorId): Orchestration | null;
  findByLoopIdSync(loopId: LoopId): Orchestration | null;
}

/**
 * Orchestration management service
 * ARCHITECTURE: Extracted for MCP/CLI reuse
 * Pattern: Service layer with DI, Result types, event emission
 */
export interface OrchestrationService {
  createOrchestration(request: OrchestratorCreateRequest): Promise<Result<Orchestration>>;
  createInteractiveOrchestration(request: OrchestratorCreateRequest): Promise<
    Result<{
      orchestration: Orchestration;
      systemPrompt: string;
      userPrompt: string;
    }>
  >;
  /**
   * Store the child process PID for remote cancel support.
   *
   * @returns ok(true) if PID was stored (orchestration still RUNNING),
   *   ok(false) if status already transitioned (e.g., remote cancel won the race —
   *   caller should kill the child process since cancel couldn't reach it without a PID).
   */
  updateInteractiveOrchestrationPid(id: OrchestratorId, pid: number): Promise<Result<boolean>>;
  /**
   * Finalize an interactive orchestration after the child process exits (or spawn failure).
   * Determines terminal status from outcome, updates DB, emits lifecycle event.
   *
   * Idempotency: Uses updateIfStatus(RUNNING) for atomic check-and-set.
   * If the orchestration has already transitioned from RUNNING (e.g., remote cancel
   * beat the child exit), returns ok without re-emitting events.
   *
   * DECISION: OrchestrationFailed is intentionally NOT emitted for interactive mode.
   * No downstream handler consumes it; the CLI error message is the feedback mechanism.
   *
   * @param outcome.cancelled - true if the user pressed Ctrl+C (SIGINT); determines
   *   CANCELLED vs FAILED. Cannot be inferred from exitCode alone (non-zero exit can
   *   be a genuine failure, not a cancellation).
   */
  finalizeInteractiveOrchestration(
    id: OrchestratorId,
    outcome: { exitCode: number | null; cancelled: boolean },
  ): Promise<Result<void>>;
  getOrchestration(id: OrchestratorId): Promise<Result<Orchestration>>;
  listOrchestrations(
    status?: OrchestratorStatus,
    limit?: number,
    offset?: number,
  ): Promise<Result<readonly Orchestration[]>>;
  /** @param opts.cancelAttributedTasks - default true; cancel queued/running sub-tasks */
  cancelOrchestration(
    id: OrchestratorId,
    reason?: string,
    opts?: { cancelAttributedTasks?: boolean },
  ): Promise<Result<void>>;
}

// ============================================================================
// v1.3.0: Task usage / cost tracking
// ARCHITECTURE: Follows OrchestrationRepository conventions (Result pattern, no throws)
// ============================================================================

/**
 * Persistence and aggregation interface for task token/cost usage.
 * ARCHITECTURE: Pure Result pattern, no exceptions.
 * Pattern: Repository pattern — one usage row per task.
 */
export interface UsageRepository {
  /** UPSERT-safe save — overwrites existing row if present (idempotent for retry replay) */
  save(usage: TaskUsage): Promise<Result<void>>;

  get(taskId: TaskId): Promise<Result<TaskUsage | null>>;

  /**
   * Sum token/cost across ALL tasks attributed to the orchestration,
   * following retry_of chains so retries are rolled up into the root task.
   */
  sumByOrchestrationId(orchId: OrchestratorId): Promise<Result<TaskUsage>>;

  /** Sum token/cost across all tasks in a loop's iteration history */
  sumByLoopId(loopId: LoopId): Promise<Result<TaskUsage>>;

  /**
   * Global sum across all tracked tasks, optionally filtered to tasks
   * completed since a given timestamp (epoch ms).
   */
  sumGlobal(sinceMs?: number): Promise<Result<TaskUsage>>;

  /**
   * Top N orchestrations by total cost in the given time window.
   * @param sinceMs - Include only usage rows with captured_at >= sinceMs
   * @param limit - Number of orchestrations to return
   */
  topOrchestrationsByCost(
    sinceMs: number,
    limit: number,
  ): Promise<Result<readonly { orchestrationId: OrchestratorId; totalCost: number }[]>>;
}

/**
 * Repository interface for Pipeline entities.
 * ARCHITECTURE: Pure Result pattern, no exceptions.
 * Pattern: Repository pattern — one row per pipeline, step_task_ids serialized as JSON.
 */
export interface PipelineRepository {
  save(pipeline: Pipeline): Promise<Result<void>>;
  update(pipeline: Pipeline): Promise<Result<void>>;
  findById(id: PipelineId): Promise<Result<Pipeline | null>>;
  findAll(limit?: number): Promise<Result<readonly Pipeline[]>>;
  findByStatus(status: PipelineStatus, limit?: number): Promise<Result<readonly Pipeline[]>>;
  findByScheduleId(scheduleId: ScheduleId): Promise<Result<readonly Pipeline[]>>;
  findByLoopId(loopId: LoopId): Promise<Result<readonly Pipeline[]>>;
  delete(id: PipelineId): Promise<Result<void>>;
  countByStatus(): Promise<Result<Record<string, number>>>;
  findUpdatedSince(sinceMs: number, limit: number): Promise<Result<readonly Pipeline[]>>;
  /** Find all active (pending/running) pipelines that contain the given task ID in their stepTaskIds. */
  findActiveByTaskId(taskId: TaskId): Promise<Result<readonly Pipeline[]>>;
  /**
   * Find all active (pending/running) pipelines that contain the given schedule ID in a step definition.
   * Used by PipelineHandler to populate stepTaskIds when a scheduled step's task is first delegated.
   */
  findActiveByStepScheduleId(scheduleId: ScheduleId): Promise<Result<readonly Pipeline[]>>;
}

// Re-export for convenience (consumers can import from interfaces instead of domain)
export type { ActivityEntry, OrchestratorChild, TaskUsage };
