///|
pub struct ThemeChange {
  path : String
  before : ThemeToken
  after : ThemeToken
} derive(Debug, Eq)

///|
pub struct ThemeDiff {
  added : Array[ThemeToken]
  removed : Array[ThemeToken]
  changed : Array[ThemeChange]
} derive(Debug, Eq)

///|
pub fn ThemeDiff::between(
  before : ThemeDocument,
  after : ThemeDocument,
) -> ThemeDiff {
  let added : Array[ThemeToken] = []
  let removed : Array[ThemeToken] = []
  let changed : Array[ThemeChange] = []
  for old in before.tokens {
    match after.find(old.path.canonical()) {
      None => removed.push(old)
      Some(new) if old != new =>
        changed.push(ThemeChange::{
          path: old.path.canonical(),
          before: old,
          after: new,
        })
      Some(_) => ()
    }
  }
  for new in after.tokens {
    if before.find(new.path.canonical()) is None {
      added.push(new)
    }
  }
  { added, removed, changed }
}

///|
pub fn ThemeDiff::is_empty(self : ThemeDiff) -> Bool {
  self.added.length() == 0 &&
  self.removed.length() == 0 &&
  self.changed.length() == 0
}

///|
pub fn ThemeDiff::change_count(self : ThemeDiff) -> Int {
  self.added.length() + self.removed.length() + self.changed.length()
}

///|
pub fn ThemeDiff::has_breaking_changes(self : ThemeDiff) -> Bool {
  self.removed.length() > 0
}

///|
pub fn ThemeDiff::summary(self : ThemeDiff) -> String {
  "added=" +
  self.added.length().to_string() +
  ", removed=" +
  self.removed.length().to_string() +
  ", changed=" +
  self.changed.length().to_string()
}

///|
pub fn ThemeDiff::to_markdown(self : ThemeDiff) -> String {
  let lines : Array[String] = [
    "## Theme diff",
    "",
    "Summary: " + self.summary(),
    "",
  ]
  if self.added.length() > 0 {
    lines.push("### Added")
    for token in self.added {
      lines.push("- `" + token.path.canonical() + "`")
    }
    lines.push("")
  }
  if self.removed.length() > 0 {
    lines.push("### Removed")
    for token in self.removed {
      lines.push("- `" + token.path.canonical() + "`")
    }
    lines.push("")
  }
  if self.changed.length() > 0 {
    lines.push("### Changed")
    for change in self.changed {
      lines.push(
        "- `" +
        change.path +
        "`: " +
        theme_value_display(change.before.value) +
        " → " +
        theme_value_display(change.after.value),
      )
    }
  }
  lines.join("\n")
}

///|
pub fn ThemeDiff::migration_steps(self : ThemeDiff) -> Array[String] {
  let steps : Array[String] = []
  for token in self.added {
    steps.push("add token " + token.path.canonical())
  }
  for token in self.removed {
    steps.push("remove token " + token.path.canonical())
  }
  for change in self.changed {
    steps.push("review change " + change.path)
  }
  steps
}

///|
pub fn BrandBook::diff_theme(self : BrandBook, other : BrandBook) -> ThemeDiff {
  ThemeDiff::between(self.theme_document(), other.theme_document())
}