// Testing utilities for Cloudflare Workers with miniflare
// Usage: Set globalThis.__MINIFLARE_D1__ before running tests

///|
/// Get D1 database from global test environment (set by miniflare test harness)
pub fn get_test_d1() -> D1Database? {
  let global = get_global_this()
  let db = global["__MINIFLARE_D1__"]
  if @core.typeof_(db) == "undefined" || @core.is_null(db) {
    None
  } else {
    Some(db.cast())
  }
}

///|
/// Get D1 database from global test environment, panic if not available
pub fn get_test_d1_exn() -> D1Database {
  match get_test_d1() {
    Some(db) => db
    None =>
      abort("D1 test database not available. Run tests with miniflare harness.")
  }
}

///|
extern "js" fn get_global_this() -> @core.Any =
  #| () => globalThis

// ============================================================================
// Miniflare bindings for direct testing
// ============================================================================

///|
/// Miniflare instance type
#external
pub type Miniflare

///|
pub fn Miniflare::as_any(self : Miniflare) -> @core.Any = "%identity"

///|
/// Miniflare options for creating a new instance
pub(all) struct MiniflareOptions {
  script : String?
  modules : Bool
  d1Databases : Array[String]?
  r2Buckets : Array[String]?
  kvNamespaces : Array[String]?
}

///|
/// Create default MiniflareOptions
pub fn MiniflareOptions::default() -> MiniflareOptions {
  {
    script: None,
    modules: true,
    d1Databases: None,
    r2Buckets: None,
    kvNamespaces: None,
  }
}

///|
/// Dynamic import for miniflare package (ESM)
extern "js" fn import_miniflare() -> @js_async.Promise[@core.Any] =
  #|() => import("miniflare")

///|
/// Create a new Miniflare instance (async due to ESM dynamic import)
pub async fn Miniflare::new(options : MiniflareOptions) -> Miniflare {
  let mod = import_miniflare().wait()
  let klass : @core.Any = mod["Miniflare"]
  let opts = miniflare_options_to_js(options)
  @core.new(klass, [opts]).cast()
}

///|
/// Convert MiniflareOptions to JS object
fn miniflare_options_to_js(options : MiniflareOptions) -> @core.Any {
  let entries : Array[(String, @core.Any)] = []
  entries.push(("modules", @core.any(options.modules)))
  match options.script {
    Some(s) => entries.push(("script", @core.any(s)))
    None =>
      entries.push(
        (
          "script",
          @core.any(
            "export default { async fetch() { return new Response(\"OK\"); } }",
          ),
        ),
      )
  }
  match options.d1Databases {
    Some(dbs) => entries.push(("d1Databases", @core.any(dbs)))
    None => ()
  }
  match options.r2Buckets {
    Some(buckets) => entries.push(("r2Buckets", @core.any(buckets)))
    None => ()
  }
  match options.kvNamespaces {
    Some(ns) => entries.push(("kvNamespaces", @core.any(ns)))
    None => ()
  }
  @core.from_entries(entries)
}

///|
/// Wait for Miniflare to be ready
pub async fn Miniflare::ready(self : Miniflare) -> Unit {
  let promise : @js_async.Promise[Unit] = self.as_any()["ready"].cast()
  promise.wait()
}

///|
/// Dispose of Miniflare instance
pub async fn Miniflare::dispose(self : Miniflare) -> Unit {
  let promise : @js_async.Promise[Unit] = self
    .as_any()
    ._call("dispose", [])
    .cast()
  promise.wait()
}

///|
/// Get D1 database from Miniflare instance
pub async fn Miniflare::get_d1_database(
  self : Miniflare,
  binding_name : String,
) -> D1Database {
  let promise : @js_async.Promise[D1Database] = self
    .as_any()
    ._call("getD1Database", [@core.any(binding_name)])
    .cast()
  promise.wait()
}

///|
/// Get R2 bucket from Miniflare instance
pub async fn Miniflare::get_r2_bucket(
  self : Miniflare,
  binding_name : String,
) -> R2Bucket {
  let promise : @js_async.Promise[R2Bucket] = self
    .as_any()
    ._call("getR2Bucket", [@core.any(binding_name)])
    .cast()
  promise.wait()
}

///|
/// Get KV namespace from Miniflare instance
pub async fn Miniflare::get_kv_namespace(
  self : Miniflare,
  binding_name : String,
) -> KVNamespace {
  let promise : @js_async.Promise[KVNamespace] = self
    .as_any()
    ._call("getKVNamespace", [@core.any(binding_name)])
    .cast()
  promise.wait()
}