///|
fn repr_hex(number : Int32, width : Int32) -> String {
  let digits = "0123456789abcdef"
  let result : Array[Char] = []
  let mut n = number
  for _ in 0.. String {
  let quote = if text.contains("'") && !text.contains("\"") {
    '"'
  } else {
    '\''
  }
  let out = StringBuilder()
  out.write_char(quote)
  for c in text.iter() {
    if c == quote || c == '\\' {
      out.write_char('\\')
      out.write_char(c)
    } else if c == '\n' {
      out.write_string("\\n")
    } else if c == '\r' {
      out.write_string("\\r")
    } else if c == '\t' {
      out.write_string("\\t")
    } else if !unicode_in_ranges(c, unicode_printable_ranges) {
      let n = c.to_int()
      out.write_string(
        if n <= 255 {
          "\\x" + repr_hex(n, 2)
        } else if n <= 65535 {
          "\\u" + repr_hex(n, 4)
        } else {
          "\\U" + repr_hex(n, 8)
        },
      )
    } else {
      out.write_char(c)
    }
  }
  out.write_char(quote)
  out.to_string()
}

///|
fn python_repr(value : QueryValue) -> String {
  match value {
    QText(s) => python_string(s)
    QNegativeZero(_) => "Decimal(" + python_string(value.to_text()) + ")"
    QNumber(n) => "Decimal(" + python_string(python_decimal(n)) + ")"
    QInt(n) => n.to_string()
    QDate(d) =>
      "datetime.date(" +
      d.year.to_string() +
      ", " +
      d.month.to_string() +
      ", " +
      d.day.to_string() +
      ")"
    QBool(b) => if b { "True" } else { "False" }
    QNull => "None"
    QDict(items) =>
      "{" +
      items
      .map(fn(p) { python_string(p.0) + ": " + python_repr(p.1) })
      .join(", ") +
      "}"
    QList(items) => "[" + items.map(python_repr).join(", ") + "]"
    QRecord(name, items) =>
      name +
      "(" +
      items.map(fn(p) { p.0 + "=" + python_repr(p.1) }).join(", ") +
      ")"
    QPythonType(name) => ""
    QEnum(name, v) => "<" + name + "." + v + ": " + python_string(v) + ">"
    QFrozenSet(items) | QSet(items) =>
      if items.is_empty() {
        "frozenset()"
      } else {
        let sorted = items.copy()
        sorted.sort_by(fn(a, b) { a.lexical_compare(b) })
        "frozenset({" + sorted.map(python_string).join(", ") + "})"
      }
    QInventory(i) => inventory_repr(i)
    _ => value.to_text()
  }
}

///|
fn nested_json(value : QueryValue) -> String {
  match value {
    QSet(items) | QFrozenSet(items) => {
      let sorted = items.copy()
      sorted.sort_by(fn(a, b) { a.lexical_compare(b) })
      "[" + sorted.map(fn(s) { QText(s).to_json() }).join(",") + "]"
    }
    QDict(items) =>
      "{" +
      items
      .map(fn(p) { QText(p.0).to_json() + ":" + nested_json(p.1) })
      .join(",") +
      "}"
    QList(items) => "[" + items.map(nested_json).join(",") + "]"
    QRecord(name, items) =>
      "{\"_type\":" +
      QText(name).to_json() +
      (if items.is_empty() { "" } else { "," }) +
      items
      .map(fn(p) { QText(p.0).to_json() + ":" + nested_json(p.1) })
      .join(",") +
      "}"
    QPythonType(name) => "{\"_python_type\":" + QText(name).to_json() + "}"
    QEnum(name, v) =>
      "{\"_enum\":" +
      QText(name).to_json() +
      ",\"value\":" +
      QText(v).to_json() +
      "}"
    QNegativeZero(_) =>
      "{\"_decimal\":" + QText(value.to_text()).to_json() + "}"
    QNumber(n) => "{\"_decimal\":" + QText(python_decimal(n)).to_json() + "}"
    QInterval(_, _) =>
      "{\"_relativedelta\":" + QText(value.to_text()).to_json() + "}"
    QDate(d) => "{\"_date\":" + QText(d.to_string()).to_json() + "}"
    _ => value.to_json()
  }
}

///|
fn meta_value(value : @ast.MetaValue) -> QueryValue {
  match value {
    MString(s) | MAccount(s) | MCurrency(s) | MTag(s) => QText(s)
    MNumber(n) => QNumber(n)
    MDate(d) => QDate(d)
    MBool(b) => QBool(b)
  }
}

///|
fn source_metadata(
  loc : @ast.Loc,
  meta : @ast.Meta,
) -> Array[(String, QueryValue)] {
  let result = [
    ("filename", QText(loc.file)),
    ("lineno", QInt(@bigint.BigInt::from_int(loc.line))),
  ]
  for (key, value) in meta.entries {
    if key != "filename" && key != "lineno" {
      result.push((key, meta_value(value)))
    }
  }
  result
}

///|
fn python_text_compare(a : String, b : String) -> Int32 {
  let left = a.code_units()
  let right = b.code_units()
  for i in 0..= 0xd800 && x <= 0xdbff && y >= 0xe000 {
        return 1
      }
      if y >= 0xd800 && y <= 0xdbff && x >= 0xe000 {
        return -1
      }
      return x.compare(y)
    }
  }
  left.length().compare(right.length())
}