/**
 * ProjectionStore - Domain projections for TUI consumption
 *
 * Implements contracts §17; DD §13.1.
 *
 * @module packages/runtime/src/projection/ProjectionStore
 */

import type { RuntimeEvent, SessionID } from '@aircoding/contracts'

export interface SessionProjection {
  session_id: string
  project_id: string
  status: string
  title?: string
  tasks: TaskProjection[]
  agents: AgentProjection[]
}

export interface TaskProjection {
  id: string
  type: string
  status: string
  title: string
  retry_count: number
  attempts: number
  created_at: string
}

export interface AgentProjection {
  id: string
  type: string
  status: string
  task_id?: string
  last_heartbeat?: string
}

export type ProjectionSubscriber = (projection: SessionProjection) => void

export class ProjectionStore {
  private snapshot: Map<string, SessionProjection> = new Map()
  private subscribers: ProjectionSubscriber[] = []

  /**
   * Hydrate projection from repositories.
   */
  hydrate(session_id: string, data: {
    session: { id: string; project_id: string; status: string; title?: string }
    tasks: TaskProjection[]
    agents: AgentProjection[]
  }): void {
    this.snapshot.set(session_id, {
      session_id: data.session.id,
      project_id: data.session.project_id,
      status: data.session.status,
      title: data.session.title,
      tasks: data.tasks,
      agents: data.agents
    })
  }

  /**
   * Apply an event to the projection (incrementally update).
   */
  apply(event: RuntimeEvent): void {
    const session_id = event.session_id
    const proj = this.snapshot.get(session_id)
    if (!proj) return

    switch (event.type) {
      case 'task.created': {
        const p = event.payload as unknown as TaskProjection
        proj.tasks.push(p)
        break
      }
      case 'task.status.changed': {
        const p = event.payload as { task_id: string; status: string }
        const task = proj.tasks.find(t => t.id === p.task_id)
        if (task) task.status = p.status
        break
      }
      case 'agent.created': {
        const p = event.payload as unknown as AgentProjection
        proj.agents.push(p)
        break
      }
      case 'agent.status.changed': {
        const p = event.payload as { agent_id: string; status: string }
        const agent = proj.agents.find(a => a.id === p.agent_id)
        if (agent) agent.status = p.status
        break
      }
      case 'session.status.changed': {
        const p = event.payload as { status: string }
        proj.status = p.status
        break
      }
    }

    this.notify(proj)
  }

  /**
   * Get current snapshot for a session.
   */
  get_snapshot(session_id: string): SessionProjection | undefined {
    return this.snapshot.get(session_id)
  }

  /**
   * Subscribe to projection updates.
   */
  subscribe(subscriber: ProjectionSubscriber): () => void {
    this.subscribers.push(subscriber)
    return () => {
      this.subscribers = this.subscribers.filter(s => s !== subscriber)
    }
  }

  /**
   * Full rebuild from DB (INV-5: from SQLite, not EventBus).
   * TODO(P6): Query all repositories to rebuild projection from database state.
   */
  rebuild(session_id: string): void {
    // STUB: Would query SessionRepository, TaskRepository, AgentRepository etc.
  }

  private notify(projection: SessionProjection): void {
    for (const sub of this.subscribers) {
      sub(projection)
    }
  }
}
