///|
/// Render an expression into the default human-readable pretty string form.
///
/// - Does: Prints an expression using the standard readable textual printer.
/// - Input: A `@symcore.Expr`.
/// - Returns: A `String`.
/// - Limits: The output is readable text, not a lossless serialization format.
pub fn pretty_string(expr : Expr) -> String {
  format_expr(expr, 0)
}

///|
fn exact_one_expr(expr : Expr) -> Bool {
  match @symcore.exact_number_num_den(expr) {
    Some((num, den)) => num.compare(1N) == 0 && den.compare(1N) == 0
    None => false
  }
}

///|
fn exact_positive_expr(expr : Expr) -> Bool {
  match @symcore.exact_number_num_den(expr) {
    Some((num, _)) => num.compare(0N) > 0
    None => false
  }
}

///|
fn exact_abs_expr(expr : Expr) -> Expr {
  match @symcore.exact_number_num_den(expr) {
    Some((num, den)) if num.compare(0N) < 0 => {
      let value = @symnum.BigRational::new(-num, den) catch {
        _ => abort("exact_abs_expr: invalid denominator")
      }
      @symcore.Expr::Number(value)
    }
    _ => expr
  }
}

///|
fn split_negative_addend(expr : Expr) -> Expr? {
  match @symcore.expr_form(expr) {
    @symcore.ExprForm::Number(_) =>
      match @symcore.exact_number_num_den(expr) {
        Some((num, _)) if num.compare(0N) < 0 => Some(exact_abs_expr(expr))
        _ => None
      }
    @symcore.ExprForm::Mul(args) => {
      for i in 0.. {
            let rebuilt : Array[Expr] = []
            for j in 0.. 1 && exact_one_expr(flipped)) {
                  rebuilt.push(flipped)
                }
              } else {
                rebuilt.push(args[j])
              }
            }
            return Some(
              match rebuilt.length() {
                0 => @symcore.int(1)
                1 => rebuilt[0]
                _ => @symcore.mul(rebuilt)
              },
            )
          }
          _ => ()
        }
      }
      None
    }
    _ => None
  }
}

///|
fn exact_positive_denominator_pow(base : Expr, exp : Expr) -> Expr? {
  match @symcore.exact_number_num_den(exp) {
    Some((num, den)) if num.compare(0N) < 0 => {
      let positive = @symnum.BigRational::new(-num, den) catch {
        _ => abort("exact_positive_denominator_pow: invalid denominator")
      }
      if positive.is_one() {
        Some(base)
      } else {
        Some(@symcore.raw_pow(base, @symcore.Expr::Number(positive)))
      }
    }
    _ => None
  }
}

///|
fn exp_negative_power_numerator(base : Expr, exp : Expr) -> Expr? {
  match @symcore.exact_number_num_den(exp) {
    Some((num, den)) if num.compare(0N) < 0 => {
      let positive = @symnum.BigRational::new(-num, den) catch {
        _ => abort("exp_negative_power_numerator: invalid exponent")
      }
      let coeff = Expr::Number(-positive)
      match base {
        Expr::Apply(Expr::FunctionHead("exp"), [arg]) =>
          Some(@symcore.function("exp", [@symcore.mul([coeff, arg])]))
        Expr::Pow(Expr::Apply(Expr::FunctionHead("exp"), [arg]), inner_exp) =>
          Some(
            @symcore.function("exp", [@symcore.mul([coeff, inner_exp, arg])]),
          )
        _ => None
      }
    }
    _ => None
  }
}

///|
fn format_mul(expr : Expr, args : Array[Expr], parent_prec : Int) -> String {
  let prec = precedence(expr)
  let numerators : Array[Expr] = []
  let denominators : Array[Expr] = []
  let mut sign = ""
  for arg in args {
    match @symcore.expr_form(arg) {
      @symcore.ExprForm::Pow(base, exp) =>
        match exp_negative_power_numerator(base, exp) {
          Some(num) => numerators.push(num)
          None =>
            match exact_positive_denominator_pow(base, exp) {
              Some(den) => denominators.push(den)
              None => numerators.push(arg)
            }
        }
      _ =>
        match @symcore.exact_number_num_den(arg) {
          Some((num, den)) => {
            let abs_num = if num.compare(0N) < 0 { -num } else { num }
            if num.compare(0N) < 0 {
              sign = if sign == "" { "-" } else { "" }
            }
            if den.compare(1N) != 0 {
              numerators.push(@symcore.int(abs_num.to_int()))
              denominators.push(@symcore.int(den.to_int()))
            } else {
              numerators.push(exact_abs_expr(arg))
            }
          }
          None => numerators.push(arg)
        }
    }
  }

  if numerators.is_empty() {
    numerators.push(@symcore.int(1))
  }

  if numerators.length() > 1 {
    let filtered : Array[Expr] = []
    for child in numerators {
      if !exact_one_expr(child) {
        filtered.push(child)
      }
    }
    if !filtered.is_empty() {
      numerators.clear()
      for child in filtered {
        numerators.push(child)
      }
    }
  }

  let numerator_text = numerators
    .map(child => format_expr(child, prec))
    .join("*")
  let body = if denominators.is_empty() {
    sign + numerator_text
  } else if denominators.length() == 1 {
    let denom = denominators[0]
    let denom_text_raw = format_expr(denom, prec)
    let denom_text = match @symcore.expr_form(denom) {
      @symcore.ExprForm::Mul(_) | @symcore.ExprForm::Add(_) =>
        "(" + denom_text_raw + ")"
      _ => denom_text_raw
    }
    sign + numerator_text + "/" + denom_text
  } else {
    let denom_text = denominators
      .map(child => format_expr(child, prec))
      .join("*")
    sign + numerator_text + "/(" + denom_text + ")"
  }
  if prec < parent_prec {
    "(" + body + ")"
  } else {
    body
  }
}

///|
fn format_expr(expr : Expr, parent_prec : Int) -> String {
  match ordinary_application_name_args(expr) {
    Some((name, args)) => {
      let rendered = args.map(child => format_expr(child, 0)).join(", ")
      let body = "\{name}(\{rendered})"
      return if 5 < parent_prec { "(\{body})" } else { body }
    }
    None => ()
  }
  let prec = precedence(expr)
  let body = match @symcore.expr_form(expr) {
    @symcore.ExprForm::Number(n) => n.to_string()
    @symcore.ExprForm::Float(f) => f.to_string()
    @symcore.ExprForm::ComplexFloat(z) =>
      if @symnum.is_zero(z.to_mpc().imag) || parent_prec == 0 {
        z.to_string()
      } else {
        "(\{z.to_string()})"
      }
    @symcore.ExprForm::NumberSymbol(kind) => @symcore.number_symbol_name(kind)
    @symcore.ExprForm::Symbol(name) => name
    @symcore.ExprForm::Dummy(name, _) => @symcore.dummy_display_name(name)
    @symcore.ExprForm::Wild(name, _, _) => @symcore.wild_display_name(name)
    @symcore.ExprForm::WildFunction(name, _) => @symcore.wild_display_name(name)
    @symcore.ExprForm::IdentityFunction => "Lambda(_x, _x)"
    @symcore.ExprForm::FunctionHead(name) =>
      @symcore.standalone_function_head_display_name(name)
    @symcore.ExprForm::UndefinedFunction(name) => name
    @symcore.ExprForm::Boolean(true) => "True"
    @symcore.ExprForm::Boolean(false) => "False"
    @symcore.ExprForm::Add(args) =>
      if args.is_empty() {
        "0"
      } else if args.length() == 2 {
        match split_negative_addend(args[0]) {
          Some(positive0) if split_negative_addend(args[1]) is None =>
            format_expr(args[1], prec) + " - " + format_expr(positive0, prec)
          _ => {
            let rendered : Array[String] = []
            rendered.push(format_expr(args[0], prec))
            for i in 1..
                  rendered.push("- " + format_expr(positive, prec))
                None => rendered.push("+ " + format_expr(args[i], prec))
              }
            }
            rendered.join(" ")
          }
        }
      } else {
        let rendered : Array[String] = []
        rendered.push(format_expr(args[0], prec))
        for i in 1.. rendered.push("- " + format_expr(positive, prec))
            None => rendered.push("+ " + format_expr(args[i], prec))
          }
        }
        rendered.join(" ")
      }
    @symcore.ExprForm::Mul(args) => return format_mul(expr, args, parent_prec)
    @symcore.ExprForm::Pow(base, exp) => {
      let base_str = format_expr(base, prec)
      let exp_str = format_expr(exp, prec)
      let wrapped_base = match @symcore.expr_form(base) {
        @symcore.ExprForm::Pow(_, _) => "(\{base_str})"
        @symcore.ExprForm::Number(n) if !n.is_integral() ||
          n.numerator().op_lt(BigInt::from_int(0)) => "(\{base_str})"
        @symcore.ExprForm::Float(f) if @symnum.mpf_sign(f.to_mpf()) < 0 =>
          "(\{base_str})"
        @symcore.ExprForm::ComplexFloat(_) => "(\{base_str})"
        _ => base_str
      }
      let wrapped_exp = match @symcore.expr_form(exp) {
        @symcore.ExprForm::Number(n) if !n.is_integral() => "(\{exp_str})"
        @symcore.ExprForm::Float(_) | @symcore.ExprForm::ComplexFloat(_) =>
          "(\{exp_str})"
        _ => exp_str
      }
      "\{wrapped_base}**\{wrapped_exp}"
    }
    @symcore.ExprForm::Mod(lhs, rhs) =>
      "Mod(\{format_expr(lhs, 0)}, \{format_expr(rhs, 0)})"
    @symcore.ExprForm::Tuple(args) => {
      let rendered = args.map(child => format_expr(child, 0)).join(", ")
      if args.length() == 1 {
        "(\{rendered},)"
      } else {
        "(\{rendered})"
      }
    }
    @symcore.ExprForm::Dict(items) => {
      let rendered : Array[String] = []
      for item in @symcore.sorted_dict_entries(items) {
        let (key, value) = item
        rendered.push("\{format_expr(key, 0)}: \{format_expr(value, 0)}")
      }
      let joined = rendered.join(", ")
      "{\{joined}}"
    }
    @symcore.ExprForm::Relational(op, lhs, rhs) =>
      match op {
        @symcore.RelOp::Eq =>
          "Eq(\{format_expr(lhs, 0)}, \{format_expr(rhs, 0)})"
        @symcore.RelOp::Ne =>
          "Ne(\{format_expr(lhs, 0)}, \{format_expr(rhs, 0)})"
        @symcore.RelOp::Lt =>
          "\{format_expr(lhs, prec)} < \{format_expr(rhs, prec)}"
        @symcore.RelOp::Le =>
          "\{format_expr(lhs, prec)} <= \{format_expr(rhs, prec)}"
        @symcore.RelOp::Gt =>
          "\{format_expr(lhs, prec)} > \{format_expr(rhs, prec)}"
        @symcore.RelOp::Ge =>
          "\{format_expr(lhs, prec)} >= \{format_expr(rhs, prec)}"
      }
    @symcore.ExprForm::Derivative(inner, deriv_args) => {
      let rendered = derivative_call_args(inner, deriv_args)
        .map(child => format_expr(child, 0))
        .join(", ")
      "Derivative(\{rendered})"
    }
    @symcore.ExprForm::Subs(inner, variable, value) =>
      "Subs(\{format_expr(inner, 0)}, \{format_expr(variable, 0)}, \{format_expr(value, 0)})"
    @symcore.ExprForm::Lambda(vars, body) =>
      "Lambda(\{format_expr(vars, 0)}, \{format_expr(body, 0)})"
    @symcore.ExprForm::Apply(head, args) => {
      let rendered = args.map(child => format_expr(child, 0)).join(", ")
      "\{format_expr(head, prec)}(\{rendered})"
    }
  }
  if prec < parent_prec {
    "(\{body})"
  } else {
    body
  }
}

///|
fn precedence(expr : Expr) -> Int {
  match @symcore.expr_form(expr) {
    @symcore.ExprForm::Relational(_, _, _) => 1
    @symcore.ExprForm::Add(_) => 2
    @symcore.ExprForm::Mul(_) => 3
    @symcore.ExprForm::Pow(_, _) => 4
    @symcore.ExprForm::Mod(_, _) => 4
    _ => 5
  }
}