/**
 * WorkerProtocol - NDJSON message protocol between parent and worker
 *
 * Implements contracts §10; DD §8.2.
 *
 * @module packages/runtime/src/workers/WorkerProtocol
 */

export type WorkerMessageDirection = 'parent_to_worker' | 'worker_to_parent'

export interface WorkerMessage {
  id: string
  kind: string           // IpcKind: control|event|log|tool.call|tool.result|tool.stream|worker.result|worker.checkpoint|protocol.error
  type: string           // kept for backward compat (alias for kind)
  direction: WorkerMessageDirection
  session_id: string
  agent_id: string
  correlation_id?: string
  protocol_version?: number
  timestamp: string
  payload: Record<string, unknown>
}

export type WorkerMessageType =
  // Parent → Worker
  | 'agent.start'
  | 'tool.result'
  | 'agent.cancel'
  | 'agent.ping'
  // Worker → Parent
  | 'worker.ready'
  | 'tool.call'
  | 'worker.result'
  | 'worker.checkpoint'
  | 'worker.heartbeat'
  | 'worker.error'
  | 'event'

const PROTOCOL_VERSION = 1

// Direction rules per DD §8.2
const DIRECTION_RULES: Record<string, WorkerMessageDirection> = {
  'agent.start': 'parent_to_worker',
  'tool.result': 'parent_to_worker',
  'agent.cancel': 'parent_to_worker',
  'agent.ping': 'parent_to_worker',
  'worker.ready': 'worker_to_parent',
  'tool.call': 'worker_to_parent',
  'worker.result': 'worker_to_parent',
  'worker.checkpoint': 'worker_to_parent',
  'worker.heartbeat': 'worker_to_parent',
  'worker.error': 'worker_to_parent',
  'event': 'worker_to_parent'
}

export class WorkerProtocol {
  private version: number

  constructor(version: number = PROTOCOL_VERSION) {
    this.version = version
  }

  /**
   * Encode a message to NDJSON line.
   */
  encode(message: WorkerMessage): string {
    return JSON.stringify(message) + '\n'
  }

  /**
   * Decode an NDJSON line to a WorkerMessage.
   */
  decode(line: string): WorkerMessage | null {
    try {
      const trimmed = line.trim()
      if (!trimmed) return null

      const obj = JSON.parse(trimmed) as Record<string, unknown>

      // Validate required fields
      if (!obj.id || !obj.type || !obj.timestamp || !obj.payload) {
        return null
      }

      return obj as unknown as WorkerMessage
    } catch {
      return null
    }
  }

  /**
   * Create a new message with auto-generated ID and timestamp.
   * kind derived from type (IpcKind), session_id/agent_id from payload or default.
   */
  create_message(type: WorkerMessageType, payload: Record<string, unknown>, direction: WorkerMessageDirection, opts?: { session_id?: string; agent_id?: string; correlation_id?: string }): WorkerMessage {
    return {
      id: crypto.randomUUID(),
      kind: type,        // kind aliases type per contracts §10 IpcKind
      type,
      direction,
      session_id: opts?.session_id || (payload.session_id as string) || '',
      agent_id: opts?.agent_id || (payload.agent_id as string) || '',
      correlation_id: opts?.correlation_id,
      protocol_version: PROTOCOL_VERSION,
      timestamp: new Date().toISOString(),
      payload
    }
  }

  /**
   * Validate message direction — rejects wrong-channel messages.
   */
  validate_direction(message: WorkerMessage, expected: WorkerMessageDirection): boolean {
    const expected_dir = DIRECTION_RULES[message.type]
    if (expected_dir && expected_dir !== expected) {
      return false
    }
    return true
  }

  /**
   * Check protocol version compatibility.
   */
  check_version(their_version: number): { compatible: boolean; error?: string } {
    if (their_version !== this.version) {
      return {
        compatible: false,
        error: `Protocol version mismatch: local=${this.version}, remote=${their_version}`
      }
    }
    return { compatible: true }
  }

  /**
   * Get protocol version.
   */
  get_version(): number {
    return this.version
  }
}
