///|
pub struct GraphEdge {
  source : String
  target : String
}

///|
pub struct CausalGraph {
  nodes : Array[String]
  edges : Array[GraphEdge]
}

///|
pub fn CausalGraph::new(nodes : Array[String]) -> CausalGraph {
  { nodes, edges: [] }
}

///|
pub fn CausalGraph::add_edge(
  self : CausalGraph,
  source : String,
  target : String,
) -> CausalGraph {
  if self.has_edge(source, target) || source == target {
    return self
  }
  let edges = self.edges.copy()
  edges.push({ source, target })
  { nodes: self.nodes, edges }
}

///|
pub fn CausalGraph::has_edge(
  self : CausalGraph,
  source : String,
  target : String,
) -> Bool {
  for edge in self.edges {
    if edge.source == source && edge.target == target {
      return true
    }
  }
  false
}

///|
pub fn CausalGraph::edge_count(self : CausalGraph) -> Int {
  self.edges.length()
}

///|
fn node_index(nodes : Array[String], target : String) -> Int {
  for i in 0.. Array[String] {
  let indegree = Array::make(self.nodes.length(), 0)
  for edge in self.edges {
    let target = node_index(self.nodes, edge.target)
    if target >= 0 {
      indegree[target] += 1
    }
  }
  let ready = Array::new()
  for i in 0..= 0 {
          indegree[target] -= 1
          if indegree[target] == 0 {
            ready.push(target)
          }
        }
      }
    }
  }
  if order.length() == self.nodes.length() {
    order
  } else {
    []
  }
}

///|
/// Returns direct parents of a node.
pub fn CausalGraph::parents(
  self : CausalGraph,
  target : String,
) -> Array[String] {
  let result = Array::new()
  for edge in self.edges {
    if edge.target == target {
      result.push(edge.source)
    }
  }
  result
}

///|
/// Returns direct children of a node.
pub fn CausalGraph::children(
  self : CausalGraph,
  source : String,
) -> Array[String] {
  let result = Array::new()
  for edge in self.edges {
    if edge.source == source {
      result.push(edge.target)
    }
  }
  result
}

///|
/// Returns all ancestors of a node using a breadth-first traversal.
pub fn CausalGraph::ancestors(
  self : CausalGraph,
  target : String,
) -> Array[String] {
  let result : Array[String] = Array::new()
  let queue = self.parents(target)
  let mut cursor = 0
  while cursor < queue.length() {
    let current = queue[cursor]
    cursor += 1
    if !result.contains(current) {
      result.push(current)
      for parent in self.parents(current) {
        if !result.contains(parent) {
          queue.push(parent)
        }
      }
    }
  }
  result
}

///|
/// Lists treatment parents that can be used as a simple backdoor adjustment set.
pub fn CausalGraph::backdoor_candidates(
  self : CausalGraph,
  treatment : String,
  outcome : String,
) -> Array[String] {
  let result = Array::new()
  for parent in self.parents(treatment) {
    if parent != outcome {
      result.push(parent)
    }
  }
  result
}