// Cloudflare Turnstile verification bindings

///|
let turnstile_siteverify_endpoint = "https://challenges.cloudflare.com/turnstile/v0/siteverify"

///|
fn turnstile_is_js_nullish(value : @core.Any) -> Bool {
  @core.typeof_(value) == "undefined" || @core.is_null(value)
}

///|
fn turnstile_as_string_option(value : @core.Any) -> String? {
  if turnstile_is_js_nullish(value) {
    None
  } else {
    Some(value.cast())
  }
}

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

///|
extern "js" fn turnstile_verify_request(
  fetch_impl : @core.Any,
  endpoint : String,
  secret : String,
  response : String,
  remote_ip : @core.Any,
  idempotency_key : @core.Any,
) -> @core.Promise[@core.Any] =
  #| async (fetchImpl, endpoint, secret, response, remoteIp, idempotencyKey) => {
  #|   const doFetch = fetchImpl == null ? globalThis.fetch : fetchImpl;
  #|   const params = new URLSearchParams();
  #|   params.set("secret", secret);
  #|   params.set("response", response);
  #|   if (remoteIp !== undefined && remoteIp !== null) {
  #|     params.set("remoteip", String(remoteIp));
  #|   }
  #|   if (idempotencyKey !== undefined && idempotencyKey !== null) {
  #|     params.set("idempotency_key", String(idempotencyKey));
  #|   }
  #|   const res = await doFetch(endpoint, {
  #|     method: "POST",
  #|     body: params,
  #|   });
  #|   return await res.json();
  #| }

///|
pub(all) struct TurnstileVerifyOptions {
  remoteIp : String?
  idempotencyKey : String?
}

///|
pub fn TurnstileVerifyOptions::default() -> TurnstileVerifyOptions {
  { remoteIp: None, idempotencyKey: None }
}

///|
pub fn TurnstileVerifyOptions::remote_ip_js(
  self : TurnstileVerifyOptions,
) -> @core.Any {
  match self.remoteIp {
    Some(value) => @core.any(value)
    None => turnstile_js_null()
  }
}

///|
pub fn TurnstileVerifyOptions::idempotency_key_js(
  self : TurnstileVerifyOptions,
) -> @core.Any {
  match self.idempotencyKey {
    Some(value) => @core.any(value)
    None => turnstile_js_null()
  }
}

///|
#external
pub type TurnstileVerifyResult

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

///|
pub fn TurnstileVerifyResult::success(self : TurnstileVerifyResult) -> Bool {
  self.as_any()["success"].cast()
}

///|
pub fn TurnstileVerifyResult::challenge_ts(
  self : TurnstileVerifyResult,
) -> String? {
  turnstile_as_string_option(self.as_any()["challenge_ts"])
}

///|
pub fn TurnstileVerifyResult::hostname(self : TurnstileVerifyResult) -> String? {
  turnstile_as_string_option(self.as_any()["hostname"])
}

///|
pub fn TurnstileVerifyResult::action(self : TurnstileVerifyResult) -> String? {
  turnstile_as_string_option(self.as_any()["action"])
}

///|
pub fn TurnstileVerifyResult::cdata(self : TurnstileVerifyResult) -> String? {
  turnstile_as_string_option(self.as_any()["cdata"])
}

///|
pub fn TurnstileVerifyResult::error_codes(
  self : TurnstileVerifyResult,
) -> Array[String] {
  let value_hyphen = self.as_any()["error-codes"]
  if !turnstile_is_js_nullish(value_hyphen) {
    value_hyphen.cast()
  } else {
    let value_snake = self.as_any()["error_codes"]
    if turnstile_is_js_nullish(value_snake) {
      []
    } else {
      value_snake.cast()
    }
  }
}

///|
pub async fn verify_turnstile_token(
  secret : String,
  response : String,
  options? : TurnstileVerifyOptions,
  endpoint? : String,
) -> TurnstileVerifyResult {
  match options {
    Some(opt) =>
      match endpoint {
        Some(url) =>
          verify_turnstile_token_with_fetch(
            turnstile_js_null(),
            secret,
            response,
            options=opt,
            endpoint=url,
          )
        None =>
          verify_turnstile_token_with_fetch(
            turnstile_js_null(),
            secret,
            response,
            options=opt,
          )
      }
    None =>
      match endpoint {
        Some(url) =>
          verify_turnstile_token_with_fetch(
            turnstile_js_null(),
            secret,
            response,
            endpoint=url,
          )
        None =>
          verify_turnstile_token_with_fetch(
            turnstile_js_null(),
            secret,
            response,
          )
      }
  }
}

///|
pub async fn verify_turnstile_token_with_fetch(
  fetch_impl : @core.Any,
  secret : String,
  response : String,
  options? : TurnstileVerifyOptions,
  endpoint? : String,
) -> TurnstileVerifyResult {
  let verify_endpoint = endpoint.unwrap_or(turnstile_siteverify_endpoint)
  let effective_options = options.unwrap_or(TurnstileVerifyOptions::default())
  let promise : @core.Promise[@core.Any] = turnstile_verify_request(
    fetch_impl,
    verify_endpoint,
    secret,
    response,
    effective_options.remote_ip_js(),
    effective_options.idempotency_key_js(),
  )
  promise.wait().cast()
}