///|
pub enum ReferenceKind {
  ComponentToken
  ComponentState
  PaletteRole
  ThemeAlias
} derive(Debug, Eq)

///|
pub struct ReferenceEdge {
  source : String
  target : String
  kind : ReferenceKind
} derive(Debug, Eq)

///|
pub struct ReferenceGraph {
  edges : Array[ReferenceEdge]
} derive(Debug, Eq)

///|
pub fn ReferenceGraph::empty() -> ReferenceGraph {
  { edges: [] }
}

///|
pub fn ReferenceGraph::add(
  self : ReferenceGraph,
  source : String,
  target : String,
  kind : ReferenceKind,
) -> ReferenceGraph {
  let edges : Array[ReferenceEdge] = []
  for edge in self.edges {
    edges.push(edge)
  }
  edges.push(ReferenceEdge::{ source, target, kind })
  { edges, }
}

///|
pub fn ReferenceGraph::add_alias(
  self : ReferenceGraph,
  source : String,
  target : String,
) -> ReferenceGraph {
  self.add(source, target, ThemeAlias)
}

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

///|
pub fn ReferenceGraph::node_count(self : ReferenceGraph) -> Int {
  let nodes : Array[String] = []
  for edge in self.edges {
    if !nodes.any(fn(item) { item == edge.source }) {
      nodes.push(edge.source)
    }
    if !nodes.any(fn(item) { item == edge.target }) {
      nodes.push(edge.target)
    }
  }
  nodes.length()
}

///|
pub fn ReferenceGraph::targets_for(
  self : ReferenceGraph,
  source : String,
) -> Array[String] {
  let result : Array[String] = []
  for edge in self.edges {
    if edge.source == source && !result.any(fn(item) { item == edge.target }) {
      result.push(edge.target)
    }
  }
  result
}

///|
pub fn ReferenceGraph::sources_for(
  self : ReferenceGraph,
  target : String,
) -> Array[String] {
  let result : Array[String] = []
  for edge in self.edges {
    if edge.target == target && !result.any(fn(item) { item == edge.source }) {
      result.push(edge.source)
    }
  }
  result
}

///|
pub fn ReferenceGraph::edges_of(
  self : ReferenceGraph,
  kind : ReferenceKind,
) -> Array[ReferenceEdge] {
  let result : Array[ReferenceEdge] = []
  for edge in self.edges {
    if edge.kind == kind {
      result.push(edge)
    }
  }
  result
}

///|
pub fn ReferenceGraph::missing_targets(
  self : ReferenceGraph,
  known : Array[String],
) -> Array[String] {
  let missing : Array[String] = []
  for edge in self.edges {
    if !known.any(fn(item) { item == edge.target }) &&
      !missing.any(fn(item) { item == edge.target }) {
      missing.push(edge.target)
    }
  }
  missing
}

///|
pub fn ReferenceGraph::to_markdown(self : ReferenceGraph) -> String {
  let lines : Array[String] = [
    "## Reference graph", "", "| Source | Target | Kind |", "| --- | --- | --- |",
  ]
  for edge in self.edges {
    lines.push(
      "| " +
      edge.source +
      " | " +
      edge.target +
      " | " +
      reference_kind_name(edge.kind) +
      " |",
    )
  }
  lines.join("\n")
}

///|
pub fn BrandBook::reference_graph(self : BrandBook) -> ReferenceGraph {
  let graph = ReferenceGraph::empty()
  let mut result = graph
  for component in self.components {
    for token in component.tokens {
      result = result.add(component.name, token, ComponentToken)
    }
    for state in component.states {
      result = result.add(component.name, state, ComponentState)
    }
  }
  for color in self.palette.colors {
    if color.role != "" {
      result = result.add(color.role, color.name, PaletteRole)
    }
  }
  result
}

///|
fn reference_kind_name(kind : ReferenceKind) -> String {
  match kind {
    ComponentToken => "component-token"
    ComponentState => "component-state"
    PaletteRole => "palette-role"
    ThemeAlias => "theme-alias"
  }
}

///|
pub fn BrandBook::missing_references(self : BrandBook) -> Array[String] {
  let known : Array[String] = []
  for color in self.palette.colors {
    known.push(color.name)
  }
  for font in self.typography.families {
    known.push(font.name)
  }
  for step in self.typography.scale {
    known.push(step.name)
  }
  self.reference_graph().missing_targets(known)
}