// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// The parameters needed to reach a PostgreSQL backend — a connection descriptor,
/// not a connection. [`connect`](PgDriver::connect) opens the live [`PgConn`],
/// which is the thing that implements the driver seam.
///
/// It exists so a caller can carry connection settings around (a config value, a
/// pool factory) and open a connection from them on demand, the way a DSN string
/// works elsewhere.
pub struct PgDriver {
  host : String
  port : Int
  user : String
  password : String
  database : String
}

///|
/// Build a connection descriptor. Does not connect: PostgreSQL I/O is async, so
/// the connection is opened by [`connect`](PgDriver::connect) inside an event loop.
pub fn PgDriver::new(
  host~ : String,
  port? : Int = 5432,
  user~ : String,
  password? : String = "",
  database~ : String,
) -> PgDriver {
  { host, port, user, password, database, }
}

///|
/// Open the connection this descriptor points at. Call it inside an event loop
/// (`async test` / `async fn main`); the returned [`PgConn`] is an
/// [`@moondb.AsyncDriver`].
pub async fn PgDriver::connect(self : PgDriver) -> PgConn raise DbError {
  PgConn::connect(self.host, self.port, self.user, self.password, self.database)
}

// The @moondb.AsyncDriver conformance. It is a direct delegation to PgConn's own
// methods rather than an adapter over something else: PgConn *is* the connection,
// and its API was already the shape of the seam. Postgres has no heartbeat cheaper
// than the trait's default `SELECT 1`, so `ping` is left to the default.

///|
/// Run a non-row statement with bound `params`.
pub impl @moondb.AsyncDriver for PgConn with fn execute(
  self : PgConn,
  sql : String,
  params : Array[Value],
) -> ExecResult raise DbError {
  PgConn::execute(self, sql, params)
}

///|
/// Run a row-returning statement with bound `params`, materialising every row.
pub impl @moondb.AsyncDriver for PgConn with fn query(
  self : PgConn,
  sql : String,
  params : Array[Value],
) -> Array[Row] raise DbError {
  PgConn::query(self, sql, params)
}

///|
/// Stream the statement's rows off the wire on demand. This overrides the trait
/// default (which materialises into an [`@moondb.AsyncArrayCursor`]) with the real
/// incremental cursor, so a large result is consumed in bounded memory.
pub impl @moondb.AsyncDriver for PgConn with fn query_stream(
  self : PgConn,
  sql : String,
  params : Array[Value],
) -> &@moondb.AsyncCursor raise DbError {
  PgConn::query_stream(self, sql, params)
}

///|
/// Begin an explicit transaction.
pub impl @moondb.AsyncDriver for PgConn with fn begin(self : PgConn) -> Unit raise DbError {
  PgConn::begin(self)
}

///|
/// Commit the current transaction.
pub impl @moondb.AsyncDriver for PgConn with fn commit(self : PgConn) -> Unit raise DbError {
  PgConn::commit(self)
}

///|
/// Roll back the current transaction.
pub impl @moondb.AsyncDriver for PgConn with fn rollback(self : PgConn) -> Unit raise DbError {
  PgConn::rollback(self)
}

///|
/// Send Terminate and close the socket; idempotent.
pub impl @moondb.AsyncDriver for PgConn with fn close(self : PgConn) -> Unit {
  PgConn::close(self)
}

///|
/// Yield the next row read off the wire, or `None` at the end of the result.
pub impl @moondb.AsyncCursor for PgRowStream with fn next(self : PgRowStream) -> Row? raise DbError {
  PgRowStream::next(self)
}

///|
/// Abandon the stream, draining the unread result so the connection can run the
/// next statement. The seam's `close` does not raise, so a failed drain is
/// swallowed — but the stream is then left un-finished on purpose, which keeps the
/// connection's streaming guard set and makes the next statement refuse rather than
/// read this result's leftovers.
pub impl @moondb.AsyncCursor for PgRowStream with fn close(self : PgRowStream) -> Unit {
  PgRowStream::close(self) catch {
    _ => ()
  }
}