// Copyright 2026 PaiGack
// Licensed under the Apache License, Version 2.0.
// Ported from jlaffaye/ftp (ISC License), see LICENSE-THIRD-PARTY.

// Layer: IO — uses the `moonbitlang/async` stack from the root moon.pkg.

// The FTP client handle and the connection state it carries.
//
// This file merges `client` and `state`: `FTPClient` owns a `Session`, the
// `Session` owns the `Control` connection, and `Options` is the resolved form
// of the caller facing `DialOptions`. The four types are a single object graph
// and the accessors on `FTPClient` were the only readers of `Session::*`, so
// keeping them in separate files only hid where the state lives.

///|
/// The FTP client, the equivalent of upstream `ftp.ServerConn`.
///
/// Upstream documents this type as *not* safe for concurrent use. We keep the
/// same constraint but enforce it with a `Mutex` so that a misuse shows up as
/// serialized commands instead of a corrupted session — a deliberate
/// improvement over the Go implementation, with unchanged semantics.
pub struct FTPClient {
  /// Connection parameters negotiated at dial/login time.
  options : DialOptions
  /// The same parameters projected onto what the data channel layer needs.
  state_options : Options
  /// The control connection.
  session : Session
  /// Whether the connection is currently open.
  mut closed : Bool
  /// Peer address as given by the caller (`host:port`).
  host : String
  /// Serializes every public operation.
  mutex : @async.Mutex
  /// Capability flags collected from `FEAT`.
  mut mlst_supported : Bool
  mut mfmt_supported : Bool
  mut mdtm_supported : Bool
  mut mdtm_can_write : Bool
  /// The timezone used to interpret `LIST` timestamps.
  mut location : @time.Zone
  /// Feature names advertised by `FEAT`, uppercased.
  mut features : Map[String, Bool]
}

///|
/// Acquire the internal lock, making every public operation serialized.
///
/// Upstream documents `ServerConn` as *not* concurrency safe; enforcing it at
/// runtime is a deliberate improvement that does not change the semantics.
pub async fn FTPClient::lock(self : FTPClient) -> Unit {
  self.mutex.acquire()
}

///|
/// Release the internal lock.
pub fn FTPClient::unlock(self : FTPClient) -> Unit {
  self.mutex.release()
}

///|
/// Whether the server advertised `name` in its `FEAT` list.
pub fn FTPClient::has_feature(self : FTPClient, name : String) -> Bool {
  self.features.contains(name.to_upper())
}

///|
/// Whether the server advertised `MLST` (and `MLSD` was not disabled
/// explicitly).
pub fn FTPClient::is_mlst_supported(self : FTPClient) -> Bool {
  self.mlst_supported
}

///|
/// Whether the server accepts `MFMT` to set modification times.
pub fn FTPClient::is_mfmt_supported(self : FTPClient) -> Bool {
  self.mfmt_supported
}

///|
/// Whether the server accepts `MDTM` to read modification times.
pub fn FTPClient::is_get_time_supported(self : FTPClient) -> Bool {
  self.mdtm_supported
}

///|
/// Whether the modification time can be written, either via `MFMT` or via the
/// VsFtpd style `MDTM