///|
fn JsonEdit::stringify(self : JsonEdit, terminal~ : Bool) -> String {
  output_colorful_text_for_terminal.val = terminal
  let output = Array::new()
  substringify(None, self, output, " ", 0)
  return output.join("\n")
}

///|
fn json_substringify(
  key : String?,
  json : Json,
  output : Array[String],
  tag : String,
  indent : Int,
) -> Unit {
  let prefix = if key is Some(key) { "\{key}: " } else { "" }
  let subindent = indent + 2
  match json {
    Object(object) => {
      output.push_line(tag, "\{String::make(indent, ' ')}\{prefix}{")
      for subkey, subvalue in object.iter2() {
        json_substringify(Some(subkey), subvalue, output, tag, subindent)
      }
      output.push_line(tag, "\{String::make(indent, ' ')}}")
    }
    Array(arr) => {
      output.push_line(tag, "\{String::make(indent, ' ')}\{prefix}[")
      for subvalue in arr {
        json_substringify(None, subvalue, output, tag, subindent)
      }
      output.push_line(tag, "\{String::make(indent, ' ')}]")
    }
    json =>
      output.push_line(
        tag,
        String::make(indent, ' ') + prefix + json.stringify(),
      )
  }
}

///|
fn substringify(
  key : String?,
  jsonedit : JsonEdit,
  output : Array[String],
  tag : String,
  indent : Int,
) -> Unit {
  let prefix = if key is Some(key) { "\{key}: " } else { "" }
  let subindent = indent + 2
  match jsonedit {
    Replace(old~, new~) => {
      json_substringify(key, old, output, "-", indent)
      json_substringify(key, new, output, "+", indent)
    }
    Array(edits) => {
      output.push_line(tag, "\{String::make(indent, ' ')}\{prefix}[")
      for edit in edits {
        match edit {
          None => output.push_line(tag, "\{String::make(subindent, ' ')}...")
          Some(Modification(change)) =>
            substringify(None, change, output, " ", subindent)
          Some(NoChange(json)) =>
            json_substringify(None, json, output, " ", subindent)
          Some(Delete(json)) =>
            json_substringify(None, json, output, "-", subindent)
          Some(Insert(json)) =>
            json_substringify(None, json, output, "+", subindent)
        }
      }
      output.push_line(tag, "\{String::make(indent, ' ')}]")
    }
    Object(diff, added~, deleted~) => {
      output.push_line(tag, "\{String::make(indent, ' ')}\{prefix}{")
      for key, value in deleted.iter2() {
        json_substringify(Some(key), value, output, "-", subindent)
      }
      for key, value in added.iter2() {
        json_substringify(Some(key), value, output, "+", subindent)
      }
      for key, value in diff.iter2() {
        substringify(Some(key), value, output, " ", subindent)
      }
      output.push_line(tag, "\{String::make(indent, ' ')}}")
    }
  }
}

///|
let output_colorful_text_for_terminal : Ref[Bool] = Ref::new(false)

///|
fn Array::push_line(self : Array[String], tag : String, line : String) -> Unit {
  let tagged_line = "\{tag}\{line}"
  if output_colorful_text_for_terminal.val {
    match tag {
      "+" => self.push("\u001B[32m\{tagged_line}\u001B[39m") // Green
      "-" => self.push("\u001B[31m\{tagged_line}\u001B[39m") // Red
      _ => self.push(tagged_line)
    }
  } else {
    self.push(tagged_line)
  }
}