///|
/// SMTP authentication mechanism selection.
pub(all) enum AuthMethod {
  Login(String, String)
  Plain(String, String)
  CramMd5(String, String)
} derive(Eq, Debug)

///|
pub fn AuthMethod::username(self : AuthMethod) -> String {
  match self {
    Login(u, _) => u
    Plain(u, _) => u
    CramMd5(u, _) => u
  }
}

///|
/// Connection configuration for an SMTP session.
pub struct SmtpConfig {
  host : String
  port : Int
  mut auth : AuthMethod?
  mut timeout_ms : Int64
  mut connect_retries : Int
} derive(Eq, Debug)

///|
pub fn SmtpConfig::new(host : String, port? : Int = 25) -> SmtpConfig {
  { host, port, auth: None, timeout_ms: 30000, connect_retries: 1 }
}

///|
pub fn SmtpConfig::host(self : SmtpConfig) -> String {
  self.host
}

///|
pub fn SmtpConfig::port(self : SmtpConfig) -> Int {
  self.port
}

///|
pub fn SmtpConfig::auth(self : SmtpConfig) -> AuthMethod? {
  self.auth
}

///|
pub fn SmtpConfig::timeout_ms(self : SmtpConfig) -> Int64 {
  self.timeout_ms
}

///|
pub fn SmtpConfig::connect_retries(self : SmtpConfig) -> Int {
  self.connect_retries
}

///|
pub fn SmtpConfig::with_auth(
  self : SmtpConfig,
  auth : AuthMethod,
) -> SmtpConfig {
  self.auth = Some(auth)
  self
}

///|
pub fn SmtpConfig::with_timeout(
  self : SmtpConfig,
  timeout_ms : Int64,
) -> SmtpConfig {
  self.timeout_ms = timeout_ms
  self
}

///|
pub fn SmtpConfig::with_connect_retries(
  self : SmtpConfig,
  retries : Int,
) -> SmtpConfig {
  self.connect_retries = retries
  self
}

///|
/// The transport abstraction. The SMTP session talks only to this interface:
/// the network adapter (`socket_transport`) and the deterministic scripted
/// server (`FakeTransport`) both implement it. The core never touches a socket
/// directly, so it stays portable across backends.
pub(open) trait SmtpTransport {
  ///| Establish the connection (and send a greeting for scripted servers).
  async fn connect(Self, SmtpConfig) -> Unit raise MailFailure
  ///| Write one command line (without the trailing CRLF).
  async fn write_line(Self, String) -> Unit raise MailFailure
  ///| Read one response line (without the trailing CRLF).
  async fn read_line(Self) -> String raise MailFailure
  ///| Close the connection (synchronous, like the underlying socket close).
  fn close(Self) -> Unit
}