/*
 * Copyright (C) 2022-2025 Seagull Developers
 *
 * Seagull is the legal property of its developers, whose names are too
 * numerous to list here. Please refer to the AUTHORS file distributed
 * with this source distribution
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
 */

#if !defined(SEAGULL_GLOBAL_HEADER_INSIDE) && !defined(SEAGULL_COMPILATION)
# error "only <seagull.h> may be included directly"
#endif

#ifndef SEAGULL_STATEMENT_H
#define SEAGULL_STATEMENT_H

#include <glib.h>
#include <gio/gio.h>

#include "seagulltypes.h"
#include "seagullversion.h"

G_BEGIN_DECLS

#define SEAGULL_STATEMENT_ERROR seagull_statement_error_quark()

/**
 * SeagullStatementError:
 * @SEAGULL_STATEMENT_ERROR_NO_DB_PROVIDED: The db property was not specified.
 * @SEAGULL_STATEMENT_ERROR_NO_SQL_PROVIDED: The sql property was not
 *  specified.
 * @SEAGULL_STATEMENT_ERROR_PREPARATION_FAILED: The statement failed to
 *  prepare.
 * @SEAGULL_STATEMENT_ERROR_MORE_THAN_ONE_STATEMENT: More than one SQL
 *  statement was provided.
 * @SEAGULL_STATEMENT_ERROR_BIND_PARAMETER_NOT_FOUND: The named column was not
 *  found.
 * @SEAGULL_STATEMENT_ERROR_BIND_FAILED: Binding a parameter failed.
 * @SEAGULL_STATEMENT_ERROR_CLEAR_BINDINGS_FAILED: Clearing the bindings
 *  failed.
 * @SEAGULL_STATEMENT_ERROR_RESET_FAILED: Resetting the statement failed.
 * @SEAGULL_STATEMENT_ERROR_STEP_FAILED: Stepping the statement failed.
 * @SEAGULL_STATEMENT_ERROR_WRONG_COLUMN_TYPE: The requested column was not of
 *  the requested type.
 *
 * Error codes returned by statements.
 *
 * Since: 0.3
 */
typedef enum {
	SEAGULL_STATEMENT_ERROR_NO_DB_PROVIDED SEAGULL_AVAILABLE_ENUMERATOR_IN_0_3,
	SEAGULL_STATEMENT_ERROR_NO_SQL_PROVIDED SEAGULL_AVAILABLE_ENUMERATOR_IN_0_3,
	SEAGULL_STATEMENT_ERROR_PREPARATION_FAILED SEAGULL_AVAILABLE_ENUMERATOR_IN_0_3,
	SEAGULL_STATEMENT_ERROR_MORE_THAN_ONE_STATEMENT SEAGULL_AVAILABLE_ENUMERATOR_IN_0_3,
	SEAGULL_STATEMENT_ERROR_BIND_PARAMETER_NOT_FOUND SEAGULL_AVAILABLE_ENUMERATOR_IN_0_3,
	SEAGULL_STATEMENT_ERROR_BIND_FAILED SEAGULL_AVAILABLE_ENUMERATOR_IN_0_3,
	SEAGULL_STATEMENT_ERROR_CLEAR_BINDINGS_FAILED SEAGULL_AVAILABLE_ENUMERATOR_IN_0_3,
	SEAGULL_STATEMENT_ERROR_RESET_FAILED SEAGULL_AVAILABLE_ENUMERATOR_IN_0_3,
	SEAGULL_STATEMENT_ERROR_STEP_FAILED SEAGULL_AVAILABLE_ENUMERATOR_IN_0_3,
	SEAGULL_STATEMENT_ERROR_WRONG_COLUMN_TYPE SEAGULL_AVAILABLE_ENUMERATOR_IN_0_3,
	/**
	 * SEAGULL_STATEMENT_ERROR_BIND_OBJECT_PROPERTY_UNKNOWN_TYPE:
	 *
	 * A property was found for the given parameter, but the value type is
	 * unknown.
	 *
	 * Since: 0.4
	 */
	SEAGULL_STATEMENT_ERROR_BIND_OBJECT_PROPERTY_UNKNOWN_TYPE SEAGULL_AVAILABLE_ENUMERATOR_IN_0_4,
	/**
	 * SEAGULL_STATEMENT_ERROR_COLUMN_OBJECT_PROPERTY_UNKNOWN_TYPE:
	 *
	 * A property was found for the given column, but the value type is
	 * unknown.
	 *
	 * Since: 0.4
	 */
	SEAGULL_STATEMENT_ERROR_COLUMN_OBJECT_PROPERTY_UNKNOWN_TYPE SEAGULL_AVAILABLE_ENUMERATOR_IN_0_4,
	/**
	 * SEAGULL_STATEMENT_ERROR_FAILED_TO_LOAD_RESOURCE:
	 *
	 * The given resource was not able to be loaded.
	 *
	 * Since: 0.4
	 */
	SEAGULL_STATEMENT_ERROR_FAILED_TO_LOAD_RESOURCE SEAGULL_AVAILABLE_ENUMERATOR_IN_0_4,
} SeagullStatementError;

/**
 * seagull_statement_error_quark:
 *
 * The error domain to identify errors with statements.
 *
 * Returns: The error domain.
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
GQuark seagull_statement_error_quark(void);

#define SEAGULL_TYPE_STATEMENT (seagull_statement_get_type())

/**
 * SeagullStatement:
 *
 * A wrapper around sqlite3_stmt to make it easier to work with named
 * parameters.
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
G_DECLARE_FINAL_TYPE(SeagullStatement, seagull_statement, SEAGULL, STATEMENT,
                     GObject)

/**
 * seagull_statement_bind_double:
 * @name: the name of the parameter
 * @value: the value.
 * @error: (out) (optional): a return address for a #GError
 *
 * Binds a double value to the named parameter.
 *
 * @name must be the exact name as specified in the query.
 *
 * Returns: true on success; otherwise false with error set.
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
gboolean seagull_statement_bind_double(SeagullStatement *statement, const char *name, double value, GError **error);

/**
 * seagull_statement_bind_int:
 * @name: the name of the parameter
 * @value: the value.
 * @error: (out) (optional): a return address for a #GError
 *
 * Binds an integer value to the named parameter.
 *
 * @name must be the exact name as specified in the query.
 *
 * Returns: true on success; otherwise false with error set.
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
gboolean seagull_statement_bind_int(SeagullStatement *statement, const char *name, int value, GError **error);

/**
 * seagull_statement_bind_null:
 * @name: the name of the parameter
 * @error: (out) (optional): a return address for a #GError
 *
 * Binds a NULL value to the named parameter.
 *
 * @name must be the exact name as specified in the query.
 *
 * Returns: true on success; otherwise false with error set.
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
gboolean seagull_statement_bind_null(SeagullStatement *statement, const char *name, GError **error);

/**
 * seagull_statement_bind_object:
 * @prefix: (nullable): an optional prefix
 * @obj: the object
 * @error: (out) (optional): a return address for a #GError
 *
 * Binds an object to the query.
 *
 * The parameters of the statement will be walked and if a property matching
 * the name of a parameter is found, then the parameter will be bound to the
 * value of that property.
 *
 * If parameters have conflicting names, you can pass a prefix to make sure the
 * correct ones are bound. Note that the prefix needs to look like a valid SQL
 * column name, so no spaces or dashes. See the following example.
 *
 * ```c
 * stmt = seagull_statement_new("INSERT INTO messages(id, author) "
 *                              "VALUES(:msg-id, :author-id)");
 * seagull_statement_bind_object(stmt, "msg_", message, &error);
 * seagull_statement_bind_object(stmt, "author_", author, &error);
 * ```
 *
 * This will look for properties named `id` on both the message and author
 * instances.
 *
 * Note that if a prefix is provided, it will be required for all parameters.
 * See the following example
 *
 * ```c
 * stmt = seagull_statement_new("INSERT INTO messages(id, author) "
 *                              "VALUES(:id, :author-id)");
 * // This bind doesn't match anything as there are no parameters with a msg-
 * // prefix.
 * seagull_statement_bind_object(stmt, "msg_", message, &error);
 *
 * // By not using a prefix, the :id parameter will get match to message:id.
 * seagull_statement_bind_object(stmt, NULL, message, &error);
 *
 * // Using a prefix with the author will only match the author-id parameter
 * // and leave the id parameter to the previously bound value.
 * seagull_statement_bind_object(stmt, "author_", author, &error);
 * ```
 *
 * Returns: true on success; otherwise false with error set.
 */
SEAGULL_AVAILABLE_IN_0_4
gboolean seagull_statement_bind_object(SeagullStatement *statement, const char *prefix, GObject *obj, GError **error);

/**
 * seagull_statement_bind_text:
 * @name: the name of the parameter
 * @value: the value.
 * @len: the length of the value in bytes, or -1 for NULL terminated
 * @destroy_notify: (nullable): a function to be called to free the value
 * @error: (out) (optional): a return address for a #GError
 *
 * Binds a text value to the named parameter.
 *
 * @name must be the exact name as specified in the query.
 *
 * Returns: true on success; otherwise false with error set.
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
gboolean seagull_statement_bind_text(SeagullStatement *statement, const char *name, const char *value, int len, GDestroyNotify destroy_notify, GError **error);

/**
 * seagull_statement_clear_bindings:
 * @error: (out) (optional): a return address for a #GError
 *
 * Clears all of the bindings.
 *
 * Returns: true on success.
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
gboolean seagull_statement_clear_bindings(SeagullStatement *statement, GError **error);

/**
 * seagull_statement_column_double:
 * @name: the name of the column
 * @error: (out) (optional): a return address for a #GError
 *
 * Gets the value of a column from the current result of a statement.
 *
 * @name must be the exact name or alias of the column as specified in the
 * query.
 *
 * Returns: The value on success or 0 on error with error set.
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
double seagull_statement_column_double(SeagullStatement *statement, const char *name, GError **error);

/**
 * seagull_statement_column_int:
 * @name: the name of the column
 * @error: (out) (optional): a return address for a #GError
 *
 * Gets the value of a column from the current result of a statement.
 *
 * @name must be the exact name or alias of the column as specified in the
 * query.
 *
 * Returns: The value on success or 0 on error with error set.
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
int seagull_statement_column_int(SeagullStatement *statement, const char *name, GError **error);

/**
 * seagull_statement_column_object:
 * @prefix: (nullable): an optional prefix
 * @obj: the object
 * @error: (out) (optional): a return address for a #GError
 *
 * Updates the properties of any object based on the current result of a
 * statement.
 *
 * This will look for columns with the same name of the properties in @obj, and
 * if found, set the property in @obj to the return value. This is a helper
 * around manually calling [method@Statement.column_double],
 * [method@Statement.column_int], [method@Statement.column_text], etc.
 *
 * @prefix may be used to limit what columns to look for. Note that you most
 * likely will need to use the SQL `AS` keyword to make the column names have
 * the prefix like the following example.
 *
 * ```c
 * sql = "SELECT id AS message_id, content AS message_content "
 *       "FROM messages WHERE conversion_id=:conversation-id "
 *       "ORDER BY created_timestamp DESC LIMIT 10";
 * stmt = seagull_statement_new(db, SQL, NULL);
 * seagull_statement_bind_string(stmt, "conversation-id", "some-random-id", -1,
 *                               NULL, NULL);
 * seagull_statement_step(stmt, NULL);
 * message = new_message();
 * seagull_statement_column_object(stmt, "message_", message, NULL);
 * ```
 *
 * Note that the object must exist for this to work, so if the object has a
 * construct only property you can pull that out ahead of time and create the
 * object and then call this function. See the following example.
 *
 * ```c
 * sql = "SELECT id, title AS conversation_title, topic AS conversation_topic "
 *       "FROM conversations WHERE account_id=:account-id";
 * stmt = seagull_statement_new(db, sql, NULL);
 * seagull_statement_bind_string(stmt, "account-id", "some-account-id", -1,
 *                               NULL, NULL);
 * seagull_statement_step(stmt, NULL);
 * conversation_id = seagull_statement_column_string(stmt, "id", NULL);
 * conversation = conversation_new(conversation_id);
 * seagull_statement_column_object(stmt, "conversation_", conversation, NULL);
 * ```
 *
 * Note that true will be returned even if no properties in @obj have been
 * updated.
 *
 * Returns: true on success; or false on error with error set.
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
gboolean seagull_statement_column_object(SeagullStatement *statement, const char *prefix, GObject *obj, GError **error);

/**
 * seagull_statement_column_text:
 * @name: the name of the column
 * @error: (out) (optional): a return address for a #GError
 *
 * Gets the value of a column from the current result of a statement.
 *
 * @name must be the exact name or alias of the column as specified in the
 * query.
 *
 * Returns: (nullable): The value on success or null on error with error set.
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
const char *seagull_statement_column_text(SeagullStatement *statement, const char *name, GError **error);

/**
 * seagull_statement_get_db:
 *
 * Gets the database.
 *
 * Returns: (transfer none): The database.
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
SeagullSqlite3 *seagull_statement_get_db(SeagullStatement *statement);

/**
 * seagull_statement_get_expanded_sql:
 *
 * Gets the SQL statement with parameters expanded.
 *
 * If all parameters have not yet been bound, they will appears as their names.
 *
 * Returns: (transfer full): The expanded SQL statement which should be freed
 *          by calling [func@free].
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
char *seagull_statement_get_expanded_sql(SeagullStatement *statement);

/**
 * seagull_statement_get_sql:
 *
 * Gets the SQL statement.
 *
 * Returns: (transfer none): The SQL statement.
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
const char *seagull_statement_get_sql(SeagullStatement *statement);

/**
 * seagull_statement_get_stmt:
 *
 * Gets the underlying SQLite3 statement.
 *
 * Returns: (transfer none): The statement.
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
SeagullSqlite3Stmt *seagull_statement_get_stmt(SeagullStatement *statement);

/**
 * seagull_statement_new:
 * @db: the database instance
 * @sql: the SQL statement
 * @error: (out) (nullable): a return address for a #GError
 *
 * Creates a new prepared statement.
 *
 * Returns: (transfer full): The new instance.
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
SeagullStatement *seagull_statement_new(SeagullSqlite3 *db, const char *sql, GError **error);

/**
 * seagull_statement_new_from_resource:
 * @db: the database instance
 * @path: the base path of the resource to use
 * @error: (out) (nullable): a return address for a #GError
 *
 * Creates a new prepared statement from a resource.
 *
 * Returns: (transfer full): The new instance.
 *
 * Since: 0.4
 */
SEAGULL_AVAILABLE_IN_0_4
SeagullStatement *seagull_statement_new_from_resource(SeagullSqlite3 *db, const char *path, GError **error);

/**
 * seagull_statement_reset:
 * @error: (out) (optional): a return address for a #GError
 *
 * Resets the statement to its initial state.
 *
 * This does not clear any bindings, use [method@Statement.clear_bindings] to
 * do that.
 *
 * Returns: true on success; other false with @error set.
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
gboolean seagull_statement_reset(SeagullStatement *statement, GError **error);

/**
 * seagull_statement_step:
 * @error: (out) (optional): a return address for a #GError
 *
 * Evaluates the statement.
 *
 * If there is no data to read, %false will be returned, but @error will not be
 * set.
 *
 * Returns: true if there is data to read.
 *
 * Since: 0.3
 */
SEAGULL_AVAILABLE_IN_0_3
gboolean seagull_statement_step(SeagullStatement *statement, GError **error);

G_END_DECLS

#endif /* SEAGULL_STATEMENT_H */
