// Connection configuration and TLS policy for the PostgreSQL client package.

///|
/// Controls how the client negotiates TLS when opening a TCP connection.
///
/// The modes are intentionally strict:
/// plaintext is opt-in via `Disable`, while TLS-using callers choose between
/// certificate-chain verification with or without endpoint identity checks.
pub(all) enum SslMode {
  /// Explicit plaintext mode.
  ///
  /// The client skips PostgreSQL SSL negotiation, does not encrypt traffic,
  /// and does not verify the server's identity.
  Disable
  /// Require TLS and verify the server certificate chain.
  ///
  /// This mode never falls back to plaintext. It validates the certificate
  /// chain against the configured trust roots, **but it does not verify that the
  /// certificate matches the requested hostname or IP address!**
  ///
  /// Its security can approach `VerifyFull` only when a private CA issues
  /// certificates exclusively for the intended PostgreSQL endpoints.
  VerifyCa
  /// Require TLS, verify the certificate chain, and verify endpoint identity.
  ///
  /// This mode never falls back to plaintext. It validates both the server
  /// certificate chain and the requested hostname or IP address, and is the
  /// default and recommended mode.
  VerifyFull
} derive(Debug, Eq)

///|
/// Controls whether SCRAM channel binding is disabled, preferred, or required.
///
/// This setting controls whether the connection must complete SCRAM channel
/// binding. It does not enable TLS by itself.
/// It is also unrelated to TLS `sslmode`: this package still does not support
/// the removed libpq-style TLS aliases `sslmode=prefer` or `sslmode=require`.
///
/// `Disable` always uses plain `SCRAM-SHA-256`.
/// `Prefer` uses `SCRAM-SHA-256-PLUS` when the TLS transport exposes a
/// supported binding and the server advertises it, otherwise it falls back to
/// plain `SCRAM-SHA-256`.
/// `Require` insists on `SCRAM-SHA-256-PLUS` and fails when TLS or server
/// support is missing. Because non-SCRAM authentication methods cannot employ
/// PostgreSQL channel binding, `Require` rejects them before sending
/// authentication credentials.
pub(all) enum ChannelBinding {
  Disable
  Prefer
  Require
} derive(Debug, Eq)

///|
/// Immutable configuration supplied to `Client::create`.
///
/// The returned `ClientExecutor::run` uses this value to establish a PostgreSQL
/// session. The client runtime retains it so that later features, such as
/// cancellation, can reopen a control connection with the exact same host and
/// port. Its `Debug` representation replaces a configured password with
/// ``.
pub struct Config {
  /// Hostname used for TLS verification and default server-name handling.
  host : String
  /// Optional concrete address used for the TCP socket connection.
  hostaddr : String?
  /// TCP port for the PostgreSQL server.
  port : Int32
  /// Login role used during startup authentication.
  user : String
  /// Database selected in the startup packet.
  database : String
  /// Optional password consumed by password-based authentication methods.
  /// PostgreSQL chooses the authentication method at startup; cleartext
  /// password and SCRAM authentication require this field to be present.
  password : String?
  /// TLS negotiation policy.
  ssl_mode : SslMode
  /// Root certificate file used to verify the server certificate chain.
  ///
  /// `None` keeps the platform default trust-store behavior. The special value
  /// `"system"` also selects the platform trust store and requires
  /// `ssl_mode == SslMode::VerifyFull`.
  ssl_root_cert : String?
  /// Channel-binding preference for SCRAM authentication.
  channel_binding : ChannelBinding
  /// Value sent as PostgreSQL `application_name`.
  application_name : String
  /// Optional PostgreSQL `options` startup parameter.
  options : String?
  /// Optional end-to-end connection timeout in milliseconds.
  connect_timeout_ms : Int32?
  /// Whether TCP keepalive should be enabled on the socket.
  keepalives : Bool?
  /// Idle time in seconds before the first TCP keepalive probe.
  keepalives_idle_s : Int32?
} derive(Eq)

///|
/// Keep connection diagnostics useful without rendering the password.
pub impl @debug.Debug for Config with fn to_repr(self) {
  let password : String? = if self.password is Some(_) {
    Some("")
  } else {
    None
  }
  @debug.Repr::record({
    "host": @debug.Repr(self.host),
    "hostaddr": @debug.Repr(self.hostaddr),
    "port": @debug.Repr(self.port),
    "user": @debug.Repr(self.user),
    "database": @debug.Repr(self.database),
    "password": @debug.Repr(password),
    "ssl_mode": @debug.Repr(self.ssl_mode),
    "ssl_root_cert": @debug.Repr(self.ssl_root_cert),
    "channel_binding": @debug.Repr(self.channel_binding),
    "application_name": @debug.Repr(self.application_name),
    "options": @debug.Repr(self.options),
    "connect_timeout_ms": @debug.Repr(self.connect_timeout_ms),
    "keepalives": @debug.Repr(self.keepalives),
    "keepalives_idle_s": @debug.Repr(self.keepalives_idle_s),
  })
}

///|
/// Build a configuration with PostgreSQL-friendly defaults.
///
/// Defaults are chosen to keep secure TLS on the main path: port `5432`,
/// `ssl_mode = SslMode::VerifyFull`, and database name equal to the user name.
/// Callers pass `application_name` explicitly.
pub fn Config::Config(
  host : String,
  hostaddr? : String,
  port~ : Int32 = 5432,
  user~ : String,
  database~ : String = user,
  password? : String,
  ssl_mode~ : SslMode = VerifyFull,
  ssl_root_cert? : String,
  channel_binding~ : ChannelBinding = Disable,
  application_name~ : String,
  options? : String,
  connect_timeout_ms? : Int32,
  keepalives? : Bool,
  keepalives_idle_s? : Int32,
) -> Config {
  {
    host,
    hostaddr,
    port,
    user,
    database,
    password,
    ssl_mode,
    ssl_root_cert,
    channel_binding,
    application_name,
    options,
    connect_timeout_ms,
    keepalives,
    keepalives_idle_s,
  }
}

///|
/// Internal constructor for a single PostgreSQL connection target.
///
/// Used by pgpool to build one client config per endpoint. Callers
/// supply every field explicitly; no defaults or validation are applied here.
#internal(internal, "for internal use only")
pub fn Config::new_internal(
  host : String,
  hostaddr : String?,
  port : Int32,
  user : String,
  database : String,
  password : String?,
  ssl_mode : SslMode,
  ssl_root_cert : String?,
  channel_binding : ChannelBinding,
  application_name : String,
  options : String?,
  connect_timeout_ms : Int32?,
  keepalives : Bool?,
  keepalives_idle_s : Int32?,
) -> Config {
  {
    host,
    hostaddr,
    port,
    user,
    database,
    password,
    ssl_mode,
    ssl_root_cert,
    channel_binding,
    application_name,
    options,
    connect_timeout_ms,
    keepalives,
    keepalives_idle_s,
  }
}