// OpenAPI security scheme objects (← FastAPI surfacing `OAuth2PasswordBearer`,
// `HTTPBearer`, `APIKeyHeader`, etc. under `components/securitySchemes`). A
// `SecurityScheme` is declared on the `App` and emitted into the generated spec
// so the OAuth2 / JWT layer from the security module is described to clients.
// Both spec shapes are produced: OpenAPI 3.x `securitySchemes`, and the Swagger
// 2.0 `securityDefinitions` shape (a flat OAuth2 flow, no `http` type).

///|
/// A security scheme describing how a client authenticates. Mirrors the OpenAPI
/// scheme types: an OAuth2 password flow (its token URL and named scopes), an
/// HTTP bearer scheme (with a `bearerFormat` such as `JWT`), an API key in a
/// header / query / cookie, and HTTP Basic.
pub(all) enum SecurityScheme {
  OAuth2Password(token_url~ : String, scopes~ : Array[(String, String)])
  HttpBearer(bearer_format~ : String)
  ApiKeyHeader(name~ : String)
  ApiKeyQuery(name~ : String)
  ApiKeyCookie(name~ : String)
  HttpBasic
}

///|
/// Build the security scheme that describes this password-bearer flow (← the
/// object FastAPI derives from `OAuth2PasswordBearer`). `scopes` are the
/// `(name, description)` pairs advertised in the OpenAPI document.
pub fn OAuth2PasswordBearer::scheme(
  self : OAuth2PasswordBearer,
  scopes? : Array[(String, String)] = [],
) -> SecurityScheme {
  OAuth2Password(token_url=self.token_url, scopes~)
}

///|
/// A `{name: description, ...}` JSON object from scope pairs.
fn scopes_object(scopes : Array[(String, String)]) -> Json {
  let m : Map[String, Json] = Map([])
  for pair in scopes {
    m[pair.0] = pair.1.to_json()
  }
  m.to_json()
}

///|
/// This scheme as an OpenAPI 3.0 / 3.1 security scheme object.
fn SecurityScheme::to_openapi_3x(self : SecurityScheme) -> Json {
  match self {
    OAuth2Password(token_url~, scopes~) => {
      let flow : Map[String, Json] = Map([
        ("tokenUrl", token_url.to_json()),
        ("scopes", scopes_object(scopes)),
      ])
      let flows : Map[String, Json] = Map([("password", flow.to_json())])
      let m : Map[String, Json] = Map([
        ("type", "oauth2".to_json()),
        ("flows", flows.to_json()),
      ])
      m.to_json()
    }
    HttpBearer(bearer_format~) => {
      let m : Map[String, Json] = Map([
        ("type", "http".to_json()),
        ("scheme", "bearer".to_json()),
        ("bearerFormat", bearer_format.to_json()),
      ])
      m.to_json()
    }
    ApiKeyHeader(name~) => api_key_json("header", name)
    ApiKeyQuery(name~) => api_key_json("query", name)
    ApiKeyCookie(name~) => api_key_json("cookie", name)
    HttpBasic => {
      let m : Map[String, Json] = Map([
        ("type", "http".to_json()),
        ("scheme", "basic".to_json()),
      ])
      m.to_json()
    }
  }
}

///|
/// An `apiKey` scheme object for `in`/`name`.
fn api_key_json(in_ : String, name : String) -> Json {
  let m : Map[String, Json] = Map([
    ("type", "apiKey".to_json()),
    ("in", in_.to_json()),
    ("name", name.to_json()),
  ])
  m.to_json()
}

///|
/// This scheme as a Swagger 2.0 security definition. Swagger 2.0 has no `http`
/// type: an HTTP bearer is expressed as an `apiKey` in the `Authorization`
/// header (the standard 2.0 workaround), Basic uses the `basic` type, and the
/// OAuth2 flow is the flat 2.0 form (`flow` + `tokenUrl` at top level). Swagger
/// 2.0 `apiKey` only allows `header`/`query`, so a cookie key maps to a header.
fn SecurityScheme::to_openapi_20(self : SecurityScheme) -> Json {
  match self {
    OAuth2Password(token_url~, scopes~) => {
      let m : Map[String, Json] = Map([
        ("type", "oauth2".to_json()),
        ("flow", "password".to_json()),
        ("tokenUrl", token_url.to_json()),
        ("scopes", scopes_object(scopes)),
      ])
      m.to_json()
    }
    HttpBearer(_) => api_key_json("header", "Authorization")
    ApiKeyHeader(name~) => api_key_json("header", name)
    ApiKeyQuery(name~) => api_key_json("query", name)
    ApiKeyCookie(name~) => api_key_json("header", name)
    HttpBasic => {
      let m : Map[String, Json] = Map([("type", "basic".to_json())])
      m.to_json()
    }
  }
}

///|
/// The `{name: scheme, ...}` object for the app's declared schemes, rendered in
/// the shape `version` uses.
fn security_schemes_json(
  schemes : Array[(String, SecurityScheme)],
  version : OpenApiVersion,
) -> Json {
  let m : Map[String, Json] = Map([])
  for pair in schemes {
    m[pair.0] = match version {
      Swagger20 => pair.1.to_openapi_20()
      _ => pair.1.to_openapi_3x()
    }
  }
  m.to_json()
}