///|
/// JSON-RPC Protocol Layer
///
/// Provides JSON-RPC 2.0 compatible request/response handling
/// for the Browser API.

///|
pub using @contract {type ApiError, type ApiMethod}

// =============================================================================
// JSON-RPC Types
// =============================================================================

///|
/// JSON-RPC request
pub(all) struct RpcRequest {
  jsonrpc : String // Should be "2.0"
  id : RpcId
  method_name : String
  params : Map[String, String] // Simplified: all params as strings
} derive(Debug)

///|
/// JSON-RPC request ID (can be string or number)
pub(all) enum RpcId {
  Num(Int)
  Str(String)
  Null
} derive(Debug, Eq)

///|
/// JSON-RPC response
pub(all) enum RpcResponse {
  Success(id~ : RpcId, result~ : String) // result is JSON string
  Error(id~ : RpcId, code~ : Int, message~ : String, data~ : String?)
} derive(Debug)

// =============================================================================
// JSON-RPC Error Codes
// =============================================================================

///|
/// Standard JSON-RPC error codes
pub(all) enum RpcErrorCode {
  ParseError // -32700
  InvalidRequest // -32600
  MethodNotFound // -32601
  InvalidParams // -32602
  InternalError // -32603
  // Custom codes for browser automation
  ElementNotFound // -32000
  PageNotFound // -32001
  Timeout // -32002
  NavigationFailed // -32003
} derive(Debug, Eq)

///|
pub impl Show for RpcRequest with fn output(self, logger) {
  logger.write_string("{jsonrpc: ")
  self.jsonrpc.output(logger)
  logger.write_string(", id: ")
  self.id.output(logger)
  logger.write_string(", method_name: ")
  self.method_name.output(logger)
  logger.write_string(", params: ")
  logger.write_object(to_repr(self.params))
  logger.write_string("}")
}

///|
pub impl Show for RpcId with fn output(self, logger) {
  match self {
    Num(value) => {
      logger.write_string("Num(")
      value.output(logger)
      logger.write_string(")")
    }
    Str(value) => {
      logger.write_string("Str(")
      value.output(logger)
      logger.write_string(")")
    }
    Null => logger.write_string("Null")
  }
}

///|
pub impl Show for RpcResponse with fn output(self, logger) {
  match self {
    Success(id~, result~) => {
      logger.write_string("Success(id=")
      id.output(logger)
      logger.write_string(", result=")
      result.output(logger)
      logger.write_string(")")
    }
    Error(id~, code~, message~, data~) => {
      logger.write_string("Error(id=")
      id.output(logger)
      logger.write_string(", code=")
      code.output(logger)
      logger.write_string(", message=")
      message.output(logger)
      logger.write_string(", data=")
      logger.write_object(to_repr(data))
      logger.write_string(")")
    }
  }
}

///|
pub impl Show for RpcErrorCode with fn output(self, logger) {
  match self {
    ParseError => logger.write_string("ParseError")
    InvalidRequest => logger.write_string("InvalidRequest")
    MethodNotFound => logger.write_string("MethodNotFound")
    InvalidParams => logger.write_string("InvalidParams")
    InternalError => logger.write_string("InternalError")
    ElementNotFound => logger.write_string("ElementNotFound")
    PageNotFound => logger.write_string("PageNotFound")
    Timeout => logger.write_string("Timeout")
    NavigationFailed => logger.write_string("NavigationFailed")
  }
}

///|
/// Get numeric error code
pub fn RpcErrorCode::code(self : RpcErrorCode) -> Int {
  match self {
    ParseError => -32700
    InvalidRequest => -32600
    MethodNotFound => -32601
    InvalidParams => -32602
    InternalError => -32603
    ElementNotFound => -32000
    PageNotFound => -32001
    Timeout => -32002
    NavigationFailed => -32003
  }
}

// =============================================================================
// JSON-RPC Serialization
// =============================================================================

///|
/// Serialize RpcId to JSON
pub fn RpcId::to_json(self : RpcId) -> String {
  match self {
    Num(n) => n.to_string()
    Str(s) => "\"" + s + "\""
    Null => "null"
  }
}

///|
/// Serialize RpcResponse to JSON
pub fn RpcResponse::to_json(self : RpcResponse) -> String {
  match self {
    Success(id~, result~) =>
      "{\"jsonrpc\":\"2.0\",\"id\":" +
      id.to_json() +
      ",\"result\":" +
      result +
      "}"
    Error(id~, code~, message~, data~) => {
      let mut json = "{\"jsonrpc\":\"2.0\",\"id\":" + id.to_json()
      json = json + ",\"error\":{\"code\":" + code.to_string()
      json = json + ",\"message\":\"" + escape_string(message) + "\""
      match data {
        Some(d) => json = json + ",\"data\":" + d
        None => ()
      }
      json = json + "}}"
      json
    }
  }
}

///|
/// Escape string for JSON
fn escape_string(s : String) -> String {
  let buf = StringBuilder::new()
  for c in s.iter() {
    match c {
      '"' => buf.write_string("\\\"")
      '\\' => buf.write_string("\\\\")
      '\n' => buf.write_string("\\n")
      '\r' => buf.write_string("\\r")
      '\t' => buf.write_string("\\t")
      _ => buf.write_char(c)
    }
  }
  buf.to_string()
}

// =============================================================================
// JSON-RPC Response Builders
// =============================================================================

///|
/// Create success response with null result
pub fn rpc_success_null(id : RpcId) -> RpcResponse {
  Success(id~, result="null")
}

///|
/// Create success response with string result
pub fn rpc_success_string(id : RpcId, value : String) -> RpcResponse {
  Success(id~, result="\"" + escape_string(value) + "\"")
}

///|
/// Create success response with bool result
pub fn rpc_success_bool(id : RpcId, value : Bool) -> RpcResponse {
  Success(id~, result=if value { "true" } else { "false" })
}

///|
/// Create success response with number result
pub fn rpc_success_number(id : RpcId, value : Double) -> RpcResponse {
  Success(id~, result=value.to_string())
}

///|
/// Create success response with raw JSON result
pub fn rpc_success_json(id : RpcId, json : String) -> RpcResponse {
  Success(id~, result=json)
}

///|
/// Create error response
pub fn rpc_error(
  id : RpcId,
  code : RpcErrorCode,
  message : String,
) -> RpcResponse {
  Error(id~, code=code.code(), message~, data=None)
}

///|
/// Create error response with data
pub fn rpc_error_with_data(
  id : RpcId,
  code : RpcErrorCode,
  message : String,
  data : String,
) -> RpcResponse {
  Error(id~, code=code.code(), message~, data=Some(data))
}

// =============================================================================
// API Error to RPC Error Conversion
// =============================================================================

///|
/// Convert ApiError to RpcResponse
pub fn api_error_to_rpc(id : RpcId, err : ApiError) -> RpcResponse {
  match err {
    ElementNotFound(selector~) =>
      rpc_error(
        id,
        RpcErrorCode::ElementNotFound,
        "Element not found: " + selector,
      )
    PageNotFound(page_id~) =>
      rpc_error(id, RpcErrorCode::PageNotFound, "Page not found: " + page_id)
    ContextNotFound(context_id~) =>
      rpc_error(
        id,
        RpcErrorCode::PageNotFound,
        "Context not found: " + context_id,
      )
    ApiError::Timeout(message~) => rpc_error(id, RpcErrorCode::Timeout, message)
    NavigationFailed(url~, reason~) =>
      rpc_error(
        id,
        RpcErrorCode::NavigationFailed,
        "Navigation to " + url + " failed: " + reason,
      )
    ElementNotVisible(element_id~) =>
      rpc_error(id, InvalidParams, "Element not visible: " + element_id)
    ElementNotEnabled(element_id~) =>
      rpc_error(id, InvalidParams, "Element not enabled: " + element_id)
    EvalError(message~) =>
      rpc_error(id, RpcErrorCode::InternalError, "Eval error: " + message)
    InvalidArgument(message~) => rpc_error(id, InvalidParams, message)
    NotSupported(message~) => rpc_error(id, MethodNotFound, message)
    InternalError(message~) =>
      rpc_error(id, RpcErrorCode::InternalError, message)
  }
}

// =============================================================================
// Method Dispatch
// =============================================================================

///|
/// Parse method name to ApiMethod
pub fn parse_method(name : String) -> ApiMethod? {
  // Note: This is a simplified parser. In production, you'd want to
  // also parse the params here.
  match name {
    "newContext" | "Browser.createContext" => Some(NewContext)
    "newPage" | "Page.create" => Some(NewPage(context_id=""))
    "goto" | "Page.navigate" => Some(Goto(page_id="", url=""))
    "goBack" | "Page.goBack" => Some(GoBack(page_id=""))
    "goForward" | "Page.goForward" => Some(GoForward(page_id=""))
    "reload" | "Page.reload" => Some(Reload(page_id=""))
    "url" | "Page.url" => Some(Url(page_id=""))
    "title" | "Page.title" => Some(Title(page_id=""))
    "query" | "Element.query" => Some(Query(page_id="", selector=""))
    "queryAll" | "Element.queryAll" => Some(QueryAll(page_id="", selector=""))
    "click" | "Element.click" => Some(Click(element_id=""))
    "fill" | "Element.fill" => Some(Fill(element_id="", value=""))
    "textContent" | "Element.textContent" => Some(TextContent(element_id=""))
    "screenshot" | "Page.screenshot" => Some(Screenshot(page_id=""))
    "evaluate" | "Runtime.evaluate" => Some(Evaluate(page_id="", expression=""))
    "accessibilitySnapshot" | "Accessibility.snapshot" =>
      Some(AccessibilitySnapshot(page_id=""))
    _ => None
  }
}

// =============================================================================
// Simple JSON Parser (for params)
// =============================================================================

///|
/// Parse a simple JSON object into a Map
/// Note: This is a very simplified parser for demo purposes
pub fn parse_json_object(json : String) -> Map[String, String] {
  let result : Map[String, String] = {}
  let json = json.trim().to_owned()

  // Remove outer braces
  if !json.has_prefix("{") || !json.has_suffix("}") {
    return result
  }
  let content = substring(json, 1, json.length() - 1)
  let pairs = split_json_pairs(content)
  for pair in pairs {
    let parts = split_at_colon(pair)
    if parts.length() == 2 {
      let key = unquote(parts[0].trim().to_owned())
      let value = unquote(parts[1].trim().to_owned())
      result[key] = value
    }
  }
  result
}

///|
/// Get substring
fn substring(s : String, start : Int, end : Int) -> String {
  let buf = StringBuilder::new()
  let mut i = 0
  for c in s.iter() {
    if i >= start && i < end {
      buf.write_char(c)
    }
    i = i + 1
  }
  buf.to_string()
}

///|
/// Split JSON at top-level commas
fn split_json_pairs(s : String) -> Array[String] {
  let result : Array[String] = []
  let current = StringBuilder::new()
  let mut depth = 0
  let mut in_string = false
  for c in s.iter() {
    match c {
      '"' => {
        in_string = !in_string
        current.write_char(c)
      }
      '{' | '[' => {
        if !in_string {
          depth = depth + 1
        }
        current.write_char(c)
      }
      '}' | ']' => {
        if !in_string {
          depth = depth - 1
        }
        current.write_char(c)
      }
      ',' =>
        if !in_string && depth == 0 {
          result.push(current.to_string())
          current.reset()
        } else {
          current.write_char(c)
        }
      _ => current.write_char(c)
    }
  }
  let last = current.to_string()
  if !last.trim().to_owned().is_empty() {
    result.push(last)
  }
  result
}

///|
/// Split at first colon (for key:value)
fn split_at_colon(s : String) -> Array[String] {
  let result : Array[String] = []
  let mut found = false
  let before = StringBuilder::new()
  let after = StringBuilder::new()
  for c in s.iter() {
    if c == ':' && !found {
      found = true
    } else if found {
      after.write_char(c)
    } else {
      before.write_char(c)
    }
  }
  result.push(before.to_string())
  if found {
    result.push(after.to_string())
  }
  result
}

///|
/// Remove quotes from string
fn unquote(s : String) -> String {
  let s = s.trim().to_owned()
  if s.length() >= 2 && s.has_prefix("\"") && s.has_suffix("\"") {
    substring(s, 1, s.length() - 1)
  } else {
    s
  }
}