///|
/// BiDi handler for browsingContext.getComputedStyles
fn BidiProtocol::handle_get_computed_styles(
  self : BidiProtocol,
  request_id : Int,
  params : Json?,
) -> Unit {
  match self.resolve_get_computed_styles(request_id, params) {
    Some(result) => self.send_success(request_id, Some(result))
    None => ()
  }
}

///|
fn BidiProtocol::handle_get_all_computed_styles(
  self : BidiProtocol,
  request_id : Int,
  params : Json?,
) -> Unit {
  match self.resolve_get_all_computed_styles(request_id, params) {
    Some(result) => self.send_success(request_id, Some(result))
    None => ()
  }
}

///|
fn BidiProtocol::resolve_get_computed_styles(
  self : BidiProtocol,
  request_id : Int,
  params : Json?,
) -> Json? {
  let params = match params {
    Some(Object(map)) => map
    _ => {
      self.send_error(
        request_id, "invalid argument", "params must be an object",
      )
      return None
    }
  }

  let ctx_id = match params.get("context") {
    Some(String(id)) => id
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "context must be a string",
      )
      return None
    }
    None => {
      self.send_error(request_id, "invalid argument", "Missing context")
      return None
    }
  }

  let _session = match self.manager.get_session(ctx_id) {
    Some(session) => session
    None => {
      self.send_error(request_id, "no such frame", "Unknown context: " + ctx_id)
      return None
    }
  }

  let mode = match params.get("mode") {
    None => "live-inline"
    Some(String(value)) =>
      match value {
        "live-inline" | "html-inline" => value
        _ => {
          self.send_error(
            request_id, "invalid argument", "mode must be 'live-inline' or 'html-inline'",
          )
          return None
        }
      }
    Some(_) => {
      self.send_error(request_id, "invalid argument", "mode must be a string")
      return None
    }
  }

  let properties : Array[String] = match params.get("properties") {
    None => []
    Some(Array(items)) => {
      let props : Array[String] = []
      for item in items {
        match item {
          String(value) => props.push(value)
          _ => ()
        }
      }
      props
    }
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "properties must be an array of strings",
      )
      return None
    }
  }

  let (html, target_selector) = match
    self.resolve_get_computed_styles_target(request_id, params, mode) {
    Some(target) => target
    None => return None
  }
  let result = @responsive.discover_computed_styles_with_state_names(
    html,
    target_selector,
    [],
    properties,
  )
  let styles_json : Map[String, Json] = {}
  result.normal.each(fn(prop, val) { styles_json[prop] = Json::string(val) })
  let out : Map[String, Json] = {}
  out["styles"] = make_object(styles_json)
  Some(make_object(out))
}

///|
fn BidiProtocol::resolve_get_all_computed_styles(
  self : BidiProtocol,
  request_id : Int,
  params : Json?,
) -> Json? {
  let params = match params {
    Some(Object(map)) => map
    _ => {
      self.send_error(
        request_id, "invalid argument", "params must be an object",
      )
      return None
    }
  }

  let ctx_id = match params.get("context") {
    Some(String(id)) => id
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "context must be a string",
      )
      return None
    }
    None => {
      self.send_error(request_id, "invalid argument", "Missing context")
      return None
    }
  }

  let _session = match self.manager.get_session(ctx_id) {
    Some(session) => session
    None => {
      self.send_error(request_id, "no such frame", "Unknown context: " + ctx_id)
      return None
    }
  }

  let mode = match params.get("mode") {
    None => "live-inline"
    Some(String(value)) =>
      match value {
        "live-inline" | "html-inline" => value
        _ => {
          self.send_error(
            request_id, "invalid argument", "mode must be 'live-inline' or 'html-inline'",
          )
          return None
        }
      }
    Some(_) => {
      self.send_error(request_id, "invalid argument", "mode must be a string")
      return None
    }
  }

  let properties : Array[String] = match params.get("properties") {
    None => []
    Some(Array(items)) => {
      let props : Array[String] = []
      for item in items {
        match item {
          String(value) => props.push(value)
          _ => ()
        }
      }
      props
    }
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "properties must be an array of strings",
      )
      return None
    }
  }

  let html = responsive_breakpoint_source_html(mode)
  let styles = @responsive.discover_all_computed_styles(html, properties)
  let styles_json : Map[String, Json] = {}
  styles.each(fn(selector, style_map) {
    let entry_json : Map[String, Json] = {}
    style_map.each(fn(prop, value) { entry_json[prop] = Json::string(value) })
    styles_json[selector] = make_object(entry_json)
  })
  let out : Map[String, Json] = {}
  out["styles"] = make_object(styles_json)
  Some(make_object(out))
}

///|
fn BidiProtocol::resolve_get_computed_styles_target(
  self : BidiProtocol,
  request_id : Int,
  params : Map[String, Json],
  mode : String,
) -> (String, String)? {
  match params.get("selector") {
    Some(String(selector)) =>
      return Some((responsive_breakpoint_source_html(mode), selector))
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "selector must be a string",
      )
      return None
    }
    None => ()
  }

  match params.get("sharedId") {
    Some(String(shared_id)) => {
      match self.validate_get_computed_styles_shared_id(request_id, shared_id) {
        Some(_) => ()
        None => return None
      }
      return Some(
        (
          responsive_breakpoint_source_html_with_shared_node_marker(shared_id),
          computed_styles_target_marker_selector(),
        ),
      )
    }
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "sharedId must be a string",
      )
      return None
    }
    None => ()
  }

  match params.get("shared_id") {
    Some(String(shared_id)) => {
      match self.validate_get_computed_styles_shared_id(request_id, shared_id) {
        Some(_) => ()
        None => return None
      }
      return Some(
        (
          responsive_breakpoint_source_html_with_shared_node_marker(shared_id),
          computed_styles_target_marker_selector(),
        ),
      )
    }
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "shared_id must be a string",
      )
      return None
    }
    None => ()
  }

  match params.get("element") {
    Some(Object(element)) => {
      let shared_id = match
        self.resolve_get_computed_styles_element_shared_id(request_id, element) {
        Some(shared_id) => shared_id
        None => return None
      }
      return Some(
        (
          responsive_breakpoint_source_html_with_shared_node_marker(shared_id),
          computed_styles_target_marker_selector(),
        ),
      )
    }
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "element must be a node reference",
      )
      return None
    }
    None => ()
  }

  match params.get("elementId") {
    Some(String(element_id)) =>
      return Some(
        (
          responsive_breakpoint_source_html_with_element_id_marker(element_id),
          computed_styles_target_marker_selector(),
        ),
      )
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "elementId must be a string",
      )
      return None
    }
    None => ()
  }

  match params.get("element_id") {
    Some(String(element_id)) =>
      return Some(
        (
          responsive_breakpoint_source_html_with_element_id_marker(element_id),
          computed_styles_target_marker_selector(),
        ),
      )
    Some(_) => {
      self.send_error(
        request_id, "invalid argument", "element_id must be a string",
      )
      return None
    }
    None => ()
  }

  self.send_error(
    request_id, "invalid argument", "Missing selector, sharedId, element, or elementId",
  )
  None
}

///|
fn BidiProtocol::resolve_get_computed_styles_element_shared_id(
  self : BidiProtocol,
  request_id : Int,
  element : Map[String, Json],
) -> String? {
  let value_map = match element.get("value") {
    Some(Object(value_map)) => Some(value_map)
    _ => None
  }
  let shared_id = match element.get("sharedId") {
    Some(String(shared_id)) => Some(shared_id)
    _ =>
      match value_map {
        Some(value_map) =>
          match value_map.get("sharedId") {
            Some(String(shared_id)) => Some(shared_id)
            _ => None
          }
        None => None
      }
  }
  let shared_id = match shared_id {
    Some(shared_id) => shared_id
    None => {
      self.send_error(
        request_id, "invalid argument", "element.sharedId must be a string",
      )
      return None
    }
  }
  match value_map {
    Some(value_map) =>
      match value_map.get("nodeType") {
        Some(Number(node_type, ..)) if node_type != 1.0 => {
          self.send_error(request_id, "no such element", "No such element")
          return None
        }
        _ => ()
      }
    None => ()
  }
  match self.validate_get_computed_styles_shared_id(request_id, shared_id) {
    Some(_) => ()
    None => return None
  }
  Some(shared_id)
}

///|
fn BidiProtocol::validate_get_computed_styles_shared_id(
  self : BidiProtocol,
  request_id : Int,
  shared_id : String,
) -> Unit? {
  if !has_runtime_shared_node(shared_id) {
    self.send_error(
      request_id,
      "no such node",
      "Unknown sharedId: " + shared_id,
    )
    return None
  }
  if !runtime_shared_node_is_element(shared_id) {
    self.send_error(request_id, "no such element", "No such element")
    return None
  }
  Some(())
}