///|
/// A Bolt explicit transaction: BEGIN, run one or more statements, then COMMIT
/// or ROLLBACK.
///
/// An explicit transaction groups several statements so they commit or roll
/// back atomically. In auto-commit mode each statement commits on its own; an
/// explicit transaction defers the write until [`Transaction::commit`].
///
/// ```moonbit
/// let conn = BoltConnection::new(transport) // ... after handshake + HELLO
/// let tx = Transaction::new(conn)
/// assert_eq(tx.begin(), true)
/// let _ = tx.run("CREATE (n:Person {name: \$name})", [("name", ...)])
/// assert_eq(tx.commit(), true)
/// ```
///
/// The transaction tracks an `open` flag and refuses out-of-order calls: `run`
/// before `begin`, or a second `begin`/`commit`/`rollback`, return `false`/`None`
/// instead of corrupting the wire. A statement that the server rejects leaves
/// the connection in the `Failed` state; recover it with
/// [`BoltConnection::reset_conn`] (a failed transaction is rolled back by the
/// server and must be reset, per the Bolt protocol).

///|
/// An explicit Bolt transaction bound to a [`BoltConnection`].
pub struct Transaction[T] {
  conn : BoltConnection[T]
  mut open : Bool
}

///|
/// Wrap a connection in a (not yet started) transaction. The connection must
/// already be handshaken and authenticated (in the [`Ready`](ConnState::Ready)
/// state).
pub fn[T] Transaction::new(conn : BoltConnection[T]) -> Transaction[T] {
  { conn, open: false, }
}

///|
/// Begin the transaction with `BEGIN`. Returns `true` when the server accepted
/// it, `false` otherwise (already open, wrong connection state, or a server
/// failure).
pub fn[T : Transport] Transaction::begin(self : Transaction[T]) -> Bool {
  if self.open {
    return false
  }
  match self.conn.begin_tx([]) {
    Some(ServerMessage::Success(_)) => {
      self.open = true
      true
    }
    _ => false
  }
}

///|
/// Run a statement inside the transaction and pull every record, returning the
/// rows. Returns `None` when the transaction is not open, the connection is not
/// ready, or the server rejected the statement.
pub fn[T : Transport] Transaction::run(
  self : Transaction[T],
  query : String,
  parameters : Array[(String, PackStreamValue)],
) -> Array[Array[PackStreamValue]]? {
  if !self.open {
    return None
  }
  self.conn.run_query(query, parameters)
}

///|
/// Commit the transaction with `COMMIT`. Returns `true` on success; the
/// transaction is then closed either way.
pub fn[T : Transport] Transaction::commit(self : Transaction[T]) -> Bool {
  if !self.open {
    return false
  }
  self.open = false
  match self.conn.commit_tx() {
    Some(ServerMessage::Success(_)) => true
    _ => false
  }
}

///|
/// Roll the transaction back with `ROLLBACK`. Returns `true` on success; the
/// transaction is then closed either way.
pub fn[T : Transport] Transaction::rollback(self : Transaction[T]) -> Bool {
  if !self.open {
    return false
  }
  self.open = false
  match self.conn.rollback_tx() {
    Some(ServerMessage::Success(_)) => true
    _ => false
  }
}

///|
/// Whether the transaction is currently open.
pub fn[T] Transaction::is_open(self : Transaction[T]) -> Bool {
  self.open
}