///|
fn optional_string_to_json(value : String?) -> Json {
  match value {
    Some(value) => Json::string(value)
    None => Json::null()
  }
}

///|
pub impl ToJson for IdeMessage with fn to_json(self) {
  match self {
    Hover(req_id~, client_id~, path~, line~, column~) =>
      {
        "ch": "ide",
        "type": "hover",
        "req_id": req_id,
        "client_id": client_id,
        "path": path,
        "line": line,
        "column": column,
      }
    CancelHover(req_id~, client_id~) =>
      {
        "ch": "ide",
        "type": "cancel_hover",
        "req_id": req_id,
        "client_id": client_id,
      }
    HoverResult(req_id~, client_id~, path~, range~, contents~) =>
      {
        "ch": "ide",
        "type": "hover_result",
        "req_id": req_id,
        "client_id": client_id,
        "path": path,
        "range": optional_string_to_json(range),
        "contents": string_array_to_json(contents),
      }
    IdeError(req_id~, client_id~, path~, code~, message~) =>
      {
        "ch": "ide",
        "type": "error",
        "req_id": req_id,
        "client_id": client_id,
        "path": path,
        "code": code.to_wire(),
        "message": message,
      }
  }
}

///|
pub impl @json.FromJson for IdeMessage with fn from_json(json, path) {
  guard json is Object(fields) else {
    raise JsonDecodeError((path, "expected JSON object"))
  }
  guard fields.get("type") is Some(String(message_type)) else {
    raise JsonDecodeError((path, "missing field 'type'"))
  }
  guard fields.get("req_id") is Some(String(req_id)) else {
    raise JsonDecodeError((path, "missing field 'req_id'"))
  }
  let client_id = parse_string_or_default(fields, "client_id")
  match message_type {
    "hover" => {
      guard fields.get("path") is Some(String(document_path)) else {
        raise JsonDecodeError((path, "missing field 'path'"))
      }
      let line = parse_int_or_default(fields, "line", default=0, path)
      let column = parse_int_or_default(fields, "column", default=0, path)
      Hover(req_id~, client_id~, path=document_path, line~, column~)
    }
    "cancel_hover" => CancelHover(req_id~, client_id~)
    "hover_result" => {
      guard fields.get("path") is Some(String(document_path)) else {
        raise JsonDecodeError((path, "missing field 'path'"))
      }
      let range = parse_optional_string(fields, "range", path)
      let contents = parse_string_array_or_empty(fields, "contents", path)
      HoverResult(req_id~, client_id~, path=document_path, range~, contents~)
    }
    "error" => {
      guard fields.get("path") is Some(String(document_path)) else {
        raise JsonDecodeError((path, "missing field 'path'"))
      }
      guard fields.get("code") is Some(String(code_wire)) else {
        raise JsonDecodeError((path, "missing field 'code'"))
      }
      guard IdeErrorCode::from_wire(code_wire) is Some(code) else {
        raise JsonDecodeError((path, "unknown ide error code: \{code_wire}"))
      }
      guard fields.get("message") is Some(String(message)) else {
        raise JsonDecodeError((path, "missing field 'message'"))
      }
      IdeError(req_id~, client_id~, path=document_path, code~, message~)
    }
    _ =>
      raise JsonDecodeError((path, "unknown IdeMessage type: \{message_type}"))
  }
}

///|
pub fn IdeMessage::to_log_string(self : IdeMessage) -> String {
  match self {
    Hover(req_id~, client_id~, path~, line~, column~) =>
      "Ide(Hover req_id=\{@debug.to_string(req_id)} client_id=\{@debug.to_string(client_id)} path=\{@debug.to_string(path)} line=\{line} column=\{column})"
    CancelHover(req_id~, client_id~) =>
      "Ide(CancelHover req_id=\{@debug.to_string(req_id)} client_id=\{@debug.to_string(client_id)})"
    HoverResult(req_id~, client_id~, path~, range~, contents~) => {
      let range_log = match range {
        Some(value) => @debug.to_string(value)
        None => "null"
      }
      "Ide(HoverResult req_id=\{@debug.to_string(req_id)} client_id=\{@debug.to_string(client_id)} path=\{@debug.to_string(path)} range=\{range_log} contents=\{contents.length()})"
    }
    IdeError(req_id~, client_id~, path~, code~, message~) =>
      "Ide(Error req_id=\{@debug.to_string(req_id)} client_id=\{@debug.to_string(client_id)} path=\{@debug.to_string(path)} code=\{code.to_wire()} message_len=\{message.length()})"
  }
}