///|
#external
pub type Statement

///|
#external
priv type StatementRef

///|
extern "c" fn StatementRef::malloc() -> StatementRef = "moonbit_sqlite3_mkref"

///|
extern "c" fn StatementRef::read(self : StatementRef) -> Statement = "moonbit_sqlite3_readref"

///|
#external
priv type UTF8StrRef

///|
extern "c" fn UTF8StrRef::malloc() -> UTF8StrRef = "moonbit_sqlite3_mkref"

// TODO: fix tail

///|
#borrow(stmt)
extern "c" fn sqlite3_prepare(
  conn : Connection,
  stmt : Bytes,
  len : Int,
  out : StatementRef,
  tail : UTF8StrRef,
) -> Int = "sqlite3_prepare_v2"

///|
extern "c" fn sqlite3_step(stmt : Statement) -> Int = "sqlite3_step"

///|
extern "c" fn sqlite3_finalize(stmt : Statement) -> Int = "sqlite3_finalize"

///|
#callsite(autofill(loc))
pub fn Connection::prepare(
  self : Connection,
  stmt : String,
  loc~ : SourceLoc,
) -> Statement raise SqliteError {
  let stmt = @encoding/utf8.encode(stmt)
  let len = stmt.length()
  let out = StatementRef::malloc()
  let tail = UTF8StrRef::malloc()
  let rescode = sqlite3_prepare(self, stmt, len, out, tail)
  if rescode == SQLITE_OK {
    let stmt_object = out.read()
    return stmt_object
  } else {
    raise SqliteError((rescode, loc))
  }
}

///|
#callsite(autofill(loc))
pub fn[T : Bind] Statement::bind(
  self : Statement,
  index~ : Int,
  val~ : T,
  loc~ : SourceLoc,
) -> Unit raise SqliteError {
  T::bind(self, index, val, loc~)
}

///|
#callsite(autofill(loc))
pub fn Statement::bind_string_as_blob(
  self : Statement,
  index~ : Int,
  val~ : String,
  loc~ : SourceLoc,
) -> Unit raise SqliteError {
  let rescode = sqlite3_bind_string(self, index, val, val.length() * 2)
  if rescode != SQLITE_OK {
    raise SqliteError((rescode, loc))
  }
}

///|
pub fn[T : Column] Statement::column(self : Statement, index~ : Int) -> T {
  T::column(self, index)
}

///|
pub fn Statement::column_blob_as_string(
  self : Statement,
  index~ : Int,
) -> String {
  sqlite3_column_string(self, index)
}

///|
#callsite(autofill(loc))
pub fn Statement::step(
  self : Statement,
  loc~ : SourceLoc,
) -> Bool raise SqliteError {
  let rescode = sqlite3_step(self)
  match rescode {
    SQLITE_DONE => false
    SQLITE_ROW => true
    _ => raise SqliteError((rescode, loc))
  }
}

///|
#callsite(autofill(loc))
pub fn Statement::step_once(
  self : Statement,
  loc~ : SourceLoc,
) -> Unit raise SqliteError {
  if self.step(loc~) {
    raise SqliteError((SQLITE_ROW, loc))
  }
}

///|
#callsite(autofill(loc))
pub fn Statement::finalize(
  self : Statement,
  loc~ : SourceLoc,
) -> Unit raise SqliteError {
  let rescode = sqlite3_finalize(self)
  if rescode != SQLITE_OK {
    raise SqliteError((rescode, loc))
  }
}