///| Typed Cloudflare binding contract helpers.

///|
/// Cloudflare binding category used for dev/test validation and docs.
pub(all) enum BindingKind {
  EnvVar
  Secret
  KV
  R2
  D1
  DurableObject
  Service
  AI
  Queue
}

///|
pub fn BindingKind::label(self : BindingKind) -> String {
  match self {
    EnvVar => "var"
    Secret => "secret"
    KV => "kv"
    R2 => "r2"
    D1 => "d1"
    DurableObject => "durable_object"
    Service => "service"
    AI => "ai"
    Queue => "queue"
  }
}

///|
/// One expected Cloudflare binding.
pub(all) struct BindingSpec {
  name : String
  kind : BindingKind
  optional : Bool
}

///|
/// A required binding that is missing from the Worker env.
pub(all) struct MissingBinding {
  name : String
  kind : BindingKind
}

///|
pub fn binding(
  name : String,
  kind : BindingKind,
  optional? : Bool = false,
) -> BindingSpec {
  { name, kind, optional }
}

///|
/// Required plain env var.
pub fn env_var(name : String, optional? : Bool = false) -> BindingSpec {
  binding(name, EnvVar, optional~)
}

///|
/// Required secret env var.
pub fn secret(name : String, optional? : Bool = false) -> BindingSpec {
  binding(name, Secret, optional~)
}

///|
/// Required KV namespace binding.
pub fn kv_namespace(name : String, optional? : Bool = false) -> BindingSpec {
  binding(name, KV, optional~)
}

///|
/// Required R2 bucket binding.
pub fn r2_bucket(name : String, optional? : Bool = false) -> BindingSpec {
  binding(name, R2, optional~)
}

///|
/// Required D1 database binding.
pub fn d1_database(name : String, optional? : Bool = false) -> BindingSpec {
  binding(name, D1, optional~)
}

///|
/// Required Durable Object namespace binding.
pub fn durable_object(name : String, optional? : Bool = false) -> BindingSpec {
  binding(name, DurableObject, optional~)
}

///|
/// Required Service Binding.
pub fn service_binding(name : String, optional? : Bool = false) -> BindingSpec {
  binding(name, Service, optional~)
}

///|
/// Required Workers AI binding.
pub fn ai_binding(name : String, optional? : Bool = false) -> BindingSpec {
  binding(name, AI, optional~)
}

///|
/// Required Queue binding.
pub fn queue_binding(name : String, optional? : Bool = false) -> BindingSpec {
  binding(name, Queue, optional~)
}

///|
extern "js" fn ffi_env_has_binding(env : @mars.JsEnv, name : String) -> Bool =
  #| (env, name) => env != null && env[name] !== undefined && env[name] !== null

///|
/// Return all missing required bindings for a Cloudflare Worker env.
pub fn missing_required_bindings(
  env : @mars.JsEnv,
  specs : Array[BindingSpec],
) -> Array[MissingBinding] {
  let missing : Array[MissingBinding] = []
  for spec in specs {
    if !spec.optional && !ffi_env_has_binding(env, spec.name) {
      missing.push({ name: spec.name, kind: spec.kind })
    }
  }
  missing
}

///|
/// True when every required binding exists.
pub fn validate_bindings(env : @mars.JsEnv, specs : Array[BindingSpec]) -> Bool {
  missing_required_bindings(env, specs).is_empty()
}

///|
/// Human-readable validation error for missing bindings.
pub fn describe_missing_bindings(missing : Array[MissingBinding]) -> String {
  if missing.is_empty() {
    return "all required Cloudflare bindings are present"
  }
  let buf = StringBuilder::new()
  buf.write_string("missing Cloudflare bindings: ")
  for i, item in missing {
    if i > 0 {
      buf.write_string(", ")
    }
    buf.write_string(item.name)
    buf.write_string(" (")
    buf.write_string(item.kind.label())
    buf.write_string(")")
  }
  buf.to_string()
}