///|
/// BiDi JSON codec

///|
/// Serialize request to JSON string
pub fn BidiRequest::to_json(self : BidiRequest) -> String {
  let obj : Map[String, Json] = {}
  obj["id"] = Json::number(self.id.to_double())
  obj["method"] = Json::string(self.method_name)
  match self.params {
    Some(p) => obj["params"] = p
    None => ()
  }
  Json::object(obj).stringify()
}

///|
/// Parse incoming command request JSON for BiDi server implementations.
pub fn parse_request(json_str : String) -> Result[BidiRequest, String] {
  let json = @json.parse(json_str) catch { _ => return Err("Invalid JSON") }
  match json {
    Object(map) => parse_request_map(map)
    _ => Err("Invalid JSON")
  }
}

///|
fn parse_request_map(map : Map[String, Json]) -> Result[BidiRequest, String] {
  let id = match map.get("id") {
    Some(Number(n, ..)) =>
      if n.trunc() == n {
        n.to_int()
      } else {
        return Err("Invalid id field")
      }
    _ => return Err("Missing id field")
  }
  let method_name = match map.get("method") {
    Some(String(method_name)) => method_name
    _ => return Err("Missing method field")
  }
  Ok({ id, method_name, params: map.get("params") })
}

///|
/// Serialize a WebDriver BiDi success response envelope.
pub fn success_response_to_json(id : Int, result : Json?) -> String {
  let obj : Map[String, Json] = {}
  obj["id"] = Json::number(id.to_double())
  obj["type"] = Json::string("success")
  match result {
    Some(value) => obj["result"] = value
    None => obj["result"] = Json::object({})
  }
  Json::object(obj).stringify()
}

///|
/// Serialize a WebDriver BiDi error response envelope.
pub fn error_response_to_json(
  id : Int,
  code : String,
  message : String,
) -> String {
  error_response_to_json_with_stacktrace(id, code, message, "")
}

///|
/// Serialize a WebDriver BiDi error response envelope with stacktrace.
pub fn error_response_to_json_with_stacktrace(
  id : Int,
  code : String,
  message : String,
  stacktrace : String,
) -> String {
  let obj : Map[String, Json] = {}
  obj["id"] = Json::number(id.to_double())
  obj["type"] = Json::string("error")
  obj["error"] = Json::string(code)
  obj["message"] = Json::string(message)
  obj["stacktrace"] = Json::string(stacktrace)
  Json::object(obj).stringify()
}

///|
/// Serialize a WebDriver BiDi event envelope.
pub fn event_to_json(event_method : String, params : Json) -> String {
  Json::object({
    "type": Json::string("event"),
    "method": Json::string(event_method),
    "params": params,
  }).stringify()
}

///|
/// Parse incoming message (response/event)
pub fn parse_incoming(json_str : String) -> Result[BidiIncoming, String] {
  let json = @json.parse(json_str) catch { _ => return Err("Invalid JSON") }
  match json {
    Object(map) =>
      match map.get("type") {
        Some(String("event")) => parse_event(map)
        Some(String("success")) => parse_response(map)
        Some(String("error")) => parse_response(map)
        Some(String(t)) => Err("Unknown message type: " + t)
        _ => Err("Missing type field")
      }
    _ => Err("Invalid JSON")
  }
}

///|
/// Parse response from JSON object map
fn parse_response(map : Map[String, Json]) -> Result[BidiIncoming, String] {
  let id = match map.get("id") {
    Some(Number(n, ..)) =>
      if n.trunc() == n {
        n.to_int()
      } else {
        return Err("Invalid id field")
      }
    _ => return Err("Missing id field")
  }
  let is_error = match map.get("type") {
    Some(String("error")) => true
    _ => false
  }
  if is_error {
    let error = match map.get("error") {
      Some(String(s)) => s
      _ => return Err("Missing error field")
    }
    let message = match map.get("message") {
      Some(String(s)) => s
      _ => return Err("Missing message field")
    }
    let stacktrace = match map.get("stacktrace") {
      Some(String(s)) => s
      _ => ""
    }
    let err : BidiError = { error, message, stacktrace }
    Ok(BidiIncoming::Response({ id, result: None, error: Some(err) }))
  } else {
    let result = match map.get("result") {
      Some(value) => value
      None => return Err("Missing result field")
    }
    Ok(BidiIncoming::Response({ id, result: Some(result), error: None }))
  }
}

///|
/// Parse event from JSON object map
fn parse_event(map : Map[String, Json]) -> Result[BidiIncoming, String] {
  let method_name = match map.get("method") {
    Some(String(s)) => s
    _ => return Err("Missing method field")
  }
  let params = match map.get("params") {
    Some(Object(obj)) => Json::object(obj)
    Some(_) => return Err("Invalid params field")
    None => Json::object({})
  }
  Ok(BidiIncoming::Event({ event_method: method_name, params }))
}

///|
/// JSON helper: get string field
fn get_string_field(json : Json, key : String) -> String? {
  match json {
    Object(map) =>
      match map.get(key) {
        Some(String(s)) => Some(s)
        _ => None
      }
    _ => None
  }
}

///|
/// JSON helper: get bool field
fn get_bool_field(json : Json, key : String) -> Bool? {
  match json {
    Object(map) =>
      match map.get(key) {
        Some(True) => Some(true)
        Some(False) => Some(false)
        _ => None
      }
    _ => None
  }
}

///|
/// JSON helper: get object field
fn get_object_field(json : Json, key : String) -> Json? {
  match json {
    Object(map) =>
      match map.get(key) {
        Some(Object(obj)) => Some(Json::object(obj))
        _ => None
      }
    _ => None
  }
}

///|
/// JSON helper: get array field
fn get_array_field(json : Json, key : String) -> Array[Json]? {
  match json {
    Object(map) =>
      match map.get(key) {
        Some(Array(items)) => Some(items)
        _ => None
      }
    _ => None
  }
}