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

///|
/// Which server is answering on the MySQL wire. TiDB reports a MySQL version *and* its
/// own in the same string (`8.0.11-TiDB-v8.5.0-…`), so the sentence has to be read
/// rather than assumed: a statement that works on MySQL 8 may not parse here, and one
/// that works on TiDB may not exist there.
#derive(Eq)
pub(all) enum Server {
  Mysql
  Mariadb
  Tidb
}

///|
pub fn Server::name(self : Server) -> String {
  match self {
    Mysql => "mysql"
    Mariadb => "mariadb"
    Tidb => "tidb"
  }
}

///|
/// The server a `SELECT VERSION()` answer names. The order matters: TiDB prefixes its
/// answer with a MySQL version for the benefit of old clients, so `tidb` has to be
/// looked for before the string is taken at face value.
pub fn Server::from_version(version : String) -> Server {
  let lowered = version.to_lower()
  if lowered.contains("tidb") {
    Tidb
  } else if lowered.contains("mariadb") {
    Mariadb
  } else {
    Mysql
  }
}

///|
/// Connect the way [`@client.MysqlConn::connect`] does — the protocol is the same one.
pub async fn connect(
  host : String,
  port : Int32,
  user : String,
  password : String,
  database : String,
  tls? : @tls.Client,
) -> @client.MysqlConn {
  @client.MysqlConn::connect(host, port, user, password, database, tls?)
}

///|
/// The server's own version sentence, as it answered.
pub async fn version(conn : @client.MysqlConn) -> String raise @moondb.DbError {
  let rows = conn.query("SELECT VERSION() AS version", []) catch {
    e => raise @moondb.QueryError(e.to_string())
  }
  guard rows.length() > 0 else {
    raise @moondb.QueryError("SELECT VERSION() returned no rows")
  }
  rows[0].text_by("version")
}

///|
/// Which server is behind this connection, asked rather than assumed.
pub async fn server_of(
  conn : @client.MysqlConn,
) -> Server raise @moondb.DbError {
  Server::from_version(version(conn))
}

///|
/// What *this server* can do, as opposed to what the driver implements. On TiDB the one
/// flag that has to go is `multiple_result_sets`: a stored procedure that returns a
/// result set is rejected at `CREATE PROCEDURE` time, so `query_multi` has nothing to
/// read. Everything else the MySQL driver offers — prepared statements, savepoints,
/// transactions, unsigned 64-bit columns — holds.
pub fn capabilities_for(server : Server) -> @moondb.Capabilities {
  let base = @moondb.Capabilities::for_dialect(@moondb.Dialect::Mysql)
  match server {
    Mysql | Mariadb => base
    Tidb => { ..base, multiple_result_sets: false, }
  }
}

///|
/// Comparing two server names is the whole point of naming them.
pub extend Server with Eq::{not_equal, equal}