///|
/// JSON and JS helpers

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

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

///|
fn js_string_literal(value : String) -> String {
  let sb = StringBuilder::new()
  sb.write_char('"')
  for ch in value {
    match ch {
      '"' => sb.write_view("\\\"")
      '\\' => sb.write_view("\\\\")
      '\n' => sb.write_view("\\n")
      '\r' => sb.write_view("\\r")
      '\t' => sb.write_view("\\t")
      _ => sb.write_char(ch)
    }
  }
  sb.write_char('"')
  sb.to_string()
}

///|
/// Convert a WebDriver BiDi RemoteValue to MoonBit Json.
/// Handles primitives (string, number, boolean, null, undefined)
/// and recursively converts objects and arrays.
/// See: https://w3c.github.io/webdriver-bidi/#type-script-RemoteValue
fn remote_value_to_json(value : Json) -> Json {
  match value {
    Object(map) =>
      match map.get("type") {
        Some(String("string")) =>
          match map.get("value") {
            Some(String(s)) => Json::string(s)
            _ => Json::string("")
          }
        Some(String("number")) =>
          match map.get("value") {
            Some(Number(n, ..)) => Json::number(n)
            // BiDi encodes special numbers as strings
            Some(String("NaN")) => Json::number(0.0 / 0.0)
            Some(String("Infinity")) => Json::number(1.0 / 0.0)
            Some(String("-Infinity")) => Json::number(-1.0 / 0.0)
            Some(String("-0")) => Json::number(-0.0)
            _ => Json::number(0.0)
          }
        Some(String("boolean")) =>
          match map.get("value") {
            Some(True) => Json::boolean(true)
            Some(False) => Json::boolean(false)
            _ => Json::boolean(false)
          }
        Some(String("null")) | Some(String("undefined")) => Json::null()
        // Object: { type: "object", value: [[key_rv, val_rv], ...] }
        Some(String("object")) =>
          match map.get("value") {
            Some(Array(entries)) => {
              let obj : Map[String, Json] = {}
              for entry in entries {
                match entry {
                  Array([key_rv, val_rv]) =>
                    match remote_value_to_string(key_rv) {
                      Some(key) => obj[key] = remote_value_to_json(val_rv)
                      None => ()
                    }
                  _ => ()
                }
              }
              Json::object(obj)
            }
            _ => Json::null()
          }
        // Array: { type: "array", value: [rv, rv, ...] }
        Some(String("array")) =>
          match map.get("value") {
            Some(Array(items)) =>
              Json::array(items.map(fn(item) { remote_value_to_json(item) }))
            _ => Json::array([])
          }
        // Map: { type: "map", value: [[key_rv, val_rv], ...] }
        Some(String("map")) =>
          match map.get("value") {
            Some(Array(entries)) => {
              let obj : Map[String, Json] = {}
              for entry in entries {
                match entry {
                  Array([key_rv, val_rv]) =>
                    match remote_value_to_string(key_rv) {
                      Some(key) => obj[key] = remote_value_to_json(val_rv)
                      None => ()
                    }
                  _ => ()
                }
              }
              Json::object(obj)
            }
            _ => Json::null()
          }
        // Set: { type: "set", value: [rv, rv, ...] }
        Some(String("set")) =>
          match map.get("value") {
            Some(Array(items)) =>
              Json::array(items.map(fn(item) { remote_value_to_json(item) }))
            _ => Json::array([])
          }
        // Date: { type: "date", value: "ISO string" }
        Some(String("date")) =>
          match map.get("value") {
            Some(String(s)) => Json::string(s)
            _ => Json::null()
          }
        // RegExp: { type: "regexp", value: { pattern, flags } }
        Some(String("regexp")) =>
          match map.get("value") {
            Some(Object(rv)) =>
              match rv.get("pattern") {
                Some(String(p)) => Json::string(p)
                _ => Json::null()
              }
            _ => Json::null()
          }
        // Node/Window/etc. — return null
        Some(String("node")) | Some(String("window")) => Json::null()
        _ => value
      }
    _ => value
  }
}

///|
fn remote_value_to_string(value : Json) -> String? {
  match value {
    // Bare string (not wrapped in {type:"string",value:...})
    String(s) => Some(s)
    Number(n, ..) => Some(n.to_string())
    True => Some("true")
    False => Some("false")
    Object(map) =>
      match map.get("type") {
        Some(String("string")) =>
          match map.get("value") {
            Some(String(s)) => Some(s)
            _ => None
          }
        Some(String("number")) =>
          match map.get("value") {
            Some(Number(n, ..)) => Some(n.to_string())
            _ => None
          }
        Some(String("boolean")) =>
          match map.get("value") {
            Some(True) => Some("true")
            Some(False) => Some("false")
            _ => None
          }
        Some(String("null")) => Some("null")
        Some(String("undefined")) => Some("undefined")
        _ => None
      }
    _ => None
  }
}

///|
fn[T] consume(value : T) -> Unit {
  let _ = value

}

///|
/// Detect if a string looks like a JavaScript function expression.
/// Returns true for: "() => ...", "(a) => ...", "function(...", "async (...", "async function"
fn is_function_expression(s : String) -> Bool {
  let trimmed = s.trim_space().to_string()
  if trimmed.has_prefix("function") ||
    trimmed.has_prefix("async function") ||
    trimmed.has_prefix("async (") ||
    trimmed.has_prefix("async()") {
    return true
  }
  // Arrow function patterns: "() =>", "(a) =>", "(a, b) =>", "a =>"
  if trimmed.has_prefix("(") {
    return true
  }
  // Single-param arrow: "x => ..."
  match trimmed.find("=>") {
    Some(_) => true
    None => false
  }
}

///|
let noop_client : @bidi.BidiClient = @bidi.BidiClient::new(
  @bidi.Transport::unsupported("noop"),
)

///|
async fn async_noop() -> Unit {
  let _ = noop_client.connect()

}

///|
async fn sleep_ms(ms : Int) -> Unit {
  if ms <= 0 {
    return
  }
  let queue : @async.Queue[Unit] = @async.Queue::new(
    kind=@aqueue.Kind::Unbounded,
  )
  let _ = @async.with_timeout_opt(ms, async fn() -> Unit { queue.get() })

}

///|
fn json_to_remote_value(value : Json) -> Json {
  match value {
    String(s) =>
      Json::object({ "type": Json::string("string"), "value": Json::string(s) })
    Number(n, ..) =>
      Json::object({ "type": Json::string("number"), "value": Json::number(n) })
    True =>
      Json::object({
        "type": Json::string("boolean"),
        "value": Json::boolean(true),
      })
    False =>
      Json::object({
        "type": Json::string("boolean"),
        "value": Json::boolean(false),
      })
    Null => Json::object({ "type": Json::string("null") })
    _ => value
  }
}