///|
let mcp_protocol_version : String = "2024-11-05"

///|
priv enum ToolInvocation {
  ReadSemantics(@core.SemanticsGeneration?)
  PerformAction(@agent.AgentActionRequest)
  DiagnosticsReadState
  DiagnosticsReadPaint
  DiagnosticsDispatchEvent(@core.AppEvent)
  DiagnosticsDispatchCommand(@core.CommandIntent)
}

///|
fn[Host : @agent.AgentHost] semantic_tool_registry(
  _host : Host,
) -> Array[ToolSpec[Host]] {
  [
    {
      name: "read_semantics",
      description: "Read an atomically committed semantics snapshot or delta.",
      input: read_semantics_input_codec(),
      output: {
        schema: tool_output_schema(semantics_read_value_schema()),
        dispatch: dispatch_read_semantics_tool,
      },
    },
    {
      name: "perform_action",
      description: "Perform a typed action on a committed semantics target.",
      input: perform_action_input_codec(),
      output: {
        schema: tool_output_schema(action_receipt_value_schema()),
        dispatch: dispatch_perform_action_tool,
      },
    },
  ]
}

///|
fn[Host : @agent.AgentDiagnosticsHost] diagnostics_tool_registry(
  _host : Host,
) -> Array[ToolSpec[Host]] {
  [
    {
      name: "diagnostics_read_state",
      description: "Read aggregate runtime lifecycle counters.",
      input: {
        schema: object_schema(Json::empty_object()),
        decode: decode_diagnostics_read_state_invocation,
      },
      output: {
        schema: tool_output_schema(({ "type": "object" } : Json)),
        dispatch: dispatch_diagnostics_read_state_tool,
      },
    },
    {
      name: "diagnostics_read_paint",
      description: "Read the current paint command summary.",
      input: {
        schema: object_schema(Json::empty_object()),
        decode: decode_diagnostics_read_paint_invocation,
      },
      output: {
        schema: tool_output_schema(({ "type": "object" } : Json)),
        dispatch: dispatch_diagnostics_read_paint_tool,
      },
    },
    {
      name: "diagnostics_dispatch_event",
      description: "Inject a raw diagnostic input event.",
      input: {
        schema: object_schema(({ "event": { "type": "object" } } : Json), required=[
          "event",
        ]),
        decode: decode_diagnostics_dispatch_event_invocation,
      },
      output: {
        schema: tool_output_schema(({ "type": "object" } : Json)),
        dispatch: dispatch_diagnostics_event_tool,
      },
    },
    {
      name: "diagnostics_dispatch_command",
      description: "Dispatch a global diagnostic command intent.",
      input: {
        schema: object_schema(({ "intent": { "type": "string" } } : Json), required=[
          "intent",
        ]),
        decode: decode_diagnostics_dispatch_command_invocation,
      },
      output: {
        schema: tool_output_schema(({ "type": "object" } : Json)),
        dispatch: dispatch_diagnostics_command_tool,
      },
    },
  ]
}

///|
fn[Host : @agent.AgentHost + @agent.AgentDiagnosticsHost] full_tool_registry(
  host : Host,
) -> Array[ToolSpec[Host]] {
  let tools = semantic_tool_registry(host)
  tools.append(diagnostics_tool_registry(host))
  tools
}

///|
fn[Host] ToolSpec::descriptor(self : ToolSpec[Host]) -> Json {
  (
    {
      "name": self.name,
      "description": self.description,
      "inputSchema": self.input.schema,
      "outputSchema": self.output.schema,
    } : Json)
}

///|
fn[Host] ToolSpec::invoke(
  self : ToolSpec[Host],
  host : Host,
  id : Json,
  arguments : Map[String, Json],
) -> Json {
  match (self.input.decode)(arguments) {
    Err(message) => tool_failure(id, "invalid_arguments", message)
    Ok(invocation) => (self.output.dispatch)(host, id, invocation)
  }
}

///|
fn[Host] find_tool(
  name : String,
  registry : Array[ToolSpec[Host]],
) -> ToolSpec[Host]? {
  for spec in registry {
    if spec.name == name {
      return Some(spec)
    }
  }
  None
}

///|
fn json_null() -> Json {
  (None : String?).to_json()
}

///|
pub fn rpc_error(id : Json, code : Int, message : String) -> Json {
  (
    {
      "jsonrpc": "2.0",
      "id": id,
      "error": { "code": code, "message": message },
    } : Json)
}

///|
pub fn rpc_error_no_id(code : Int, message : String) -> Json {
  rpc_error(json_null(), code, message)
}

///|
fn rpc_result(id : Json, result : Json) -> Json {
  ({ "jsonrpc": "2.0", "id": id, "result": result } : Json)
}

///|
fn rpc_id(request : Json) -> Json {
  match request {
    Object(fields) => fields.get("id").unwrap_or(json_null())
    _ => json_null()
  }
}

///|
fn rpc_method(request : Json) -> String? {
  match request {
    Object(fields) =>
      match fields.get("jsonrpc") {
        Some(String("2.0")) =>
          match fields.get("method") {
            Some(String(method_name)) => Some(method_name)
            _ => None
          }
        _ => None
      }
    _ => None
  }
}

///|
fn tool_envelope_success(value : Json) -> Json {
  ({ "ok": true, "value": value } : Json)
}

///|
fn tool_envelope_error(code : String, message : String, details : Json) -> Json {
  (
    {
      "ok": false,
      "error": { "code": code, "message": message, "details": details },
    } : Json)
}

///|
fn tool_response(id : Json, envelope : Json, is_error : Bool) -> Json {
  let result : Json = {
    "content": [{ "type": "text", "text": envelope.stringify() }],
    "structuredContent": envelope,
    "isError": is_error,
  }
  rpc_result(id, result)
}

///|
fn tool_success(id : Json, value : Json) -> Json {
  tool_response(id, tool_envelope_success(value), false)
}

///|
fn tool_failure(
  id : Json,
  code : String,
  message : String,
  details? : Json = Json::empty_object(),
) -> Json {
  tool_response(id, tool_envelope_error(code, message, details), true)
}

///|
fn semantics_action_kind_code(kind : @core.SemanticsActionKind) -> String {
  match kind {
    Activate => "activate"
    Focus => "focus"
    SetText => "set_text"
    Submit => "submit"
    Scroll => "scroll"
    Select => "select"
    Expand => "expand"
    Collapse => "collapse"
    Dismiss => "dismiss"
    Increment => "increment"
    Decrement => "decrement"
    SetNumericValue => "set_numeric_value"
    SetSelection => "set_selection"
    ShowMenu => "show_menu"
  }
}

///|
fn semantics_target_json(target : @core.SemanticsTarget) -> Json {
  match target {
    BySemanticId(id) => ({ "kind": "semantic_id", "id": id } : Json)
    ByNodeId(id) => ({ "kind": "node_id", "id": id } : Json)
    ByPath(segments) =>
      (
        { "kind": "path", "path": segments.map(semantics_path_segment_json) } :
        Json)
  }
}

///|
fn semantics_path_segment_json(segment : @core.SemanticsPathSegment) -> Json {
  (
    {
      "index": segment.index,
      "role": match segment.role {
        Some(role) => semantics_role_code(role)
        None => json_null()
      },
      "label": optional_string_json(segment.label),
      "value": optional_string_json(segment.value),
      "semantic_id": optional_semantic_id_json(segment.semantic_id),
    } : Json)
}

///|
fn optional_string_json(value : String?) -> Json {
  match value {
    Some(text) => text.to_json()
    None => json_null()
  }
}

///|
fn agent_action_error_details(error : @agent.AgentActionError) -> Json {
  match error {
    HostClosed | ReentrantDispatch => Json::empty_object()
    StaleGeneration(expected, current) =>
      ({ "expected": expected, "current": current } : Json)
    TargetNotFound(target) | TargetDisabled(target) =>
      ({ "target": semantics_target_json(target) } : Json)
    TargetAmbiguous(id) => ({ "semantic_id": id } : Json)
    ActionUnavailable(target, kind) | HandlerRejected(target, kind) =>
      (
        {
          "target": semantics_target_json(target),
          "action": semantics_action_kind_code(kind),
        } : Json)
  }
}

///|
fn initialize_result() -> Json {
  (
    {
      "protocolVersion": mcp_protocol_version,
      "capabilities": { "tools": Json::empty_object() },
      "serverInfo": { "name": "moui-agent", "version": "0.1.10" },
    } : Json)
}

///|
fn[Host] list_tools(id : Json, registry : Array[ToolSpec[Host]]) -> Json {
  let tools : Array[Json] = registry.map(spec => spec.descriptor())
  rpc_result(id, ({ "tools": tools } : Json))
}

///|
pub fn[Host : @agent.AgentHost] route(host : Host, request : Json) -> Json {
  let id = rpc_id(request)
  let registry = semantic_tool_registry(host)
  match rpc_method(request) {
    None => rpc_error(id, -32600, "Invalid Request: missing method")
    Some("initialize") => rpc_result(id, initialize_result())
    Some("tools/list") => list_tools(id, registry)
    Some("tools/call") => route_tool_call(host, id, request, registry)
    Some(method_name) =>
      rpc_error(id, -32601, "Method not found: \{method_name}")
  }
}

///|
pub fn[Host : @agent.AgentHost + @agent.AgentDiagnosticsHost] route_with_diagnostics(
  host : Host,
  request : Json,
) -> Json {
  let id = rpc_id(request)
  let registry = full_tool_registry(host)
  match rpc_method(request) {
    None => rpc_error(id, -32600, "Invalid Request: missing method")
    Some("initialize") => rpc_result(id, initialize_result())
    Some("tools/list") => list_tools(id, registry)
    Some("tools/call") => route_tool_call(host, id, request, registry)
    Some(method_name) =>
      rpc_error(id, -32601, "Method not found: \{method_name}")
  }
}

///|
fn call_envelope(
  id : Json,
  request : Json,
) -> Result[(String, Map[String, Json]), Json] {
  guard request is Object(fields) else {
    return Err(rpc_error(id, -32600, "Invalid Request"))
  }
  guard fields.get("params") is Some(Object(params)) else {
    return Err(rpc_error(id, -32602, "Invalid tools/call params object"))
  }
  guard params.get("name") is Some(String(name)) else {
    return Err(rpc_error(id, -32602, "Invalid tools/call tool name"))
  }
  let arguments = match params.get("arguments") {
    None => Map([])
    Some(Object(arguments)) => arguments
    _ =>
      return Err(rpc_error(id, -32602, "Invalid tools/call arguments object"))
  }
  Ok((name, arguments))
}

///|
fn[Host] route_tool_call(
  host : Host,
  id : Json,
  request : Json,
  registry : Array[ToolSpec[Host]],
) -> Json {
  match call_envelope(id, request) {
    Err(response) => response
    Ok((name, arguments)) =>
      match find_tool(name, registry) {
        None => tool_failure(id, "unknown_tool", "Unknown tool: \{name}")
        Some(spec) => spec.invoke(host, id, arguments)
      }
  }
}

///|
fn[Host : @agent.AgentHost] dispatch_read_semantics_tool(
  host : Host,
  id : Json,
  invocation : ToolInvocation,
) -> Json {
  match invocation {
    ReadSemantics(since) =>
      match host.read_semantics(since) {
        Ok(read) => tool_success(id, agent_semantics_read_json(read))
        Err(error) =>
          tool_failure(
            id,
            error.code(),
            "Agent host could not read committed semantics.",
          )
      }
    _ => tool_registry_mismatch(id)
  }
}

///|
fn[Host : @agent.AgentHost] dispatch_perform_action_tool(
  host : Host,
  id : Json,
  invocation : ToolInvocation,
) -> Json {
  match invocation {
    PerformAction(action_request) =>
      match host.perform_action(action_request) {
        Ok(receipt) => tool_success(id, receipt.to_json())
        Err(error) =>
          tool_failure(
            id,
            error.code(),
            "Semantic action was rejected.",
            details=agent_action_error_details(error),
          )
      }
    _ => tool_registry_mismatch(id)
  }
}

///|
fn[Host : @agent.AgentDiagnosticsHost] dispatch_diagnostics_read_state_tool(
  host : Host,
  id : Json,
  invocation : ToolInvocation,
) -> Json {
  match invocation {
    DiagnosticsReadState =>
      match host.runtime_state() {
        Ok(value) => tool_success(id, value.to_json())
        Err(error) =>
          tool_failure(id, error.code(), "Diagnostics host is closed.")
      }
    _ => tool_registry_mismatch(id)
  }
}

///|
fn[Host : @agent.AgentDiagnosticsHost] dispatch_diagnostics_read_paint_tool(
  host : Host,
  id : Json,
  invocation : ToolInvocation,
) -> Json {
  match invocation {
    DiagnosticsReadPaint =>
      match host.paint_summary() {
        Ok(value) => tool_success(id, value.to_json())
        Err(error) =>
          tool_failure(id, error.code(), "Diagnostics host is closed.")
      }
    _ => tool_registry_mismatch(id)
  }
}

///|
fn[Host : @agent.AgentDiagnosticsHost] dispatch_diagnostics_event_tool(
  host : Host,
  id : Json,
  invocation : ToolInvocation,
) -> Json {
  match invocation {
    DiagnosticsDispatchEvent(event) =>
      match host.dispatch_event(event) {
        Ok(value) => tool_success(id, value.to_json())
        Err(error) =>
          tool_failure(id, error.code(), "Diagnostics host is closed.")
      }
    _ => tool_registry_mismatch(id)
  }
}

///|
fn[Host : @agent.AgentDiagnosticsHost] dispatch_diagnostics_command_tool(
  host : Host,
  id : Json,
  invocation : ToolInvocation,
) -> Json {
  match invocation {
    DiagnosticsDispatchCommand(intent) =>
      match host.dispatch_command(intent) {
        Ok(value) => tool_success(id, value.to_json())
        Err(error) =>
          tool_failure(id, error.code(), "Diagnostics host is closed.")
      }
    _ => tool_registry_mismatch(id)
  }
}

///|
fn tool_registry_mismatch(id : Json) -> Json {
  tool_failure(
    id, "internal_tool_registry_error", "Tool registry decoder and dispatcher do not match.",
  )
}

///|
fn fields_are_exact(
  fields : Map[String, Json],
  allowed : Array[String],
) -> Bool {
  for name, _ in fields {
    if !allowed.contains(name) {
      return false
    }
  }
  true
}

///|
fn decode_read_semantics_invocation_with_codec(
  arguments : Map[String, Json],
  generation : JsonCodec[UInt64],
) -> Result[ToolInvocation, String] {
  if !fields_are_exact(arguments, ["since"]) {
    return Err("read_semantics contains unknown arguments.")
  }
  match decode_since_with_codec(arguments, generation) {
    Ok(since) => Ok(ReadSemantics(since))
    Err(message) => Err(message)
  }
}

///|
fn decode_action_request_with_codecs(
  arguments : Map[String, Json],
  target : JsonCodec[@core.SemanticsTarget],
  action : JsonCodec[@core.SemanticsAction],
  precondition : JsonCodec[@agent.AgentGenerationPrecondition],
) -> Result[ToolInvocation, String] {
  if !fields_are_exact(arguments, ["target", "action", "precondition"]) {
    return Err("perform_action contains unknown arguments.")
  }
  guard arguments.get("target") is Some(target_json) else {
    return Err("Missing tagged 'target' object.")
  }
  guard arguments.get("action") is Some(action_json) else {
    return Err("Missing tagged 'action' object.")
  }
  guard arguments.get("precondition") is Some(precondition_json) else {
    return Err("Missing tagged 'precondition' object.")
  }
  match
    (
      (target.decode)(target_json),
      (action.decode)(action_json),
      (precondition.decode)(precondition_json),
    ) {
    (Ok(target), Ok(action), Ok(precondition)) =>
      Ok(PerformAction({ target, action, precondition, }))
    (Err(message), _, _) => Err(message)
    (_, Err(message), _) => Err(message)
    (_, _, Err(message)) => Err(message)
  }
}

///|
fn decode_diagnostics_read_state_invocation(
  arguments : Map[String, Json],
) -> Result[ToolInvocation, String] {
  if arguments.length() == 0 {
    Ok(DiagnosticsReadState)
  } else {
    Err("diagnostics_read_state does not accept arguments.")
  }
}

///|
fn decode_diagnostics_read_paint_invocation(
  arguments : Map[String, Json],
) -> Result[ToolInvocation, String] {
  if arguments.length() == 0 {
    Ok(DiagnosticsReadPaint)
  } else {
    Err("diagnostics_read_paint does not accept arguments.")
  }
}

///|
fn decode_diagnostics_dispatch_event_invocation(
  arguments : Map[String, Json],
) -> Result[ToolInvocation, String] {
  if !fields_are_exact(arguments, ["event"]) {
    return Err("diagnostics_dispatch_event contains unknown arguments.")
  }
  match decode_event_argument(arguments) {
    Some(event) => Ok(DiagnosticsDispatchEvent(event))
    None => Err("Invalid event payload.")
  }
}

///|
fn decode_diagnostics_dispatch_command_invocation(
  arguments : Map[String, Json],
) -> Result[ToolInvocation, String] {
  if !fields_are_exact(arguments, ["intent"]) {
    return Err("diagnostics_dispatch_command contains unknown arguments.")
  }
  match decode_command_argument(arguments) {
    Some(intent) => Ok(DiagnosticsDispatchCommand(intent))
    None => Err("Invalid command intent.")
  }
}

///|
fn decode_since_with_codec(
  arguments : Map[String, Json],
  generation : JsonCodec[UInt64],
) -> Result[@core.SemanticsGeneration?, String] {
  match arguments.get("since") {
    None => Ok(None)
    Some(value) =>
      match (generation.decode)(value) {
        Ok(generation) => Ok(Some(@core.SemanticsGeneration::new(generation)))
        Err(_) => Err("'since' must be a canonical UInt64 decimal string.")
      }
  }
}

///|
fn json_string_field(fields : Map[String, Json], name : String) -> String? {
  match fields.get(name) {
    Some(String(value)) => Some(value)
    _ => None
  }
}

///|
fn decode_target_with_codecs(
  fields : Map[String, Json],
  semantic_id : JsonCodec[@core.SemanticId],
  node_id : JsonCodec[UInt64],
) -> Result[@core.SemanticsTarget, String] {
  if !fields_are_exact(fields, ["kind", "id"]) {
    return Err("Target contains unknown fields.")
  }
  guard json_string_field(fields, "kind") is Some(kind) else {
    return Err("Target requires string 'kind'.")
  }
  guard fields.get("id") is Some(value) else {
    return Err("Target requires 'id'.")
  }
  match kind {
    "semantic_id" =>
      match (semantic_id.decode)(value) {
        Ok(id) => Ok(BySemanticId(id))
        Err(message) => Err(message)
      }
    "node_id" =>
      match (node_id.decode)(value) {
        Ok(id) => Ok(ByNodeId(@core.SemanticsNodeId::new(id)))
        Err(_) => Err("Invalid node_id.")
      }
    _ => Err("Unknown target kind.")
  }
}

///|
fn decode_action(
  fields : Map[String, Json],
) -> Result[@core.SemanticsAction, String] {
  guard json_string_field(fields, "kind") is Some(kind) else {
    return Err("Action requires string 'kind'.")
  }
  match kind {
    "activate" =>
      if fields_are_exact(fields, ["kind"]) {
        Ok(Activate)
      } else {
        Err("activate contains unknown fields.")
      }
    "focus" =>
      if fields_are_exact(fields, ["kind"]) {
        Ok(Focus)
      } else {
        Err("focus contains unknown fields.")
      }
    "set_text" =>
      match
        (
          fields_are_exact(fields, ["kind", "value"]),
          json_string_field(fields, "value"),
        ) {
        (false, _) => Err("set_text contains unknown fields.")
        (true, Some(value)) => Ok(SetText(value))
        (true, None) => Err("set_text requires string 'value'.")
      }
    "submit" =>
      if fields_are_exact(fields, ["kind"]) {
        Ok(Submit)
      } else {
        Err("submit contains unknown fields.")
      }
    "scroll" =>
      match
        (
          fields_are_exact(fields, ["kind", "direction"]),
          json_string_field(fields, "direction"),
        ) {
        (false, _) => Err("scroll contains unknown fields.")
        (true, Some(direction)) =>
          match decode_scroll_direction(direction) {
            Some(value) => Ok(Scroll(value))
            None => Err("Invalid scroll direction.")
          }
        (true, None) => Err("scroll requires string 'direction'.")
      }
    "select" =>
      if fields_are_exact(fields, ["kind"]) {
        Ok(Select)
      } else {
        Err("select contains unknown fields.")
      }
    "expand" =>
      if fields_are_exact(fields, ["kind"]) {
        Ok(Expand)
      } else {
        Err("expand contains unknown fields.")
      }
    "collapse" =>
      if fields_are_exact(fields, ["kind"]) {
        Ok(Collapse)
      } else {
        Err("collapse contains unknown fields.")
      }
    "dismiss" =>
      if fields_are_exact(fields, ["kind"]) {
        Ok(Dismiss)
      } else {
        Err("dismiss contains unknown fields.")
      }
    "increment" =>
      if fields_are_exact(fields, ["kind"]) {
        Ok(Increment)
      } else {
        Err("increment contains unknown fields.")
      }
    "decrement" =>
      if fields_are_exact(fields, ["kind"]) {
        Ok(Decrement)
      } else {
        Err("decrement contains unknown fields.")
      }
    "set_numeric_value" =>
      match fields.get("value") {
        Some(Number(value, repr=_)) if fields_are_exact(fields, [
            "kind", "value",
          ]) => Ok(SetNumericValue(value))
        Some(_) => Err("set_numeric_value requires numeric 'value'.")
        None => Err("set_numeric_value requires numeric 'value'.")
      }
    "set_selection" =>
      match (fields.get("start"), fields.get("end")) {
        (Some(Number(start, repr=_)), Some(Number(end, repr=_))) if fields_are_exact(
            fields,
            ["kind", "start", "end"],
          ) &&
          start >= 0.0 &&
          end >= 0.0 &&
          start == start.to_int().to_double() &&
          end == end.to_int().to_double() =>
          Ok(
            SetSelection(
              @core.TextRange::new(start=start.to_int(), end=end.to_int()),
            ),
          )
        _ => Err("set_selection requires non-negative integer start and end.")
      }
    "show_menu" =>
      if fields_are_exact(fields, ["kind"]) {
        Ok(ShowMenu)
      } else {
        Err("show_menu contains unknown fields.")
      }
    _ => Err("Unknown action kind.")
  }
}

///|
fn decode_scroll_direction(value : String) -> @core.SemanticsScrollDirection? {
  match value {
    "forward" => Some(Forward)
    "backward" => Some(Backward)
    "up" => Some(Up)
    "down" => Some(Down)
    "left" => Some(Left)
    "right" => Some(Right)
    _ => None
  }
}

///|
fn decode_precondition_with_codec(
  fields : Map[String, Json],
  generation : JsonCodec[UInt64],
) -> Result[@agent.AgentGenerationPrecondition, String] {
  guard json_string_field(fields, "kind") is Some(kind) else {
    return Err("Precondition requires string 'kind'.")
  }
  match kind {
    "latest" =>
      if fields_are_exact(fields, ["kind"]) {
        Ok(Latest)
      } else {
        Err("latest precondition contains unknown fields.")
      }
    "exact" =>
      match
        (
          fields_are_exact(fields, ["kind", "generation"]),
          fields.get("generation"),
        ) {
        (false, _) => Err("exact precondition contains unknown fields.")
        (true, Some(value)) =>
          match (generation.decode)(value) {
            Ok(generation) =>
              Ok(Exact(@core.SemanticsGeneration::new(generation)))
            Err(_) => Err("Invalid exact generation.")
          }
        (true, None) => Err("exact precondition requires string 'generation'.")
      }
    _ => Err("Unknown precondition kind.")
  }
}

///|
fn agent_semantics_issue_json(issue : @agent.AgentSemanticsIssue) -> Json {
  match issue {
    DuplicateSemanticId(id, nodes) =>
      (
        { "kind": "duplicate_semantic_id", "semantic_id": id, "nodes": nodes } :
        Json)
    MissingRelationTarget(source, kind, id) =>
      (
        {
          "kind": "missing_relation_target",
          "source": source,
          "relation": semantics_relation_kind_code(kind),
          "semantic_id": id,
        } : Json)
    AmbiguousRelationTarget(source, kind, id, nodes) =>
      (
        {
          "kind": "ambiguous_relation_target",
          "source": source,
          "relation": semantics_relation_kind_code(kind),
          "semantic_id": id,
          "nodes": nodes,
        } : Json)
  }
}

///|
fn semantics_relation_kind_code(kind : @core.SemanticsRelationKind) -> String {
  match kind {
    LabelledBy => "labelled_by"
    DescribedBy => "described_by"
    Controls => "controls"
    ErrorMessage => "error_message"
    ActiveDescendant => "active_descendant"
  }
}

///|
fn semantics_role_code(role : @core.SemanticsRole) -> String {
  match role {
    None => "none"
    Text => "text"
    Link => "link"
    Heading => "heading"
    Navigation => "navigation"
    Main => "main"
    Button => "button"
    Checkbox => "checkbox"
    TextField => "text_field"
    List => "list"
    ListItem => "list_item"
    Grid => "grid"
    ScrollView => "scroll_view"
    Switch => "switch"
    Radio => "radio"
    Slider => "slider"
    Progress => "progress"
    Tab => "tab"
    Dialog => "dialog"
    Tooltip => "tooltip"
    Menu => "menu"
    ButtonGroup => "button_group"
    Form => "form"
    FormField => "form_field"
    Tree => "tree"
    TreeItem => "tree_item"
    Group => "group"
    WebView => "web_view"
    Image => "image"
    Alert => "alert"
    Status => "status"
    ComboBox => "combo_box"
    Option => "option"
    Table => "table"
    Row => "row"
    Cell => "cell"
    Separator => "separator"
  }
}

///|
fn optional_node_id_json(value : @core.SemanticsNodeId?) -> Json {
  match value {
    Some(node_id) => node_id.to_json()
    None => json_null()
  }
}

///|
fn optional_semantic_id_json(value : @core.SemanticId?) -> Json {
  match value {
    Some(semantic_id) => semantic_id.to_json()
    None => json_null()
  }
}

///|
fn optional_int_json(value : Int?) -> Json {
  match value {
    Some(number) => number.to_json()
    None => json_null()
  }
}

///|
fn optional_checked_json(value : @core.SemanticsCheckedState?) -> Json {
  match value {
    Some(Unchecked) => "unchecked".to_json()
    Some(Checked) => "checked".to_json()
    Some(Mixed) => "mixed".to_json()
    None => json_null()
  }
}

///|
fn semantics_state_json(state : @core.SemanticsState) -> Json {
  (
    {
      "focused": state.focused,
      "disabled": state.disabled,
      "selected": state.selected,
      "pressed": state.pressed,
      "checked": optional_checked_json(state.checked),
      "expanded": state.expanded,
      "invalid": state.invalid,
      "required": state.required,
      "read_only": state.read_only,
      "busy": state.busy,
      "multiline": state.multiline,
      "password": state.password,
      "modal": state.modal,
    } : Json)
}

///|
fn semantics_live_code(value : @core.SemanticsLive) -> String {
  match value {
    Off => "off"
    Polite => "polite"
    Assertive => "assertive"
  }
}

///|
fn accessibility_announcement_json(
  announcement : @agent.AgentAccessibilityAnnouncement,
) -> Json {
  (
    {
      "node_id": announcement.node_id,
      "generation": announcement.generation,
      "live": semantics_live_code(announcement.live),
      "atomic": announcement.is_atomic,
      "text": announcement.text,
    } : Json)
}

///|
fn semantics_node_json(node : @core.SemanticsNode) -> Json {
  (
    {
      "node_id": node.node_id,
      "semantic_id": optional_semantic_id_json(node.semantic_id),
      "role": semantics_role_code(node.role),
      "level": optional_int_json(node.level),
      "url": node.url,
      "label": node.label,
      "value": node.value,
      "description": node.description,
      "state": semantics_state_json(node.state),
      "numeric": node.numeric,
      "text": node.text,
      "collection": node.collection,
      "relations": node.relations,
      "live": semantics_live_code(node.live),
      "atomic": node.live_atomic,
      "actions": node.actions.map(semantics_action_kind_code),
      "focus_order": optional_int_json(node.focus_order),
      "frame": node.frame,
      "children": node.children,
    } : Json)
}

///|
fn agent_semantics_read_json(read : @agent.AgentSemanticsRead) -> Json {
  match read {
    Full(snapshot) =>
      (
        {
          "kind": "full",
          "generation": snapshot.generation,
          "root": optional_node_id_json(snapshot.root),
          "focused": optional_node_id_json(snapshot.focused),
          "nodes": snapshot.nodes.map(semantics_node_json),
          "issues": snapshot.issues.map(agent_semantics_issue_json),
        } : Json)
    Delta(delta) =>
      (
        {
          "kind": "delta",
          "from": delta.from,
          "to": delta.to,
          "root": optional_node_id_json(delta.root),
          "focused": optional_node_id_json(delta.focused),
          "upserted": delta.upserted.map(semantics_node_json),
          "removed": delta.removed,
          "issues": delta.issues.map(agent_semantics_issue_json),
          "announcements": delta.announcements.map(
            accessibility_announcement_json,
          ),
        } : Json)
    Unchanged(generation) =>
      ({ "kind": "unchanged", "generation": generation } : Json)
  }
}

///|
fn decode_command_argument(
  arguments : Map[String, Json],
) -> @core.CommandIntent? {
  guard json_string_field(arguments, "intent") is Some(intent) else {
    return None
  }
  match intent {
    "Activate" => Some(Activate)
    "Submit" => Some(Submit)
    "Cancel" => Some(Cancel)
    "Copy" => Some(Copy)
    "Cut" => Some(Cut)
    "Paste" => Some(Paste)
    "Undo" => Some(Undo)
    "Redo" => Some(Redo)
    "SelectAll" => Some(SelectAll)
    "OpenContextMenu" => Some(OpenContextMenu)
    _ => None
  }
}

///|
fn decode_event_argument(arguments : Map[String, Json]) -> @core.AppEvent? {
  guard arguments.get("event") is Some(Object(fields)) else { return None }
  guard json_string_field(fields, "kind") is Some("Pointer") else {
    return None
  }
  guard fields.get("position") is Some(Object(position)) else { return None }
  guard position.get("x") is Some(Number(x, repr=_)) else { return None }
  guard position.get("y") is Some(Number(y, repr=_)) else { return None }
  guard json_string_field(fields, "phase") is Some(phase) else { return None }
  let pointer_phase = match phase {
    "Down" => @core.PointerPhase::Down
    "Up" => Up
    "Move" => Move
    "Cancel" => Cancel
    "Exit" => Exit
    _ => return None
  }
  Some(
    Pointer(
      @core.PointerEvent::new(
        position=@core.Point::new(x~, y~),
        phase=pointer_phase,
      ),
    ),
  )
}