/**
 * TaskGraph - Dependency graph for task scheduling
 *
 * Implements DD §7.2.
 * get_runnable_tasks (hard deps done, conflicts blocked), dependents_of, validate_refs.
 *
 * @module packages/runtime/src/scheduler/TaskGraph
 */

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

export type DependencyType = 'hard' | 'soft' | 'conflict'

export interface TaskNode {
  id: TaskID
  status: string
  dependencies: Array<{ task_id: TaskID; type: DependencyType }>
}

export interface GraphValidation {
  valid: boolean
  errors: Array<{ task_id: TaskID; message: string }>
  cycles: TaskID[][]
}

export class TaskGraph {
  private tasks: Map<TaskID, TaskNode> = new Map()

  /**
   * Add a task to the graph.
   */
  add_task(task: TaskNode): void {
    this.tasks.set(task.id, { ...task })
  }

  /**
   * Add a dependency between tasks.
   */
  add_dependency(from: TaskID, to: TaskID, type: DependencyType): void {
    const task = this.tasks.get(from)
    if (task) {
      if (!task.dependencies.some(d => d.task_id === to)) {
        task.dependencies.push({ task_id: to, type })
      }
    }
  }

  /**
   * Get runnable tasks — hard deps completed, conflict deps resolved.
   */
  get_runnable_tasks(): TaskNode[] {
    const runnable: TaskNode[] = []

    for (const task of this.tasks.values()) {
      if (task.status !== 'pending') continue

      const hard_deps = task.dependencies.filter(d => d.type === 'hard')
      const conflict_deps = task.dependencies.filter(d => d.type === 'conflict')

      // All hard deps must be completed
      const hard_done = hard_deps.every(d => {
        const dep_task = this.tasks.get(d.task_id)
        return dep_task && dep_task.status === 'completed'
      })

      if (!hard_done) continue

      // No running conflict deps
      const conflict_running = conflict_deps.some(d => {
        const dep_task = this.tasks.get(d.task_id)
        return dep_task && dep_task.status === 'running'
      })

      if (conflict_running) continue

      runnable.push(task)
    }

    return runnable
  }

  /**
   * Get tasks that depend on a given task.
   */
  dependents_of(task_id: TaskID): TaskNode[] {
    const result: TaskNode[] = []

    for (const task of this.tasks.values()) {
      if (task.dependencies.some(d => d.task_id === task_id)) {
        result.push(task)
      }
    }

    return result
  }

  /**
   * Mark a task with a new status.
   */
  mark_terminal(task_id: TaskID, status: 'completed' | 'failed' | 'cancelled' | 'running'): void {
    const task = this.tasks.get(task_id)
    if (task) {
      task.status = status
    }
  }

  /**
   * Validate references — check no dangling dependencies.
   */
  validate_refs(): GraphValidation {
    const errors: Array<{ task_id: TaskID; message: string }> = []
    const cycles: TaskID[][] = []

    for (const task of this.tasks.values()) {
      for (const dep of task.dependencies) {
        if (!this.tasks.has(dep.task_id)) {
          errors.push({
            task_id: task.id,
            message: `Dangling dependency: ${dep.task_id} not found`
          })
        }
      }
    }

    // Detect cycles (simple DFS)
    const visited = new Set<string>()
    const stack = new Set<string>()

    const detect_cycle = (task_id: string, path: string[]): boolean => {
      if (stack.has(task_id)) {
        cycles.push([...path, task_id])
        return true
      }
      if (visited.has(task_id)) return false

      visited.add(task_id)
      stack.add(task_id)

      const task = this.tasks.get(task_id)
      if (task) {
        for (const dep of task.dependencies) {
          detect_cycle(dep.task_id, [...path, task_id])
        }
      }

      stack.delete(task_id)
      return false
    }

    for (const task_id of this.tasks.keys()) {
      if (!visited.has(task_id)) {
        detect_cycle(task_id, [])
      }
    }

    return { valid: errors.length === 0 && cycles.length === 0, errors, cycles }
  }

  /**
   * Get all tasks.
   */
  get_all(): TaskNode[] {
    return Array.from(this.tasks.values())
  }

  /**
   * Get tasks filtered by status.
   */
  get_tasks_by_status(status: string): TaskNode[] {
    return this.get_all().filter(t => t.status === status)
  }

  /**
   * Get task count by status.
   */
  count_by_status(): Record<string, number> {
    const counts: Record<string, number> = {}
    for (const task of this.tasks.values()) {
      counts[task.status] = (counts[task.status] || 0) + 1
    }
    return counts
  }
}
