/**
 * WorkerRuntime - In-worker side-effect surface
 *
 * Implements contracts §10; DD §8.3.
 * INV-3: workers reach fs/shell/network/SQLite ONLY through parent-mediated tool IPC.
 *
 * @module packages/workers/src/WorkerRuntime
 */

export interface WorkerRuntimeConfig {
  agent_id: string
  session_id: string
}

export interface ToolCallRequest {
  call_id: string
  name: string
  arguments: Record<string, unknown>
}

export interface ToolCallResult {
  call_id: string
  type: 'text' | 'error' | 'artifact'
  content: Record<string, unknown>
}

export class WorkerRuntime {
  private agent_id: string
  private session_id: string
  private pending_calls: Map<string, { resolve: (r: ToolCallResult) => void; reject: (e: Error) => void }> = new Map()
  private output: (line: string) => void

  constructor(config: WorkerRuntimeConfig, output: (line: string) => void) {
    this.agent_id = config.agent_id
    this.session_id = config.session_id
    this.output = output
  }

  /**
   * Call a tool through the parent process via IPC.
   * INV-3: This is the ONLY way workers interact with the outside world.
   */
  async call_tool(name: string, args: Record<string, unknown>): Promise<ToolCallResult> {
    const call_id = crypto.randomUUID()

    const promise = new Promise<ToolCallResult>((resolve, reject) => {
      this.pending_calls.set(call_id, { resolve, reject })

      // Set timeout
      setTimeout(() => {
        this.pending_calls.delete(call_id)
        reject(new Error(`Tool call timeout: ${name}`))
      }, 300000) // 5 minutes
    })

    // Send tool.call via IPC
    this.send_message('tool.call', {
      call_id,
      name,
      arguments: args
    })

    return promise
  }

  /**
   * Emit an event to the parent.
   */
  emit(type: string, payload: Record<string, unknown>): void {
    this.send_message('event', { type, ...payload })
  }

  /**
   * Create a checkpoint.
   */
  checkpoint(name: string, data?: Record<string, unknown>): void {
    this.send_message('worker.checkpoint', { name, data })
  }

  /**
   * Report worker result to parent.
   */
  async report_result(result: Record<string, unknown>): Promise<void> {
    this.send_message('worker.result', result)
  }

  /**
   * Send heartbeat.
   */
  heartbeat(): void {
    this.send_message('worker.heartbeat', { timestamp: new Date().toISOString() })
  }

  /**
   * Handle incoming message from parent (tool.result, agent.cancel, agent.ping).
   */
  handle_message(type: string, payload: Record<string, unknown>): void {
    switch (type) {
      case 'tool.result': {
        const call_id = payload.call_id as string
        const pending = this.pending_calls.get(call_id)
        if (pending) {
          this.pending_calls.delete(call_id)
          pending.resolve(payload as unknown as ToolCallResult)
        }
        break
      }

      case 'agent.cancel':
        // Cancel all pending calls
        for (const [id, pending] of this.pending_calls) {
          pending.reject(new Error('Agent cancelled'))
          this.pending_calls.delete(id)
        }
        break

      case 'agent.ping':
        // Respond to ping
        this.send_message('worker.heartbeat', { timestamp: new Date().toISOString() })
        break
    }
  }

  /**
   * Send ready handshake.
   */
  send_ready(protocol_version: number, worker_version: string): void {
    this.send_message('worker.ready', {
      protocol_version,
      worker_version,
      agent_id: this.agent_id,
      session_id: this.session_id
    })
  }

  /**
   * Send error.
   */
  send_error(message: string): void {
    this.send_message('worker.error', { message })
  }

  // ============================================================================
  // Private
  // ============================================================================

  private send_message(type: string, payload: Record<string, unknown>): void {
    const msg = {
      id: crypto.randomUUID(),
      kind: type,          // IpcKind per contracts §10
      type,
      direction: 'worker_to_parent',
      session_id: this.session_id,
      agent_id: this.agent_id,
      protocol_version: 1,
      timestamp: new Date().toISOString(),
      payload
    }
    this.output(JSON.stringify(msg))
  }
}
