// Full JSON parser built on MoonParse combinators

///|
pub(all) enum JsonValue {
  Null
  Bool(Bool)
  Number(Float)
  String(String)
  Array(Array[JsonValue])
  Object(Array[(String, JsonValue)])
}

// Forward declaration helpers

///|
fn parse_json_value(input : ParseInput) -> (ParseInput, JsonValue)? {
  let i = multispace0(input)
  if i.is_eof() {
    return None
  }
  match parse_json_string(i) {
    Some((r, v)) => Some((r, JsonValue::String(v)))
    None =>
      match parse_json_number_val(i) {
        Some((r, v)) => Some((r, JsonValue::Number(v)))
        None =>
          match parse_json_bool_val(i) {
            Some((r, v)) => Some((r, JsonValue::Bool(v)))
            None =>
              match parse_json_null_val(i) {
                Some((r, _)) => Some((r, JsonValue::Null))
                None =>
                  match parse_json_array(i) {
                    Some((r, v)) => Some((r, JsonValue::Array(v)))
                    None =>
                      match parse_json_object(i) {
                        Some((r, v)) => Some((r, JsonValue::Object(v)))
                        None => None
                      }
                  }
              }
          }
      }
  }
}

///|
fn parse_json_string(input : ParseInput) -> (ParseInput, String)? {
  quoted_string(input)
}

///|
fn parse_json_number_val(input : ParseInput) -> (ParseInput, Float)? {
  parse_float(input)
}

///|
fn parse_json_bool_val(input : ParseInput) -> (ParseInput, Bool)? {
  match tag("true", input) {
    Some((r, _)) => Some((r, true))
    None =>
      match tag("false", input) {
        Some((r, _)) => Some((r, false))
        None => None
      }
  }
}

///|
fn parse_json_null_val(input : ParseInput) -> (ParseInput, String)? {
  tag("null", input)
}

///|
fn parse_json_array(input : ParseInput) -> (ParseInput, Array[JsonValue])? {
  let i = multispace0(input)
  match tag("[", i) {
    Some((r1, _)) => {
      let r2 = multispace0(r1)
      match tag("]", r2) {
        Some((r3, _)) => Some((r3, []))
        None => parse_json_array_elements(r2, [])
      }
    }
    None => None
  }
}

///|
fn parse_json_array_elements(
  input : ParseInput,
  acc : Array[JsonValue],
) -> (ParseInput, Array[JsonValue])? {
  let i = multispace0(input)
  match parse_json_value(i) {
    Some((r1, val)) => {
      acc.push(val)
      let r2 = multispace0(r1)
      match tag(",", r2) {
        Some((r3, _)) => parse_json_array_elements(r3, acc)
        None => {
          let r3 = multispace0(r2)
          match tag("]", r3) {
            Some((r4, _)) => Some((r4, acc))
            None => None
          }
        }
      }
    }
    None => None
  }
}

///|
fn parse_json_object(
  input : ParseInput,
) -> (ParseInput, Array[(String, JsonValue)])? {
  let i = multispace0(input)
  match tag("{", i) {
    Some((r1, _)) => {
      let r2 = multispace0(r1)
      match tag("}", r2) {
        Some((r3, _)) => Some((r3, []))
        None => parse_json_object_pairs(r2, [])
      }
    }
    None => None
  }
}

///|
fn parse_json_object_pairs(
  input : ParseInput,
  acc : Array[(String, JsonValue)],
) -> (ParseInput, Array[(String, JsonValue)])? {
  let i = multispace0(input)
  match quoted_string(i) {
    Some((r1, key)) => {
      let r2 = multispace0(r1)
      match tag(":", r2) {
        Some((r3, _)) => {
          let r4 = multispace0(r3)
          match parse_json_value(r4) {
            Some((r5, val)) => {
              acc.push((key, val))
              let r6 = multispace0(r5)
              match tag(",", r6) {
                Some((r7, _)) => parse_json_object_pairs(r7, acc)
                None => {
                  let r7 = multispace0(r6)
                  match tag("}", r7) {
                    Some((r8, _)) => Some((r8, acc))
                    None => None
                  }
                }
              }
            }
            None => None
          }
        }
        None => None
      }
    }
    None => None
  }
}

// Public API

///|
pub fn json_parse(input : ParseInput) -> (ParseInput, JsonValue)? {
  let i = multispace0(input)
  parse_json_value(i)
}

// Helper: format JsonValue back to string

///|
pub fn json_value_to_string(val : JsonValue) -> String {
  match val {
    JsonValue::Null => "null"
    JsonValue::Bool(b) => if b { "true" } else { "false" }
    JsonValue::Number(f) => f.to_string()
    JsonValue::String(s) => "\"" + s + "\""
    JsonValue::Array(arr) => json_array_to_string(arr, 0, "[")
    JsonValue::Object(pairs) => json_object_to_string(pairs, 0, "{")
  }
}

///|
fn json_array_to_string(
  arr : Array[JsonValue],
  idx : Int,
  result : String,
) -> String {
  if idx >= arr.length() {
    result + "]"
  } else {
    let sep = if idx > 0 { "," } else { "" }
    json_array_to_string(
      arr,
      idx + 1,
      result + sep + json_value_to_string(arr[idx]),
    )
  }
}

///|
fn json_object_to_string(
  pairs : Array[(String, JsonValue)],
  idx : Int,
  result : String,
) -> String {
  if idx >= pairs.length() {
    result + "}"
  } else {
    let (k, v) = pairs[idx]
    let sep = if idx > 0 { "," } else { "" }
    json_object_to_string(
      pairs,
      idx + 1,
      result + sep + "\"" + k + "\":" + json_value_to_string(v),
    )
  }
}